TCP——並發服務


在上一節的程序中,服務端在進行到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 }
pthread_tcp.c
 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 }
client.c
1 all:
2     gcc  -o client client.c -lpthread
3     gcc  -o pthread_tcp pthread_tcp.c -lpthread
4 
5 clean:
6     rm  *.elf
Makefile

測試結果:

 

 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 }
process_tcp.c
 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 }
client.c
1 all:
2     gcc  -o client client.c 
3     gcc  -o process_tcp process_tcp.c 
4 
5 clean:
6     rm  *.elf
Makefile

測試結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM