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!
");
 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!
",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
", 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!
");
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("
%s serv_ip serv_port
",s);
 7     printf("
	 serv_ip:serv ip address");
 8     printf("
	 serv_port: sever port(>5000)

");
 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 ...
");
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!
");
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!
");
 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!
",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
", 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!
");
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("
%s serv_ip serv_port
",s);
 7     printf("
	 serv_ip:serv ip address");
 8     printf("
	 serv_port: sever port(>5000)

");
 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 ...
");
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!
");
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

测试结果:

原文地址:https://www.cnblogs.com/y4247464/p/12182801.html