五十四、linux 編程——TCP 編程模型
阿新 • • 發佈:2019-02-23
獲取 log sin 進制 backlog string idt 主機 ket
54.1 編程模型介紹
54.1.1 TCP 客戶端服務器編程模型
- 客戶端調用序列
- 調用 socket 函數創建套接字
- 調用 connect 連接服務器端
- 調用 I/O 函數(read/write) 與服務器端通訊
- 調用 close 關閉套接字
- 服務器端調用序列
- 調用 socket 函數創建套接字
- 調用 bind 綁定本地地址和端口
- 調用 listen 啟動監聽
- 調用 accept 從已連接隊列中提取客戶連接
- 調用 I/O 函數(read/write)與客戶端通訊
- 調用 close 關閉套接字
54.1.2 套接字與地址綁定
sockaddr 為自定義的結構體,示例如下:
(1)綁定地址
- 函數返回值:成功,則返回 0;出錯,則返回 -1
(2)查找綁定到套接字的地址
- 返回值:成功,則返回 0;出錯,則返回 -1
(3)獲取對方地址
- 返回值:成功,則返回 0;出錯, 則返回 -1
(4)建立連接
服務器端:
- 返回:成功返回0;出錯返回 -1.
- 說明:backlog 指定進行客戶端連接排隊的隊列長度
- 函數功能:獲取客戶端的連接
- 函數參數:
- address:通用地址,可以存放來源於客戶端的地址信息,若不想獲取客戶端的信息,設置為NULL
- 返回值:
客戶端:
- 返回:成功返回0;出錯返回 -1
54.1.3 特殊 bind 地址
- 一臺主機可以有多個網絡接口和多個 IP 地址,如果我們只關心某個地址的連接請求,我們可以指定一個具體的本地 IP 地址,如果要響應所有接口上的連接請求,就要使用一個特殊的地址 INADDR_ANY
- #define INADDR_ANY (uint32_t)0x00000000
54.2 TCP 編程例子
客戶端連接到服務器端後,服務器端返回給客戶端一個系統時間,客戶端將此時間打印出來
54.2.1 服務器端編程
time_tcp_server.c
1 #include <netdb.h> 2 #include <netinet/in.h> 3 #include <sys/socket.h> 4#include <unistd.h> 5 #include <string.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <memory.h> 9 #include <signal.h> 10 #include <time.h> 11 #include <arpa/inet.h> 12 13 14 int sockfd; 15 16 void sig_handler(int signo) 17 { 18 if(signo == SIGINT){ 19 printf("server close\n"); 20 /** 步驟6: 關閉 socket */ 21 close(sockfd); 22 exit(1); 23 } 24 } 25 26 /** 輸出連接上來的客戶端相關信息 */ 27 void out_addr(struct sockaddr_in *clientaddr) 28 { 29 /** 將端口從網絡字節序轉換成主機字節序 */ 30 int port = ntohs(clientaddr->sin_port); 31 char ip[16]; 32 memset(ip, 0, sizeof(ip)); 33 /** 將 ip 地址從網絡字節序轉換成點分十進制 */ 34 inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip)); 35 printf("client: %s(%d) connected\n", ip, port); 36 } 37 38 void do_service(int fd) 39 { 40 /** 獲得系統時間 */ 41 long t = time(0); 42 char *s = ctime(&t); 43 ssize_t size = strlen(s) * sizeof(char); 44 45 /** 將服務器獲得的系統時間寫回到客戶端 */ 46 if(write(fd, s, size) != size){ 47 perror("write error"); 48 } 49 } 50 51 int main(int argc, char *argv[]) 52 { 53 if(argc < 2){ 54 printf("usage: %s #port\n", argv[0]); 55 exit(1); 56 } 57 58 if(signal(SIGINT, sig_handler) == SIG_ERR){ 59 perror("signal sigint error"); 60 exit(1); 61 } 62 63 /** 步驟1: 創建 socket(套接字) 64 * 註: socket 創建在內核中,是一個結構體. 65 * AF_INET: IPV4 66 * SOCK_STREAM: tcp 協議 67 * AF_INET6: IPV6 68 */ 69 sockfd = socket(AF_INET, SOCK_STREAM, 0); 70 71 /** 72 * 步驟2: 調用 bind 函數將 socket 和地址(包括 ip、port)進行綁定 73 */ 74 struct sockaddr_in serveraddr; 75 memset(&serveraddr, 0, sizeof(struct sockaddr_in)); 76 /** 往地址中填入 ip、port、internet 地址族類型 */ 77 serveraddr.sin_family = AF_INET; ///< IPV4 78 serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口 79 serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址 80 if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){ 81 perror("bind error"); 82 exit(1); 83 } 84 85 /** 86 * 步驟3: 調用 listen 函數啟動監聽(指定 port 監聽) 87 * 通知系統去接受來自客戶端的連接請求 88 * (將接受到的客戶端連接請求放置到對應的隊列中) 89 * 第二個參數: 指定隊列的長度 90 */ 91 if(listen(sockfd, 10) < 0){ 92 perror("listen error"); 93 exit(1); 94 } 95 96 /** 97 * 步驟4: 調用 accept 函數從隊列中獲得一個客戶端的請求連接, 並返回新的 98 * socket 描述符 99 * 註意: 若沒有客戶端連接,調用此函數後會足則, 直到獲得一個客戶端的連接 100 */ 101 struct sockaddr_in clientaddr; 102 socklen_t clientaddr_len = sizeof(clientaddr); 103 while(1){ 104 int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len); 105 if(fd < 0){ 106 perror("accept error"); 107 continue; 108 } 109 110 /** 111 * 步驟5: 調用 IO 函數(read/write)和連接的客戶端進行雙向的通信 112 */ 113 out_addr(&clientaddr); 114 do_service(fd); 115 116 /** 步驟6: 關閉 socket */ 117 close(fd); 118 } 119 120 return 0; 121 }
編譯測試:
可以看到,另一個終端返回了系統時間。
54.2.2 客戶端編程
time_tcp_client.c
1 #include <sys/types.h> 2 #include <stdlib.h> 3 #include <stdio.h> 4 #include <memory.h> 5 #include <unistd.h> 6 #include <sys/socket.h> 7 #include <netdb.h> 8 #include <signal.h> 9 #include <string.h> 10 #include <time.h> 11 #include <arpa/inet.h> 12 13 14 int main(int argc, char *argv[]) 15 { 16 if(argc < 3){ 17 printf("usage: %s ip port\n", argv[0]); 18 exit(1); 19 } 20 21 /** 步驟1: 創建 socket */ 22 int sockfd = socket(AF_INET, SOCK_STREAM, 0); 23 if(sockfd < 0){ 24 perror("socket error"); 25 exit(1); 26 } 27 28 /** 往 serveraddr 中填入 ip、port 和地址族類型(ipv4) */ 29 struct sockaddr_in serveraddr; 30 memset(&serveraddr, 0, sizeof(struct sockaddr_in)); 31 serveraddr.sin_family = AF_INET; 32 serveraddr.sin_port = htons(atoi(argv[2])); 33 /** 將 ip 地址轉換成網絡字節序後填入 serveraddr 中 */ 34 inet_pton(AF_INET, argv[1], &serveraddr.sin_addr.s_addr); 35 36 /** 37 * 步驟2: 客戶端調用 connect 函數連接到服務器端 38 */ 39 if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0){ 40 perror("connect error"); 41 exit(1); 42 } 43 44 /** 步驟3: 調用 IO 函數(read/write)和服務器端進行雙向通信 */ 45 char buffer[1024]; 46 memset(buffer, 0, sizeof(buffer)); 47 ssize_t size; 48 if((size = read(sockfd, buffer, sizeof(buffer))) < 0){ 49 perror("read error"); 50 } 51 if(write(STDIN_FILENO, buffer, size) != size){ 52 perror("write error"); 53 } 54 55 /** 步驟4: 關閉 socket */ 56 close(sockfd); 57 58 return 0; 59 }
編譯在兩個終端上,一個打開服務器,一個打開客戶端測試:
五十四、linux 編程——TCP 編程模型