網絡編程(自制服務器)

    如今的網絡應用隨處可見,沒法想象生活離開了網絡會變得怎樣,最多見的就是經過瀏覽器上網,在地址欄輸入 URL 敲擊回車,而後瀏覽器就呈現出相應的頁面。雖然如今的網絡應用五花八門,可是它們都是基於相同的編程模型,依賴相同的編程接口。java

    每一個網絡應用都是基於客戶端-服務端編程模型的,採用這個模型,一個應用是由一個服務器進程和一個或多個客戶端進程組成。由服務器管理着某種資源,經過操做這些資源來爲客戶端提供某種服務。須要注意的是,客戶端和服務端是進程,而不是咱們常提到的機器或主機。linux

    在系統級 I/O中已經提到過,網絡也僅僅是一種文件而已。一個插到 I/O 總線擴展槽的網絡適配器提供了網絡的物理接口。從網絡上接收到的數據從適配器通過 I/O 和內存總線複製到內存;固然數據也一樣能夠從內存複製到網絡。git

    互聯網絡由大大小小的局域網和廣域網組成,這些局域網和廣域網可能採用了徹底不一樣和不兼容的技術,互聯網絡須要解決的問題是把這些不兼容的網絡鏈接起來。解決辦法就是一層運行在每臺機器和路由器上的協議軟件,這種解決方案隨處可見,好比 java 之因此誇平臺是由於不一樣平臺上的 JVM 作了適配。github

    這個協議軟件控制路由器和主機如何協同工做來實現數據傳輸,在命名上定義一種統一的主機地址格式消除差別;在數據傳輸上經過定義一種把數據位捆綁成不連續的片(包)來消除差別,一個包由包頭和有效載荷組成,包頭包括包的大小以及源主機和目的主機的地址,有效載荷包括從源主機發出的數據位。web

    全球 IP 因特網是最著名最成功的互聯網絡實現,每臺因特網主機都運行實現了 TCP/IP 協議的軟件。一個 IP 地址就是一個 32 位無符號整數,以下是 IP 網絡程序存放的 IP 地址結構,它老是以大端法(網絡字節順序)存放。編程

struct in_addr {
    uint32_t s_addr;
}
複製代碼

    因特網客戶端和服務器相互通訊使用的是 IP 地址,可是這對於人們而言太不友好了,因此因特網定義了一組更加人性化的域名,而且定義了域名集合和 IP 地址集合之間的映射。在 1988 年以前,這個映射都是經過一個叫作HOSTS.TXT的文本文件來手工維護的,此後改成經過 DNS(域名系統)來維護。瀏覽器

    客戶端和服務端經過在鏈接上發送和接收字節流來通訊,這個鏈接是點對點的,而且是全雙工的。一個套接字就是鏈接的一個端點,每一個套接字都有相應的套接字地址,由一個因特網地址和一個 16 位整數端口組成。客戶端套接字地址中的端口是內核自動隨機分配的,稱之爲臨時端口;而服務端一般是很著名的端口,好比 Web 服務器用 80 端口,電子郵件服務器用 25 端口,而且每一個知名端口都有其相應的服務名。下面是套接字地址的結構。安全

// _in 是 internet 的縮寫
struct scoket_in {
    uint16_t sin_family; // 協議族
    uint16_t sin_port; // 端口號
    struct sin_addr; // IP 地址
    unsigned char sin_zero[8]; // sizeof(struct sockaddr)
}

struct socketaddr {
    uint16_t sa_family;
    char sa_data[14];
}
複製代碼

    內核提供了編寫網絡應用所須要的函數,基本看函數名稱就知道它的功能了,下面展現了一小部分函數。服務器

int socket(int domain, int type, int protocol);
int connect(int clientfd, const struct sockadd *addr, socklen_t addrlen);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int listenfd, struct sockaddr *addr, int *addrlen);

int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **result);
int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *service, size_t servlen, int flags);
............
複製代碼

    像用於轉換二進制和字符串表示的getaddrinfo()getnameinfo()函數是可重入的。經過內核提供的這些函數就能夠編寫出本身的 web 服務器。網絡

    可重入函數主要用於多任務系統中,簡單講就是可中斷函數,在這個函數執行的任何階段打斷它,操做系統去調用另外一段代碼,再返回控制時不會出現什麼錯誤。由於它除了使用本身棧上的變量外不依賴於任何環境。注意可重入函數不必定是線程安全的。

    最後是原書做者本身實現的一個 TINY Wed 服務器,用 C 實現的,在 linux 環境下編譯運行便可訪問,下圖是這個服務器運行的效果,訪問連接http://127.0.0.1:8000/cgi-bin/adder?3&7就能夠獲得運算結果。整個程序代碼也不長,我準備好好看看再抄一遍,源碼可訪問連接

image
相關文章
相關標籤/搜索