朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型

来源:互联网 发布:versions 1.7 for mac 编辑:程序博客网 时间:2024/05/29 04:34


        在《朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型》中,我们分析了它只能支持1024个连接同时处理的原因。但是在有些需要同时处理更多连接的情况下,1024个连接往往是不够的,也就是不能够高并发。那么这个时候我们就可以采用本文介绍的Poll模型。(转载请指明出于breaksoftware的csdn博客)

        在使用Poll模型之前,我们需要定义一个保存连接信息的数组

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. struct pollfd fds[FDS_COUNT];  

        之后创建异步监听socket、绑定端口和监听端口等行为和《朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型》一文中一模一样,本文就不列出代码了。我们把创建的socket信息赋值给fds数组的第一个元素,并且设定我们需要关注的事件POLLIN——可读。

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1.     int timeout;  
  2.     int cur_fds_count;  
  3.     int rc;  
  4.     int index;  
  5.     int expect_events;  
  6.     int error_events;  
  7. ……// 创建socket  
  8.     memset(fds, 0, sizeof(fds));  
  9.   
  10.     error_events = POLLERR | POLLNVAL;  
  11.     expect_events = POLLIN;  
  12.       
  13.     fds[0].fd = listen_sock;  
  14.     fds[0].events = expect_events;  
  15.     cur_fds_count = 1;  
        cur_fds_count用于记录当前fds数组中有多少个被关心的文件描述符。因为一开始我们只关心监听socket,所以它的初始值是1。

        然后我们就要在一个死循环中,不停的调用poll函数,监控我们关心的文件描述符是否发生了状态改变

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. timeout = (500);      
  2.   
  3. while (1) {  
  4.     rc = poll(fds, cur_fds_count, timeout);  
  5.     if (rc < 0) {  
  6.         perror("poll error\n");  
  7.         exit(EXIT_FAILURE);  
  8.     };  
  9.     if (rc == 0) {  
  10.         //perror("poll timeout\n");  
  11.     };  
        poll函数的返回值和select函数类似。如果返回小于0,则说明发生了错误,我们让程序退出。如果返回了0,则说明poll函数超时。如果大于0,则说明被关心的文件描述符状态发生了改变。但是此时,我们仍然不知道是哪个文件描述符发生了改变,所以我们要遍历fds数组。

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. int cur_fds_count_temp = cur_fds_count;  
  2. for (index = 0; index < cur_fds_count_temp; ++index) {  
        这个时候我们有必要说明下pollfd结构体的定义

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. struct pollfd {  
  2.     int fd;  
  3.     short events;  
  4.     short revents;  
  5. };  
        pollfd中的fd是用于记录我们关心的文件描述符;events表示我们关心的事件,如POLLIN、POLLOUT等。revents是实际发生的事件。于是我们一开始要判断改pollfd是否发生了事件改变

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. if (fds[index].revents == 0) {  
  2.     continue;  
  3. }  
        接着我们判断下发生的事件是否是我们定义的出错事件。

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. if (fds[index].revents & error_events) {  
  2.     perror("revents error");  
        如果出错的是监听socket,则我们退出程序。
[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. if (fds[index].fd == listen_sock) {  
  2.     perror("listen sock error");  
  3.     exit(EXIT_FAILURE);  
  4. }  
        如果出错的不是监听socket,则它就是客户端接入的socket,我们将它关闭,并且将fds数组的最后一个元素覆盖当前位置,让数组长度减一。这个过程是最精简的数组缩小方式,如果使用新数组去记录,将导致效率降低。

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. else {  
  2.     close(fds[index].fd);  
  3.     cur_fds_count--;  
  4.     if (index < cur_fds_count) {  
  5.         memcpy(&fds[index], &fds[cur_fds_count], sizeof(fds[cur_fds_count]));  
  6.         memset(&fds[cur_fds_count], 0, sizeof(fds[cur_fds_count]));  
  7.         index--;  
  8.     }  
  9.     cur_fds_count_temp--;  
  10.     continue;  
  11. }     
        我们再看发生的事件是否是我们关心的事件,如果不是,则continue掉,继续处理下一个pollfd。

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. if (!(fds[index].revents & fds[index].events)) {  
  2.     continue;  
  3. }  
        经过上述筛选,剩下的就是我们要正常处理的pollfd了。和Select方式一样,我们先看看其是否是监听socket。如果是,则获取客户端接入的socket值,并记录到数组中。

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. if (fds[index].fd == listen_sock) {  
  2.     int new_sock;  
  3.     new_sock = accept(listen_sock, NULL, NULL);  
  4.     if (new_sock < 0) {  
  5.         //perror("accept error");  
  6.         if (errno != EWOULDBLOCK) {  
  7.             continue;  
  8.         }  
  9.         exit(EXIT_FAILURE);  
  10.     }  
  11.     else {  
  12.         request_add(1);  
  13.         //set_block_filedes_timeout(new_sock);  
  14.         if (cur_fds_count + 1 < sizeof(fds)) {  
  15.             fds[cur_fds_count].fd = new_sock;  
  16.             fds[cur_fds_count].events = expect_events;  
  17.             cur_fds_count++;  
  18.         }  
  19.     }  
  20. }  
        如果不是监听socket,则是客户端接入的socket。我们就读取这个socket中的内容,并写入我们的回包。

[cpp] view plain copy print?在CODE上查看代码片派生到我的代码片
  1.             else {  
  2.                 if (0 == server_read(fds[index].fd)) {  
  3.                     server_write(fds[index].fd);  
  4.                 }  
  5.                 close(fds[index].fd);  
  6.                 cur_fds_count--;  
  7.                 if (index < cur_fds_count) {  
  8.                     memcpy(&fds[index], &fds[cur_fds_count], sizeof(fds[cur_fds_count]));  
  9.                     memset(&fds[cur_fds_count], 0, sizeof(fds[cur_fds_count]));  
  10.                     index--;  
  11.                 }  
  12.                 cur_fds_count_temp--;  
  13.             }  
  14.         }  
  15.     }  
  16.     return 0;  
  17. }  

        如此,我们便将简单的poll服务器给写完了。我们看下poll模型的处理能力。采用和《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》一文中相同的环境和压力,我们看下服务器的数据输出


        再看下客户端的输出


       可见当前环境下poll模型的处理能力大概是每秒7500次请求。

0 0
原创粉丝点击