五十5、linux 編程——TCP 鏈接和關閉過程及服務器的併發處理

55.1 TCP 鏈接和關閉過程

55.1.1 介紹

  

  創建鏈接的過程就是三次握手的過程:客戶端發送 SYN 報文給服務器,服務器回覆 SYN+ACK 報文,客戶機再發送 ACK 報文。編程

  關閉鏈接的過程:客戶機先發送 FIN 報文,服務器回覆 ACK 報文,服務器再發送 FIN 報文,客戶機再發送響應報文 ACK。服務器

55.1.2  自定義協議編程例子 

  msg.h網絡

 1 #ifndef __MSG_H__  2 #define __MSG_H__
 3 
 4 #include <sys/types.h>
 5 
 6 typedef struct {  7     /** 協議頭部: 不傳輸任何數據,只包含發送端的一些信息 */
 8     char head[10];      ///< 協議頭部  9  char checknum; ///< 校驗碼 10 
11     /**協議體部 */
12     char buff[512];     ///< 數據 13 }Msg;
14 
15 /** 發送一個基於自定義協議的 message,發送的數據存放在 buff 中 */
16 extern int write_msg(int sockfd, char *buff, ssize_t len); 17 
18 /** 讀取一個基於自定義協議的 message, 讀取的數據存放在 buff 中 */
19 extern int read_msg(int sockfd, char *buff, ssize_t len); 20 
21 #endif

  msg.c多線程

 1 #include "msg.h"
 2 #include <unistd.h>
 3 #include <string.h>
 4 #include <memory.h>
 5 #include <sys/types.h>
 6 
 7 
 8 /** 計算校驗碼 */
 9 static unsigned char msg_check(Msg *message) 10 { 11     unsigned char s = 0; 12     int i; 13     for(i = 0; i < sizeof(message->head); i++){ 14         s += message->head[i]; 15  } 16 
17     for(i = 0; i < sizeof(message->buff); i++){ 18         s += message->buff[i]; 19  } 20 
21     return s; 22 } 23 
24 
25 /** 發送一個基於自定義協議的 message,發送的數據存放在 buff 中 */
26 int write_msg(int sockfd, char *buff, ssize_t len) 27 { 28  Msg message; 29     memset(&message, 0, sizeof(message)); 30     strcpy(message.head, "hello"); 31  memcpy(message.buff, buff, len); 32     message.checknum = msg_check(&message); 33 
34     if(write(sockfd, &message, sizeof(message)) != sizeof(message)){ 35         return -1; 36  } 37 
38     return 0; 39 } 40 
41 /** 讀取一個基於自定義協議的 message, 讀取的數據存放在 buff 中 */
42 int read_msg(int sockfd, char *buff, ssize_t len) 43 { 44  Msg message; 45     memset(&message, 0, sizeof(message)); 46 
47  ssize_t size; 48     if((size = read(sockfd, &message, sizeof(message))) < 0){ 49         return -1; 50  } 51     else if(size == 0){ 52         return 0; 53  } 54 
55     /** 進行校驗碼驗證,判斷接收到的 message 是否完整 */
56     unsigned char s = msg_check(&message); 57     if((s == (unsigned char)message.checknum) && (!strcmp("hello", message.head))){ 58  memcpy(buff, message.buff, len); 59         return sizeof(message); 60  } 61     return -1; 62 
63 }

  編譯成 .o 文件:gcc -o obj/msg.o -Iinclude -c src/msg.c併發

55.2 服務器的併發過程

55.2.1 介紹

  一個服務器處理多個客戶端的請求,就稱爲服務器的併發。socket

  • 服務器端併發性處理
    • 多進程模型
    • 多線程模型
    • I/O多路轉換(select)

  

55.2.2  基於自定義協議的多進程模型編程

(1)服務器代碼

  echo_tcp_server.ctcp

 1 #include <netdb.h>
 2 #include <netinet/in.h>
 3 #include <sys/socket.h>
 4 #include <sys/wait.h>
 5 #include <unistd.h>
 6 #include <string.h>
 7 #include <stdio.h>
 8 #include <stdlib.h>
 9 #include <memory.h>
 10 #include <signal.h>
 11 #include <time.h>
 12 #include <arpa/inet.h>
 13 #include <errno.h>
 14 #include "msg.h"
 15 
 16 
 17 int sockfd;  18 
 19 void sig_handler(int signo)  20 {  21     if(signo == SIGINT){  22         printf("server close\n");  23         /** 步驟6: 關閉 socket */
 24  close(sockfd);  25         exit(1);  26  }  27 
 28     if(signo == SIGINT){  29         printf("child process deaded...\n");  30         wait(0);  31  }  32 }  33 
 34 /** 輸出鏈接上來的客戶端相關信息 */
 35 void out_addr(struct sockaddr_in *clientaddr)  36 {  37     /** 將端口從網絡字節序轉換成主機字節序 */
 38     int port = ntohs(clientaddr->sin_port);  39     char ip[16];  40     memset(ip, 0, sizeof(ip));  41     /** 將 ip 地址從網絡字節序轉換成點分十進制 */
 42     inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));  43     printf("client: %s(%d) connected\n", ip, port);  44 }  45 
 46 void do_service(int fd)  47 {  48     /** 和客戶端進行讀寫操做(雙向通訊) */
 49     char buff[512];  50     while(1){  51         memset(buff, 0, sizeof(buff));  52         printf("start read and write....\n");  53  ssize_t size;  54         if((size = read_msg(fd, buff, sizeof(buff))) < 0){  55             perror("protocal error");  56             break;  57  }  58         else if(size == 0){  59             break;  60  }  61         else {  62             printf("%s\n", buff);  63             if(write_msg(fd, buff, sizeof(buff)) < 0){  64                 if(errno == EPIPE){  65                     break;  66  }  67                 perror("protocal error");  68  }  69  }  70  }  71 }  72 
 73 int main(int argc, char *argv[])  74 {  75     if(argc < 2){  76         printf("usage: %s #port\n", argv[0]);  77         exit(1);  78  }  79 
 80     if(signal(SIGINT, sig_handler) == SIG_ERR){  81         perror("signal sigint error");  82         exit(1);  83  }  84 
 85     if(signal(SIGCHLD, sig_handler) == SIG_ERR){  86         perror("signal sigchld error");  87         exit(1);  88  }  89 
 90     /** 步驟1: 建立 socket(套接字)  91  * 注: socket 建立在內核中,是一個結構體.  92  * AF_INET: IPV4  93  * SOCK_STREAM: tcp 協議  94  * AF_INET6: IPV6  95      */
 96     sockfd = socket(AF_INET, SOCK_STREAM, 0);  97     if(sockfd < 0){  98         perror("socket error");  99         exit(1); 100  } 101 
102     /** 103  * 步驟2: 調用 bind 函數將 socket 和地址(包括 ip、port)進行綁定 104      */
105     struct sockaddr_in serveraddr; 106     memset(&serveraddr, 0, sizeof(struct sockaddr_in)); 107     /** 往地址中填入 ip、port、internet 地址族類型 */
108     serveraddr.sin_family = AF_INET;    ///< IPV4 109  serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口 110  serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址 111  if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
112         perror("bind error"); 113         exit(1); 114  } 115 
116     /** 117  * 步驟3: 調用 listen 函數啓動監聽(指定 port 監聽) 118  * 通知系統去接受來自客戶端的鏈接請求 119  * (將接受到的客戶端鏈接請求放置到對應的隊列中) 120  * 第二個參數: 指定隊列的長度 121      */
122     if(listen(sockfd, 10) < 0){ 123         perror("listen error"); 124         exit(1); 125  } 126 
127     /** 128  * 步驟4: 調用 accept 函數從隊列中得到一個客戶端的請求鏈接, 並返回新的 129  * socket 描述符 130  * 注意: 若沒有客戶端鏈接,調用此函數後會足則, 直到得到一個客戶端的鏈接 131      */
132     struct sockaddr_in clientaddr; 133     socklen_t clientaddr_len = sizeof(clientaddr); 134     while(1){ 135         int fd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len); 136         if(fd < 0){ 137             perror("accept error"); 138             continue; 139  } 140 
141         /** 142  * 步驟5: 啓動子進程去調用 IO 函數(read/write)和鏈接的客戶端進行雙向的通訊 143          */
144         pid_t pid = fork(); 145         if(pid < 0){ 146             continue; 147  } 148         else if(pid == 0){ 149             /** 子進程 */
150             out_addr(&clientaddr); 151  do_service(fd); 152             /** 步驟6: 關閉 socket */
153  close(fd); 154             break; 155  } 156         else{ 157             /** 父進程 */
158             /** 步驟6: 關閉 socket */
159  close(fd); 160  } 161  } 162 
163     return 0; 164 }

  gcc -o bin/echo_tcp_server -Iinclude obj/msg.o src/echo_tcp_server.c 函數

(2)客戶端代碼

  echo_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 #include "msg.h"
13 
14 
15 int main(int argc, char *argv[]) 16 { 17     if(argc < 3){ 18         printf("usage: %s ip port\n", argv[0]); 19         exit(1); 20  } 21 
22     /** 步驟1: 建立 socket */
23     int sockfd = socket(AF_INET, SOCK_STREAM, 0); 24     if(sockfd < 0){ 25         perror("socket error"); 26         exit(1); 27  } 28 
29     /** 往 serveraddr 中填入 ip、port 和地址族類型(ipv4) */
30     struct sockaddr_in serveraddr; 31     memset(&serveraddr, 0, sizeof(struct sockaddr_in)); 32     serveraddr.sin_family = AF_INET; 33     serveraddr.sin_port = htons(atoi(argv[2])); 34     /** 將 ip 地址轉換成網絡字節序後填入 serveraddr 中 */
35     inet_pton(AF_INET, argv[1], &serveraddr.sin_addr.s_addr); 36 
37     /** 38  * 步驟2: 客戶端調用 connect 函數鏈接到服務器端 39      */
40     if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0){ 41         perror("connect error"); 42         exit(1); 43  } 44 
45     /** 步驟3: 調用 IO 函數(read/write)和服務器端進行雙向通訊 */
46     char buff[512]; 47  ssize_t size; 48     char *prompt = "==>"; 49     while(1){ 50         memset(buff, 0, sizeof(buff)); 51         write(STDOUT_FILENO, prompt, 3); 52         size = read(STDIN_FILENO, buff, sizeof(buff)); 53         if(size < 0) continue; 54         buff[size - 1] = '\0'; 55 
56         if(write_msg(sockfd, buff, sizeof(buff)) < 0){ 57             perror("write msg error"); 58             continue; 59  } 60         else { 61             if(read_msg(sockfd, buff, sizeof(buff)) < 0){ 62                 perror("read msg error"); 63                 continue; 64  } 65             else { 66                 printf("%s\n", buff); 67  } 68  } 69  } 70 
71     /** 步驟4: 關閉 socket */
72  close(sockfd); 73 
74     return 0; 75 }

  gcc -o bin/echo_tcp_client -Iinclude obj/msg.o src/echo_tcp_client.cspa

(3)測試

  開啓兩個終端進行測試,一個運行服務器,一個運行客戶端:

  

  

  

55.2.3  基於自定義協議的多線程模型編程

  

  echo_tcp_server_th.c

 1 #include <netdb.h>
 2 #include <netinet/in.h>
 3 #include <sys/socket.h>
 4 #include <sys/wait.h>
 5 #include <unistd.h>
 6 #include <string.h>
 7 #include <stdio.h>
 8 #include <stdlib.h>
 9 #include <memory.h>
 10 #include <signal.h>
 11 #include <time.h>
 12 #include <arpa/inet.h>
 13 #include <errno.h>
 14 #include "msg.h"
 15 #include <pthread.h>
 16 
 17 
 18 int sockfd;  19 
 20 void sig_handler(int signo)  21 {  22     if(signo == SIGINT){  23         printf("server close\n");  24         /** 步驟6: 關閉 socket */
 25  close(sockfd);  26         exit(1);  27  }  28 }  29 
 30 void do_service(int fd)  31 {  32     /** 和客戶端進行讀寫操做(雙向通訊) */
 33     char buff[512];  34     while(1){  35         memset(buff, 0, sizeof(buff));  36  ssize_t size;  37         if((size = read_msg(fd, buff, sizeof(buff))) < 0){  38             perror("protocal error");  39             break;  40  }  41         else if(size == 0){  42             break;  43  }  44         else {  45             printf("%s\n", buff);  46             if(write_msg(fd, buff, sizeof(buff)) < 0){  47                 if(errno == EPIPE){  48                     break;  49  }  50                 perror("protocal error");  51  }  52  }  53  }  54 }  55 
 56 
 57 void out_fd(int fd)  58 {  59     struct sockaddr_in addr;  60     socklen_t len = sizeof(addr);  61     /** 從 fd 中得到鏈接的客戶端相關信息並放置到 sockaddr_in 當中 */
 62     if(getpeername(fd, (struct sockaddr *)&addr, &len) < 0){  63         perror("getpeername error");  64         return;  65  }  66 
 67     char ip[16];  68     memset(ip, 0, sizeof(ip));  69     int port = ntohs(addr.sin_port);  70     inet_ntop(AF_INET, &addr.sin_addr.s_addr, ip, sizeof(ip));  71     printf("%16s(%5d) closed!\n", ip, port);  72 }  73 
 74 void *th_fn(void *arg)  75 {  76     int fd = (int)arg;  77 
 78  do_service(fd);  79  out_fd(fd);  80  close(fd);  81     return (void *)0;  82 }  83 
 84 int main(int argc, char *argv[])  85 {  86     if(argc < 2){  87         printf("usage: %s #port\n", argv[0]);  88         exit(1);  89  }  90 
 91     if(signal(SIGINT, sig_handler) == SIG_ERR){  92         perror("signal sigint error");  93         exit(1);  94  }  95 
 96 
 97     /** 步驟1: 建立 socket(套接字)  98  * 注: socket 建立在內核中,是一個結構體.  99  * AF_INET: IPV4 100  * SOCK_STREAM: tcp 協議 101  * AF_INET6: IPV6 102      */
103     sockfd = socket(AF_INET, SOCK_STREAM, 0); 104     if(sockfd < 0){ 105         perror("socket error"); 106         exit(1); 107  } 108 
109     /** 110  * 步驟2: 調用 bind 函數將 socket 和地址(包括 ip、port)進行綁定 111      */
112     struct sockaddr_in serveraddr; 113     memset(&serveraddr, 0, sizeof(struct sockaddr_in)); 114     /** 往地址中填入 ip、port、internet 地址族類型 */
115     serveraddr.sin_family = AF_INET;    ///< IPV4 116  serveraddr.sin_port = htons(atoi(argv[1])); ///< 填入端口 117  serveraddr.sin_addr.s_addr = INADDR_ANY; ///< 填入 IP 地址 118  if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in))){
119         perror("bind error"); 120         exit(1); 121  } 122 
123     /** 124  * 步驟3: 調用 listen 函數啓動監聽(指定 port 監聽) 125  * 通知系統去接受來自客戶端的鏈接請求 126  * (將接受到的客戶端鏈接請求放置到對應的隊列中) 127  * 第二個參數: 指定隊列的長度 128      */
129     if(listen(sockfd, 10) < 0){ 130         perror("listen error"); 131         exit(1); 132  } 133 
134     /** 135  * 步驟4: 調用 accept 函數從隊列中得到一個客戶端的請求鏈接, 並返回新的 136  * socket 描述符 137  * 注意: 若沒有客戶端鏈接,調用此函數後會足則, 直到得到一個客戶端的鏈接 138      */
139 
140     /** 設置線程的分離屬性 */
141  pthread_attr_t attr; 142     pthread_attr_init(&attr); 143     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 144 
145     while(1){ 146         /** 主控線程負責調用 accept 去得到客戶端的鏈接 */
147         int fd = accept(sockfd, NULL, NULL); 148         if(fd < 0){ 149             perror("accept error"); 150             continue; 151  } 152 
153         /** 154  * 步驟5: 啓動子線程去調用 IO 函數(read/write)和鏈接的客戶端進行雙向的通訊 155          */
156  pthread_t th; 157         int err; 158         /** 以分離狀態啓動子線程 */
159         if((err = pthread_create(&th, &attr, th_fn, (void *)fd)) != 0){ 160             perror("pthread create error"); 161  } 162 
163         pthread_attr_destroy(&attr); 164 
165  } 166 
167     return 0; 168 }

  客戶端程序用上面的客戶端程序便可。

相關文章
相關標籤/搜索