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

举报
Yuchuan 发表于 2021/05/12 17:17:30 2021/05/12
【摘要】 高并发服务器

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 }

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


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。