概述vim
要編寫經過計算機網絡通訊的程序,首先要肯定這些程序相互通訊所用的協議。大多數網絡是按照劃分紅客戶和服務器來組織的。本章及後續章節的焦點是TCP/IP協議族,也可稱爲網際協議族。下圖爲客戶與服務器使用TCP在同一個以太網中通訊:服務器
圖1.1 客戶與服務器使用TCP在同一個以太網進行通訊網絡
同一網絡中的客戶機與服務器無需出於同局域網,上圖1.1所示的是同一個局域網。下圖1.2所示的是處於不一樣局域網的客戶機與服務器,這兩個局域網經過使用路由器鏈接到廣域網。socket
圖1.2 出於不一樣局域網的客戶主機與服務器主機經過廣域網進行鏈接tcp
現在討論Unix是常常使用POSIC一詞,它是一種被多數廠商採納的標準。函數
一個簡單的時間獲取客戶程序ui
// 該頭文件包含了大部分網絡程序都須要的許多系統頭文件 #include "unp.h" // main函數定義,其形式參數就是命令行參數 int main(int argc, char **argv) { int sockfd, n; char recvline[MAXLINE + 1]; struct sockaddr_in servaddr; if (argc != 2) err_quit("usage: a.out <IPaddress>"); // socket函數建立一個網際(AF_INET)字節流(SOCK_STREAM)套接字,該函數返回一個小整數描述符。若是socket函數調用失敗,調用err_sys函數放棄程序運行 if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) err_sys("socket error"); // 把IP地址和端口號填入一個網際套接字地址結構(一個名爲servadrr的sockdrr_in結構變量),使用bzero把整個結構清零 bzero(&servaddr, sizeof(servaddr)); // 置地址族爲AF_INET,端口號爲13,IP地址爲第一個命令行參數的值(argv[1]) // 網際套接字結構中IP地址和端口號必須使用特定格式,爲此調用庫函數htons去轉換二進制端口號,又調用inet_pton去把ASCII命令行參數轉換爲合適的格式 servaddr.sin_family = AF_INET; servaddr.sin_port = htons(13); /* daytime server */ if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) err_quit("inet_pton error for %s", argv[1]); // connect函數應用於TCP套接字時,將由它的第二個參數指向套接字地址結構指定的服務器創建一個TCP鏈接 // 套接字地址結構的長度必須做爲該函數的第三個參數指定 if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0) err_sys("connect error"); // 使用read函數讀取服務器的應答,並用標準I/O函數fputs輸出結果 // 把read放在循環以便讀取完數據,當read返回0或者負數時終止循環 while ( (n = read(sockfd, recvline, MAXLINE)) > 0) { recvline[n] = 0; /* null terminate */ if (fputs(recvline, stdout) == EOF) err_sys("fputs error"); } if (n < 0) err_sys("read error"); // 終止程序運行 exit(0); }
咱們從官網www.unpcook.com下載源代碼unpv13e.tar.gz。解壓後進入文件夾。個人是Ubuntu系統。根據文件夾中Readme的提示輸入相應的命令。this
redhat@redhat-virtual-machine:~/桌面/unpv13e$ ./configure redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd ./lib redhat@redhat-virtual-machine:~/桌面/unpv13e/lib$ make redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd ./libfree redhat@redhat-virtual-machine:~/桌面/unpv13e/libfree$ make // 若是報錯以下,則須要在當前目錄下打開inet_ntop.c文件 // 將第60行的size_t size修改成socklen_t size 而後保存 // 從新輸入make後不報錯便可 gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o inet_ntop.o inet_ntop.c inet_ntop.c: In function ‘inet_ntop’: inet_ntop.c:60:9: error: argument ‘size’ doesn’t match prototype size_t size; ^ In file included from inet_ntop.c:27:0: /usr/include/arpa/inet.h:64:20: error: prototype declaration extern const char *inet_ntop (int __af, const void *__restrict __cp, ^ make: *** [inet_ntop.o] Error 1 redhat@redhat-virtual-machine:~/桌面/unpv13e/libfree$ cd ../libgai redhat@redhat-virtual-machine:~/桌面/unpv13e/libgai$ make // 如下的warning不用理會 /usr/include/arpa/inet.h: In function ‘inet_ntop’: inet_ntop.c:152:23: warning: ‘best.len’ may be used uninitialized in this function [-Wmaybe-uninitialized] if (best.base == -1 || cur.len > best.len) ^ inet_ntop.c:123:28: note: ‘best.len’ was declared here struct { int base, len; } best, cur; ^ gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o inet_pton.o inet_pton.c ar rv ../libunp.a in_cksum.o inet_ntop.o inet_pton.o a - in_cksum.o a - inet_ntop.o a - inet_pton.o ranlib ../libunp.a // 用root權限將以上編譯生成的libunp.a 文件複製到/usr/lib目錄中 redhat@redhat-virtual-machine:~/桌面/unpv13e/libgai$ cd .. redhat@redhat-virtual-machine:~/桌面/unpv13e$ sudo cp libunp.a /usr/lib [sudo] redhat 的密碼: // 打開unp.h文件將其中的#include "../config.h" 改爲 #include "config.h" redhat@redhat-virtual-machine:~/桌面/unpv13e$ vim lib/unp.h // 進入intro目錄編譯客戶端文件並用root權限運行 redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd intro/ redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ make daytimetcpcli redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ sudo ./daytimetcpcli 127.0.0.1 // 錯誤提示沒法鏈接 connect error: Connection refused // 咱們先打開服務器 redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ make daytimetcpsrv redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ sudo ./daytimetcpsrv // 而後再打開另外一個終端,在那裏再運行客戶端便可 redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ sudo ./daytimetcpcli 127.0.0.1 [sudo] redhat 的密碼: Mon Dec 25 21:00:36 2017
上面提到了客戶端獲取時間的程序代碼,下面爲服務器端的程序。spa
#include "unp.h" #include <time.h> int main(int argc, char **argv) { int listenfd, connfd; struct sockaddr_in servaddr; char buff[MAXLINE]; time_t ticks; listenfd = Socket(AF_INET, SOCK_STREAM, 0); // 填寫一個網際套接字地址結構並調用bind函數,把服務器的端口捆綁到所建立的套接字中 bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(13); /* daytime server */ Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); // 調用listen函數把該套接字轉換成一個監聽套接字,使來自客戶端的鏈接能夠在該套接字上由內核接受 // socket、bind、listen這三個函數調用步驟是任何tcp服務器準備監聽描述符的正常步驟 // LISTENQ定義在頭文件中,它指定系統內核容許在這個監聽描述副符上排隊的最大客戶鏈接數 Listen(listenfd, LISTENQ); // 服務器進程在accept調用中被投入睡眠,等待客戶的鏈接 // TCP鏈接的三次握手完畢時accept返回,其返回值是一個被稱爲已鏈接描述符的新描述符 for ( ; ; ) { connfd = Accept(listenfd, (SA *) NULL, NULL); // time函數獲取當前時間,ctime函數把時間轉換成直觀可讀的時間格式 ticks = time(NULL); // snprintf函數在這個字符串末尾添加一個回車符和一個換行符 // write函數把結果字符串寫給客戶 snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); Write(connfd, buff, strlen(buff)); // 終止鏈接 Close(connfd); } }