在上一節的程序中,服務端在進行到accept()環節會等待客戶端的請求到來,若客戶端一直不發生請求,則服務端會一直阻塞。
因此,引入並發服務器的概念。
一、並發服務器
同一時刻可以響應多個客戶端的請求,多任務完成服務每個客戶端的請求,每個客戶端不需要排隊等待,可以立即進行服務。
並發服務器設計技術一般有:多進程服務器、多線程服務器、I/O復用服務器(循環服務器)等。
(圖片來源)
1、多線程服務器
父進程監聽客戶端的連接請求,創建子線程,進行線程分離
子線程與客戶端進行數據交互
多線程服務器是對多進程服務器的改進,由於多進程服務器在創建進程時要消耗較大的系統資源,所以用線程來取代進程,這樣服務處理程序可以較快的創建。
優點: 服務效率高,客戶端無需等待。
缺點: 復雜、較多地占用了系統的資源,一旦客戶端過多會嚴重浪費系統資源。
TCP多線程實例:

1 #include "net.h" 2 void cli_data_handle(void *arg); 3 4 int main() 5 { 6 int fd = -1; 7 struct sockaddr_in sin; //Internet環境下套接字的地址形式,對其進行操作以建立信息 8 9 /*1、創建套接字描述符fd */ 10 if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) 11 { 12 perror("socket"); 13 exit(1); 14 } 15 16 /*2、將套接字綁定到一個本地地址與端口上*/ 17 18 /*2.1 填充struct sockaddr_in結構體變量*/ 19 bzero(&sin, sizeof(sin)); //初始值置零 20 //sin_port和sin_addr都必須是NBD,且可視化的數字一般都是HBD,如端口號23 21 sin.sin_family = AF_INET; //協議,ipv4 22 sin.sin_port = htons(SERV_PORT); //將端口號轉化為NBD 23 24 /*優化1:讓服務器能綁定在任意IP上*/ 25 sin.sin_addr.s_addr = htonl(INADDR_ANY); 26 27 /*2.2 綁定 */ 28 if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) 29 { 30 perror("bind"); 31 exit(1); 32 } 33 34 /*3. 把套接字設為監聽模式,准備接收客戶請求*/ 35 if(listen(fd, BACKLOG) < 0) 36 { 37 perror("listen"); 38 exit(1); 39 } 40 printf("Severing start...OK!\n"); 41 42 /*4.等待客戶請求到來,當請求到來后,接收請求,返回一個基於此次的新的套接字 */ 43 int newfd = -1; 44 45 /*優化3:用多進程/多線程處理已經建立好連接的客戶端數據*/ 46 pthread_t tid; 47 48 struct sockaddr_in cin; 49 socklen_t addrlen = sizeof(cin); 50 while(1) 51 { 52 /*優化2:通過程序獲取剛建立連接的socket的客戶端的IP地址與端口號 */ 53 //獲取客戶端信息 54 if((newfd = accept(fd, (struct sockaddr *)&cin, &addrlen))<0) 55 { 56 perror("accept"); 57 exit(1); 58 } 59 //讀出客戶端信息,並將HBD轉為NBD 60 char ipv4_addr[16]; 61 if(!inet_ntop(AF_INET, (void *)&cin.sin_addr,ipv4_addr,sizeof(cin))) 62 { 63 perror("inet_ntop"); 64 exit(1); 65 } 66 //打印客戶端的IP和端口號 67 printf("Client(%s,%d) is connected!\n",ipv4_addr,ntohs(cin.sin_port)); 68 //打印完之后創建一個線程 69 pthread_create(&tid, NULL, (void *)cli_data_handle, (void *)&newfd); 70 } 71 72 close(fd); 73 return 0; 74 75 } 76 77 void cli_data_handle(void *arg) 78 { 79 int newfd = *(int *)arg; 80 81 printf("thread:newfd = %d\n", newfd); 82 83 /*5. 讀寫數據*/ 84 int ret = -1; 85 char buf[BUFSIZ]; 86 87 while(1) 88 { 89 bzero(buf, BUFSIZ); 90 do{ 91 ret = read(newfd, buf, BUFSIZ-1); 92 }while(ret < 0 && EINTR == errno); 93 if(ret < 0) 94 { 95 perror("read"); 96 exit(1); 97 } 98 99 if(!ret) //對方已經關閉 100 { 101 break; 102 } 103 printf("Receive data: %s",buf); 104 105 if(!strncasecmp(buf,QUIT_STR, strlen(QUIT_STR))) 106 { 107 printf("Client is exiting!\n"); 108 break; 109 } 110 } 111 close(newfd); 112 113 }

1 /*運行方式: ./client serv_ip serv_port */ 2 #include "net.h" 3 4 void usage(char *s) 5 { 6 printf("\n%s serv_ip serv_port\n",s); 7 printf("\n\t serv_ip:serv ip address"); 8 printf("\n\t serv_port: sever port(>5000)\n\n"); 9 } 10 11 int main(int argc, char **argv) 12 { 13 int fd = -1; 14 15 int port = -1; 16 struct sockaddr_in sin; 17 18 if(argc != 3)//參數錯誤檢測 19 { 20 usage(argv[0]); 21 exit(1); 22 } 23 /*1.創建sock fd */ 24 if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) 25 { 26 perror("socket"); 27 exit(1); 28 } 29 30 port = atoi(argv[2]); 31 if(port < 5000) 32 { 33 usage(argv[0]); 34 exit(1); 35 } 36 /*2.連接服務器 */ 37 /*2.1 填充struct sockaddr_in結構體變量*/ 38 bzero(&sin, sizeof(sin)); //初始值置零 39 sin.sin_family = AF_INET; // 40 sin.sin_port = htons(port); //轉化為NBD 41 #if 0 42 sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); 43 #else 44 if(inet_pton(AF_INET, argv[1],(void *)&sin.sin_addr.s_addr) != 1) 45 { 46 perror("inet_pton"); 47 exit(1); 48 } 49 #endif 50 51 if(connect(fd,(struct sockaddr *)&sin, sizeof(sin)) < 0) 52 { 53 perror("connect"); 54 exit(1); 55 } 56 57 printf("Client starting ...\n"); 58 59 /*3.讀寫數據*/ 60 char buf[BUFSIZ]; 61 int ret = -1; 62 while(1) 63 { 64 bzero(buf,BUFSIZ); 65 if(fgets(buf, BUFSIZ-1, stdin) == NULL) 66 { 67 continue; 68 } 69 do{ 70 ret = write(fd, buf, strlen(buf)); 71 }while(ret < 0 && EINTR == errno); 72 if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))) 73 { 74 printf("Clinet is exiting!\n"); 75 break; 76 } 77 78 } 79 80 /*4.關閉套接字 */ 81 close(fd); 82 83 return 0; 84 }

1 all: 2 gcc -o client client.c -lpthread 3 gcc -o pthread_tcp pthread_tcp.c -lpthread 4 5 clean: 6 rm *.elf
測試結果:
2、多進程服務器
在多進程處理模型中
父進程主要用來監聽客戶端的連接請求,當有客戶端請求連接時,accept()返回用於通信的文件描述符fd,父進程fock創建子進程,並負責回收子進程。
子進程獲得父進程數據空間、堆和棧的復制品,拿到文件描述符fd后,和客戶端進行數據交互。
通信結束后,子進程exit()結束,主進程進程資源的回收。
TCP多進程實例:

1 #include "net.h" 2 void cli_data_handle(void *arg); 3 void sig_child_handle(int signo) 4 { 5 if(SIGCHLD == signo) 6 { 7 waitpid(-1, NULL, WNOHANG); 8 } 9 } 10 11 int main() 12 { 13 int fd = -1; 14 struct sockaddr_in sin; //Internet環境下套接字的地址形式,對其進行操作以建立信息 15 /*當客戶端退出時,會通過信號機制回收子進程,防止變成僵屍進程*/ 16 signal(SIGCHLD, sig_child_handle); //回收子進程 17 18 /*1、創建套接字描述符fd */ 19 if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) 20 { 21 perror("socket"); 22 exit(1); 23 } 24 25 /*優化4:允許綁定地址快速重用*/ 26 int b_reuse = 1; 27 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); 28 29 /*2、將套接字綁定到一個本地地址與端口上*/ 30 31 /*2.1 填充struct sockaddr_in結構體變量*/ 32 bzero(&sin, sizeof(sin)); //初始值置零 33 //sin_port和sin_addr都必須是NBD,且可視化的數字一般都是HBD,如端口號23 34 sin.sin_family = AF_INET; //協議,ipv4 35 sin.sin_port = htons(SERV_PORT); //將端口號轉化為NBD 36 37 /*優化1:讓服務器能綁定在任意IP上*/ 38 sin.sin_addr.s_addr = htonl(INADDR_ANY); 39 40 /*2.2 綁定 */ 41 if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) 42 { 43 perror("bind"); 44 exit(1); 45 } 46 47 /*3. 把套接字設為監聽模式,准備接收客戶請求*/ 48 if(listen(fd, BACKLOG) < 0) 49 { 50 perror("listen"); 51 exit(1); 52 } 53 printf("Severing start...OK!\n"); 54 55 /*4.等待客戶請求到來,當請求到來后,接收請求,返回一個基於此次的新的套接字 */ 56 int newfd = -1; 57 58 struct sockaddr_in cin; 59 socklen_t addrlen = sizeof(cin); 60 while(1) 61 { 62 pid_t pid = -1; 63 //獲取客戶端信息 64 if((newfd = accept(fd, (struct sockaddr *)&cin, &addrlen)) < 0) 65 { 66 perror("accept"); 67 break; 68 } 69 /*創建一個子進程用於處理已經建立連接的客戶的交互數據 */ 70 if((pid = fork()) < 0) 71 { 72 perror("fork"); 73 break; 74 } 75 76 if(0 == pid) //子進程 77 { 78 close(fd); //關閉不需要的文件描述符,節省資源 79 //讀出客戶端信息,並將HBD轉為NBD 80 char ipv4_addr[16]; 81 if(!inet_ntop(AF_INET, (void *)&cin.sin_addr,ipv4_addr,sizeof(cin))) 82 { 83 perror("inet_ntop"); 84 exit(1); 85 } 86 87 //打印客戶端的IP和端口號 88 printf("Client(%s,%d) is connected!\n",ipv4_addr,ntohs(cin.sin_port)); 89 cli_data_handle(&newfd); 90 return 0; //客戶端數據處理完畢,return跳出循環 91 92 }else //pid > 0, 父進程 93 { 94 close(newfd); //關閉不需要的文件描述符 95 } 96 } 97 close(fd); 98 return 0; 99 100 } 101 102 void cli_data_handle(void *arg) 103 { 104 int newfd = *(int *)arg; 105 106 printf("Child handle process:newfd = %d\n", newfd); 107 108 /*5. 讀寫數據*/ 109 int ret = -1; 110 char buf[BUFSIZ]; 111 112 while(1) 113 { 114 bzero(buf, BUFSIZ); 115 do{ 116 ret = read(newfd, buf, BUFSIZ-1); 117 }while(ret < 0 && EINTR == errno); 118 if(ret < 0) 119 { 120 perror("read"); 121 exit(1); 122 } 123 124 if(!ret) //對方已經關閉 125 { 126 break; 127 } 128 printf("Receive data: %s",buf); 129 130 if(!strncasecmp(buf,QUIT_STR, strlen(QUIT_STR))) 131 { 132 printf("Client is exiting!\n"); 133 break; 134 } 135 } 136 close(newfd); 137 138 }

1 /*運行方式: ./client serv_ip serv_port */ 2 #include "net.h" 3 4 void usage(char *s) 5 { 6 printf("\n%s serv_ip serv_port\n",s); 7 printf("\n\t serv_ip:serv ip address"); 8 printf("\n\t serv_port: sever port(>5000)\n\n"); 9 } 10 11 int main(int argc, char **argv) 12 { 13 int fd = -1; 14 15 int port = -1; 16 struct sockaddr_in sin; 17 18 if(argc != 3)//參數錯誤檢測 19 { 20 usage(argv[0]); 21 exit(1); 22 } 23 /*1.創建sock fd */ 24 if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0) 25 { 26 perror("socket"); 27 exit(1); 28 } 29 30 port = atoi(argv[2]); 31 if(port < 5000) 32 { 33 usage(argv[0]); 34 exit(1); 35 } 36 /*2.連接服務器 */ 37 /*2.1 填充struct sockaddr_in結構體變量*/ 38 bzero(&sin, sizeof(sin)); //初始值置零 39 sin.sin_family = AF_INET; // 40 sin.sin_port = htons(port); //轉化為NBD 41 #if 0 42 sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); 43 #else 44 if(inet_pton(AF_INET, argv[1],(void *)&sin.sin_addr.s_addr) != 1) 45 { 46 perror("inet_pton"); 47 exit(1); 48 } 49 #endif 50 51 if(connect(fd,(struct sockaddr *)&sin, sizeof(sin)) < 0) 52 { 53 perror("connect"); 54 exit(1); 55 } 56 57 printf("Client starting ...\n"); 58 59 /*3.讀寫數據*/ 60 char buf[BUFSIZ]; 61 int ret = -1; 62 while(1) 63 { 64 bzero(buf,BUFSIZ); 65 if(fgets(buf, BUFSIZ-1, stdin) == NULL) 66 { 67 continue; 68 } 69 do{ 70 ret = write(fd, buf, strlen(buf)); 71 }while(ret < 0 && EINTR == errno); 72 if(!strncasecmp(buf, QUIT_STR, strlen(QUIT_STR))) 73 { 74 printf("Clinet is exiting!\n"); 75 break; 76 } 77 78 } 79 80 /*4.關閉套接字 */ 81 close(fd); 82 83 return 0; 84 }

1 all: 2 gcc -o client client.c 3 gcc -o process_tcp process_tcp.c 4 5 clean: 6 rm *.elf
測試結果: