【Linux C编程】第十八章 高并发服务器(四)
4. 实例三
基于网络C/S非阻塞模型的epoll ET触发模式
实现过程中注意两点:
- 当服务端接收到客户端新的连接(cfd),需要设置客户端连接问价描述符(cfd)为非阻塞模式,因为下面需要循环读取服务端缓冲区中的数据,而如果不设置 cfd 为非阻塞模式,则当读完缓冲区的数据 recv 再次读取会阻塞住,则整个程序会被阻塞;
- 通过errno == EAGAIN来判断缓冲区中的数据读取完成。
nonblock_et_epoll.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <string.h>
6 #include <sys/socket.h>
7 #include <arpa/inet.h>
8 #include <ctype.h>
9 #include <sys/epoll.h>
10 #include <fcntl.h>
11 #include <errno.h>
12
13 int main(int argc, const char* argv[])
14 {
15 if(argc < 2)
16 {
17 printf("eg: ./a.out port\n");
18 exit(1);
19 }
20 struct sockaddr_in serv_addr;
21 socklen_t serv_len = sizeof(serv_addr);
22 int port = atoi(argv[1]);
23
24 // 创建套接字
25 int lfd = socket(AF_INET, SOCK_STREAM, 0);
26 // 初始化服务器 sockaddr_in
27 memset(&serv_addr, 0, serv_len);
28 serv_addr.sin_family = AF_INET; // 地址族
29 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP
30 serv_addr.sin_port = htons(port); // 设置端口
31 // 绑定IP和端口
32 bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
33
34 // 设置同时监听的最大个数
35 listen(lfd, 36);
36 printf("Start accept ......\n");
37
38 struct sockaddr_in client_addr;
39 socklen_t cli_len = sizeof(client_addr);
40
41 // 创建epoll树根节点
42 int epfd = epoll_create(2000);
43 // 初始化epoll树
44 struct epoll_event ev;
45
46 // 设置边沿触发
47 ev.events = EPOLLIN;
48 ev.data.fd = lfd;
49 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
50
51 struct epoll_event all[2000];
52 while(1)
53 {
54 // 使用epoll通知内核fd 文件IO检测
55 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1);
56 printf("================== epoll_wait =============\n");
57
58 // 遍历all数组中的前ret个元素
59 for(int i=0; i<ret; ++i)
60 {
61 int fd = all[i].data.fd;
62 // 判断是否有新连接
63 if(fd == lfd)
64 {
65 // 接受连接请求
66 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len);
67 if(cfd == -1)
68 {
69 perror("accept error");
70 exit(1);
71 }
72 // 设置文件cfd为非阻塞模式
73 int flag = fcntl(cfd, F_GETFL);
74 flag |= O_NONBLOCK;
75 fcntl(cfd, F_SETFL, flag);
76
77 // 将新得到的cfd挂到树上
78 struct epoll_event temp;
79 // 设置边沿触发
80 temp.events = EPOLLIN | EPOLLET;
81 temp.data.fd = cfd;
82 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp);
83
84 // 打印客户端信息
85 char ip[64] = {0};
86 printf("New Client IP: %s, Port: %d\n",
87 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)),
88 ntohs(client_addr.sin_port));
89
90 }
91 else
92 {
93 // 处理已经连接的客户端发送过来的数据
94 if(!all[i].events & EPOLLIN)
95 {
96 continue;
97 }
98
99 // 读数据
100 char buf[5] = {0};
101 int len;
102 // 循环读数据
103 while( (len = recv(fd, buf, sizeof(buf), 0)) > 0 )
104 {
105 // 数据打印到终端
106 write(STDOUT_FILENO, buf, len);
107 // 发送给客户端
108 send(fd, buf, len, 0);
109 }
110 if(len == 0)
111 {
112 printf("客户端断开了连接\n");
113 ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
114 if(ret == -1)
115 {
116 perror("epoll_ctl - del error");
117 exit(1);
118 }
119 close(fd);
120 }
121 else if(len == -1)
122 {
123 if(errno == EAGAIN)
124 {
125 printf("缓冲区数据已经读完\n");
126 }
127 else
128 {
129 printf("recv error----\n");
130 exit(1);
131 }
132 }
133 }
134 }
135 }
136
137 close(lfd);
138 return 0;
139 }
tcp_client.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8 #include <fcntl.h>
9
10 // tcp client
11 int main(int argc, const char* argv[])
12 {
13 if(argc < 2)
14 {
15 printf("eg: ./a.out port\n");
16 exit(1);
17 }
18 // 创建套接字
19 int fd = socket(AF_INET, SOCK_STREAM, 0);
20 if(fd == -1)
21 {
22 perror("socket error");
23 exit(1);
24 }
25 int port = atoi(argv[1]);
26 // 连接服务器
27 struct sockaddr_in serv_addr;
28 memset(&serv_addr, 0, sizeof(serv_addr));
29 serv_addr.sin_family = AF_INET;
30 serv_addr.sin_port = htons(port);
31 inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
32 int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
33 if(ret == -1)
34 {
35 perror("connect error");
36 exit(1);
37 }
38
39 // 通信
40 while(1)
41 {
42 // 写数据
43 // 接收键盘输入
44 char buf[512];
45 fgets(buf, sizeof(buf), stdin);
46 // 发送给服务器
47 write(fd, buf, strlen(buf)+1);
48
49 // 接收服务器端的数据
50 int len = read(fd, buf, sizeof(buf));
51 printf("read buf = %s, len = %d\n", buf, len);
52 }
53 return 0;
54 }
执行结果:
server端:
[root@centos epoll]# ./nonblock_et_epoll 8888
Start accept ......
================== epoll_wait =============
New Client IP: 127.0.0.1, Port: 47634
================== epoll_wait =============
000001111122222
缓冲区数据已经读完
================== epoll_wait =============
hello world
缓冲区数据已经读完
client端:
[root@centos epoll]# ./client 8888
000001111122222
read buf = 000001111122222
, len = 17
hello world
read buf = hello world
, len = 13
执行结果分析:可以看出设置为epoll et非阻塞模式,当客户端发送数据不管有多少个字节,server端会全部从缓冲区读取并发送给客户端(包括客户端发送的回车('\n'))。
5. 示例四
基于网络C/S模型的epoll LT触发模式
lt_epoll.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <string.h>
6 #include <sys/socket.h>
7 #include <arpa/inet.h>
8 #include <ctype.h>
9 #include <sys/epoll.h>
10
11
12 int main(int argc, const char* argv[])
13 {
14 if(argc < 2)
15 {
16 printf("eg: ./a.out port\n");
17 exit(1);
18 }
19 struct sockaddr_in serv_addr;
20 socklen_t serv_len = sizeof(serv_addr);
21 int port = atoi(argv[1]);
22
23 // 创建套接字
24 int lfd = socket(AF_INET, SOCK_STREAM, 0);
25 // 初始化服务器 sockaddr_in
26 memset(&serv_addr, 0, serv_len);
27 serv_addr.sin_family = AF_INET; // 地址族
28 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机所有的IP
29 serv_addr.sin_port = htons(port); // 设置端口
30 // 绑定IP和端口
31 bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
32
33 // 设置同时监听的最大个数
34 listen(lfd, 36);
35 printf("Start accept ......\n");
36
37 struct sockaddr_in client_addr;
38 socklen_t cli_len = sizeof(client_addr);
39
40 // 创建epoll树根节点
41 int epfd = epoll_create(2000);
42 // 初始化epoll树
43 struct epoll_event ev;
44 ev.events = EPOLLIN;
45 ev.data.fd = lfd;
46 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
47
48 struct epoll_event all[2000];
49 while(1)
50 {
51 // 使用epoll通知内核fd 文件IO检测
52 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1);
53 printf("================== epoll_wait =============\n");
54
55 // 遍历all数组中的前ret个元素
56 for(int i=0; i<ret; ++i)
57 {
58 int fd = all[i].data.fd;
59 // 判断是否有新连接
60 if(fd == lfd)
61 {
62 // 接受连接请求
63 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len);
64 if(cfd == -1)
65 {
66 perror("accept error");
67 exit(1);
68 }
69 // 将新得到的cfd挂到树上
70 struct epoll_event temp;
71 temp.events = EPOLLIN;
72 temp.data.fd = cfd;
73 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp);
74
75 // 打印客户端信息
76 char ip[64] = {0};
77 printf("New Client IP: %s, Port: %d\n",
78 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)),
79 ntohs(client_addr.sin_port));
80
81 }
82 else
83 {
84 // 处理已经连接的客户端发送过来的数据
85 if(!all[i].events & EPOLLIN)
86 {
87 continue;
88 }
89
90 // 读数据
91 char buf[5] = {0};
92 int len = recv(fd, buf, sizeof(buf), 0);
93 if(len == -1)
94 {
95 perror("recv error");
96 exit(1);
97 }
98 else if(len == 0)
99 {
100 printf("client disconnected ....\n");
101 // fd从epoll树上删除
102 ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
103 if(ret == -1)
104 {
105 perror("epoll_ctl - del error");
106 exit(1);
107 }
108 close(fd);
109
110 }
111 else
112 {
113 // printf(" recv buf: %s\n", buf);
114 write(STDOUT_FILENO, buf, len);
115 write(fd, buf, len);
116 }
117 }
118 }
119 }
120
121 close(lfd);
122 return 0;
123 }
tcp_client.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8 #include <fcntl.h>
9
10 // tcp client
11 int main(int argc, const char* argv[])
12 {
13 if(argc < 2)
14 {
15 printf("eg: ./a.out port\n");
16 exit(1);
17 }
18 // 创建套接字
19 int fd = socket(AF_INET, SOCK_STREAM, 0);
20 if(fd == -1)
21 {
22 perror("socket error");
23 exit(1);
24 }
25 int port = atoi(argv[1]);
26 // 连接服务器
27 struct sockaddr_in serv_addr;
28 memset(&serv_addr, 0, sizeof(serv_addr));
29 serv_addr.sin_family = AF_INET;
30 serv_addr.sin_port = htons(port);
31 inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
32 int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
33 if(ret == -1)
34 {
35 perror("connect error");
36 exit(1);
37 }
38
39 // 通信
40 while(1)
41 {
42 // 写数据
43 // 接收键盘输入
44 char buf[512];
45 fgets(buf, sizeof(buf), stdin);
46 // 发送给服务器
47 write(fd, buf, strlen(buf)+1);
48
49 // 接收服务器端的数据
50 int len = read(fd, buf, sizeof(buf));
51 printf("read buf = %s, len = %d\n", buf, len);
52 }
53 return 0;
54 }
执行结果:
server端:
[root@centos epoll]# ./lt_epoll 8888
Start accept ......
================== epoll_wait =============
New Client IP: 127.0.0.1, Port: 47636
================== epoll_wait =============
00000================== epoll_wait =============
11111================== epoll_wait =============
22222================== epoll_wait =============
client端:
[root@centos epoll]# ./client 8888
000001111122222
read buf = 000001111122222
, len = 17
执行结果分析:可以看出,当客户端发送数据(000001111122222)到server端(接收数据缓冲区内),但是由于server端一次只接受5个字节(00000),因此在接受完5个字节之后,将接收的5个字节数据保存到发送缓冲区。然后程序回到epoll_wait处,此时检测到接收缓冲区还有未接收完的数据程序没有在epoll_wait处阻塞等待。而是继续从上一次缓冲区中读取剩余的数据(11111)及(22222),读取完成之后将所有数据发送给客户端。
文件描述符突破1024限制:
- select - 突破不了, 需要编译内核
- poll和epoll可以突破1024限制
解决办法:
- 查看受计算机硬件限制的文件描述符上限
- 通过配置文件修改上限值
五、线程池并发服务器
1)预先创建阻塞于accept多线程,使用互斥锁上锁保护accept
2)预先创建多线程,由主线程调用accept
六、UDP服务器
传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议。TCP协议在网络通信中占主导地位,绝大多数的网络通信借助TCP协议完成数据传输。但UDP也是网络通信中不可
或缺的重要通信手段。
相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和
正确率便得不到保证。因此,我们称UDP为“无连接的不可靠报文传递”。
那么与我们熟知的TCP相比,UDP有哪些优点和不足呢?由于无需创建连接,所以UDP开销较小,数据传输速度快,实时性较强。多用于对实时性要求较高的通信场合,如视频会议、电话会议
等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅
助校验协议来弥补UDP的不足,以达到数据可靠传输的目的。
与TCP类似的,UDP也有可能出现缓冲区被填满后,再接收数据时丢包的现象。由于它没有TCP滑动窗口的机制,通常采用如下两种方法解决:
1)服务器应用层设计流量控制,控制发送数据速度。
2)借助setsockopt函数改变接收缓冲区大小。如:
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
int n = 220x1024
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
注意:udp的数据是不安全的, 容易丢包。出现丢包, 不会出现丢部分数据,要丢只会丢全部数据。
TCP和UDP的使用场景:
tcp使用场景:
1)对数据安全性要求高的时候
a. 登录数据的传输
c. 文件传输
2)http协议
传输层协议 - tcp
udp使用场景:
1)效率高 - 实时性要求比较高
a. 视频聊天
b. 通话
2)有实力的大公司
a. 使用upd
b. 在应用层自定义协议, 做数据校验
七、C/S模型-UDP
由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,保证通讯可靠性的机制需要在应用层实现。
编译运行server,在两个终端里各开一个client与server交互,看看server是否具有并发服务的能力。用Ctrl+C关闭server,然后再运行server,看此时client还能否和server联系上。和前面TCP程序
的运行结果相比较,体会无连接的含义。
udp_server.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8
9 int main(int argc, const char* argv[])
10 {
11 // 创建套接字
12 int fd = socket(AF_INET, SOCK_DGRAM, 0);
13 if(fd == -1)
14 {
15 perror("socket error");
16 exit(1);
17 }
18
19 // fd绑定本地的IP和端口
20 struct sockaddr_in serv;
21 memset(&serv, 0, sizeof(serv));
22 serv.sin_family = AF_INET;
23 serv.sin_port = htons(8765);
24 serv.sin_addr.s_addr = htonl(INADDR_ANY);
25 int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv));
26 if(ret == -1)
27 {
28 perror("bind error");
29 exit(1);
30 }
31
32 struct sockaddr_in client;
33 socklen_t cli_len = sizeof(client);
34 // 通信
35 char buf[1024] = {0};
36 while(1)
37 {
38 int recvlen = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&client, &cli_len);
39 if(recvlen == -1)
40 {
41 perror("recvform error");
42 exit(1);
43 }
44
45 printf("recv buf: %s\n", buf);
46 char ip[64] = {0};
47 printf("New Client IP: %s, Port: %d\n",
48 inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)),
49 ntohs(client.sin_port));
50
51 // 给客户端发送数据
52 sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client));
53 }
54
55 close(fd);
56
57 return 0;
58 }
udp_client.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <string.h>
7 #include <arpa/inet.h>
8
9 int main(int argc, const char* argv[])
10 {
11 // create socket
12 int fd = socket(AF_INET, SOCK_DGRAM, 0);
13 if(fd == -1)
14 {
15 perror("socket error");
16 exit(1);
17 }
18
19 // 初始化服务器的IP和端口
20 struct sockaddr_in serv;
21 memset(&serv, 0, sizeof(serv));
22 serv.sin_family = AF_INET;
23 serv.sin_port = htons(8765);
24 inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
25
26 // 通信
27 while(1)
28 {
29 char buf[1024] = {0};
30 fgets(buf, sizeof(buf), stdin);
31 // 数据的发送 - server - IP port
32 sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&serv, sizeof(serv));
33
34 // 等待服务器发送数据过来
35 recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
36 printf("recv buf: %s\n", buf);
37 }
38
39 close(fd);
40
41 return 0;
42 }
- 点赞
- 收藏
- 关注作者
评论(0)