在上一节的程序中,服务端在进行到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
测试结果: