Linux C编程第十八章 高并发服务器(四)

网友投稿 581 2022-05-29

4. 实例三

基于网络C/S非阻塞模型的epoll ET触发模式

实现过程中注意两点:

当服务端接收到客户端新的连接(cfd),需要设置客户端连接问价描述符(cfd)为非阻塞模式,因为下面需要循环读取服务端缓冲区中的数据,而如果不设置 cfd 为非阻塞模式,则当读完缓冲区的数据 recv 再次读取会阻塞住,则整个程序会被阻塞;

通过errno == EAGAIN来判断缓冲区中的数据读取完成。

nonblock_et_epoll.c

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 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 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 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 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 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 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

tcp_client.c

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 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 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

【Linux C编程】第十八章 高并发服务器(四)

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 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 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 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 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 }

【Linux C编程】第十八章 高并发服务器(五)

Linux TCP/IP

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:实例说明optimize table在优化MySQL时很重要
下一篇:华为全面启航计算战略:“鲲鹏+昇腾”双引擎
相关文章