socket 编程篇六之IPO多路复用-select poll epoll

http://blog.csdn.net/woxiaohahaa/article/details/51498951

文章参考自:http://blog.csdn.net/tennysonsky/article/details/45745887(秋叶原 — Mike VS 麦克《Linux系统编程——I/O多路复用select、poll、epoll的区别使用》)

此外,还有一篇好文:epoll机制:epoll_create、epoll_ctl、epoll_wait、close(鱼思故渊的专栏)


在上一篇中,我简单学习了 IO多路复用的基本概念,这里我将初学其三种实现手段:selectpollepoll


I/O 多路复用是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程或线程不阻塞于某个特定的 I/O 系统调用。

select(),poll(),epoll()都是I/O多路复用的机制。I/O多路复用通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪,就是这个文件描述符进行读写操作之前),能够通知程序进行相应的读写操作。但select(),poll(),epoll()本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

与多线程(TPC(Thread Per Connection)模型)和多进程(典型的Apache模型(Process Per Connection,简称PPC))相比,I/O 多路复用的最大优势是系统开销小,系统不需要建立新的进程或者线程,也不必维护这些线程和进程。



select()的使用


所需头文件:

[cpp] view plaincopy
  1. #include <sys/select.h>  


函数原型:

[cpp] view plaincopy
  1. int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);  


函数描述:

监视并等待多个文件描述符的属性变化(可读、可写或错误异常)。select()函数监视的文件描述符分 3 类,分别是writefds、readfds、和 exceptfds。调用后 select() 函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有错误异常),或者超时( timeout 指定等待时间),函数才返回。当 select()函数返回后,可以通过遍历 fdset,来找到就绪的描述符。


参数描述:

nfds: 要监视的文件描述符的范围,一般取监视的描述符数的最大值+1,如这里写 10, 这样的话,描述符 0,1, 2 …… 9 都会被监视,在 Linux 上最大值一般为1024。

readfd: 监视的可读描述符集合,只要有文件描述符读操作准备就绪,这个文件描述符就存储到这。

writefds监视的可写描述符集合。

exceptfds: 监视的错误异常描述符集合。


三个参数 readfds、writefds 和 exceptfds 指定我们要让内核监测读、写和异常条件的描述字。如果不需要使用某一个的条件,就可以把它设为NULL 。


几个较为重要的宏:

[cpp] view plaincopy
  1. //清空集合  
  2. void FD_ZERO(fd_set *fdset);   
  3.   
  4. //将一个给定的文件描述符加入集合之中  
  5. void FD_SET(int fd, fd_set *fdset);  
  6.   
  7. //将一个给定的文件描述符从集合中删除  
  8. void FD_CLR(int fd, fd_set *fdset);  
  9.   
  10. //检查集合中指定的文件描述符是否可以读写   
  11. int FD_ISSET(int fd, fd_set *fdset);   
  12.   
  13. timeout: 超时时间,它告知内核等待所指定描述字中的任何一个就绪可花多少时间。其 timeval 结构用于指定这段时间的秒数和微秒数。  
  14. struct timeval  
  15. {  
  16. time_t tv_sec;       /* 秒 */  
  17. suseconds_t tv_usec; /* 微秒 */  
  18. };  


三种可能的函数返回情况:

1)永远等待下去:timeout 设置为空指针 NULL,且没有一个描述符准备好。

2)等待固定时间timeout 设置为某个固定时间,在有一个描述符准备好时返回,如果时间到了,就算没有文件描述符准备就绪,这个函数也会返回 0。

3)不等待(不阻塞):检查描述字后立即返回,这称为轮询。为此,struct timeval变量的时间值指定为 0 秒 0 微秒,文件描述符属性无变化返回 0,有变化返回准备好的描述符数量。


函数返回值:

成功:就绪描述符的数目(同时修改readfds、writefds 和 exceptfds 三个参数),超时返回 0;
出错:-1。


下面用 Socket 举例,两个客户端,其中一个每隔 5s 发一个固定的字符串到服务器,另一个采集终端的键盘输入,将其发给服务器,一个服务器,使用 IO 多路复用处理这两个客户端。代码如下:


服务器:

[cpp] view plaincopy
  1. #include <cstdio>  
  2. #include <sys/select.h>  
  3. #include <unistd.h>  
  4. #include <stdlib.h>  
  5. #include <cstring>  
  6. #include <cassert>  
  7. #include <sys/types.h>  
  8. #include <sys/socket.h>  
  9. #include <netinet/in.h>  
  10. #include <arpa/inet.h>  
  11.   
  12. const int BUFFER_SIZE = 4096;  
  13. const int SERVER_PORT = 2222;  
  14.   
  15. inline int max(int a, int b){ return (a > b ? : a, b);}  
  16.   
  17. int main()  
  18. {  
  19.     int server_socket;  
  20.     char buff1[BUFFER_SIZE];  
  21.     char buff2[BUFFER_SIZE];  
  22.     fd_set rfds;  
  23.     struct timeval tv;  
  24.     int ret;  
  25.     int n;  
  26.   
  27.     server_socket = socket(AF_INET, SOCK_STREAM, 0);  
  28.     assert(server_socket != -1);  
  29.   
  30.     struct sockaddr_in server_addr;  
  31.     memset(&server_addr, 0, sizeof(server_addr));  
  32.     server_addr.sin_family = AF_INET;  
  33.     server_addr.sin_port = htons(SERVER_PORT);  
  34.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  35.   
  36.     assert(bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) != -1);  
  37.     assert(listen(server_socket, 5) != -1);  
  38.       
  39.     struct sockaddr_in client_addr1, client_addr2;  
  40.     socklen_t client_addr_len = sizeof(struct sockaddr_in);  
  41.       
  42.     printf("waiting...\n");  
  43.   
  44.     //此处先建立两个 TCP 连接  
  45.     int connfd1 = accept(server_socket, (struct sockaddr*)&client_addr1, &client_addr_len);  
  46.     assert(connfd1 != -1);  
  47.     printf("connect from %s:%d\n", inet_ntoa(client_addr1.sin_addr), ntohs(client_addr1.sin_port));  
  48.     int connfd2 = accept(server_socket, (struct sockaddr*)&client_addr2, &client_addr_len);  
  49.     assert(connfd2 != -1);  
  50.     printf("connect from %s:%d\n", inet_ntoa(client_addr2.sin_addr), ntohs(client_addr2.sin_port));  
  51.   
  52.     while(1)  
  53.     {  
  54.         FD_ZERO(&rfds);  
  55.         FD_SET(connfd1, &rfds);  
  56.         FD_SET(connfd2, &rfds);  
  57.   
  58.         tv.tv_sec = 10;  
  59.         tv.tv_usec = 0;  
  60.           
  61.         printf("select...\n");  
  62.         ret = select(max(connfd1, connfd2) + 1, &rfds, NULL, NULL, NULL);  
  63.         //ret = select(max(connfd1, connfd2) + 1, &rfds, NULL, NULL, &tv);  
  64.           
  65.         if(ret == -1)  
  66.             perror("select()");  
  67.         else if(ret > 0)  
  68.         {  
  69.             if(FD_ISSET(connfd1, &rfds))  
  70.             {     
  71.                 n = recv(connfd1, buff1, BUFFER_SIZE, 0);  
  72.                 buff1[n] = '\0';                    //注意手动添加字符串结束符  
  73.                 printf("connfd1: %s\n", buff1);  
  74.             }  
  75.             if(FD_ISSET(connfd2, &rfds))  
  76.             {  
  77.                 n = recv(connfd2, buff2, BUFFER_SIZE, 0);  
  78.                 buff2[n] = '\0';                    //注意手动添加字符串结束符  
  79.                 printf("connfd2: %s\n", buff2);  
  80.             }         
  81.         }  
  82.         else  
  83.             printf("time out\n");  
  84.     }  
  85.   
  86.     return 0;  
  87. }  

客户端1:

[cpp] view plaincopy
  1. #include <cstdio>  
  2. #include <unistd.h>  
  3. #include <stdlib.h>  
  4. #include <cstring>  
  5. #include <cassert>  
  6. #include <sys/types.h>  
  7. #include <sys/socket.h>  
  8. #include <netinet/in.h>  
  9. #include <arpa/inet.h>  
  10.   
  11. const int BUFFER_SIZE = 4096;  
  12. const int SERVER_PORT = 2222;  
  13.   
  14. int main()  
  15. {  
  16.     int client_socket;  
  17.     const char *server_ip = "127.0.0.1";  
  18.     char buffSend[BUFFER_SIZE] = "I'm from d.cpp";  
  19.   
  20.     client_socket = socket(AF_INET, SOCK_STREAM, 0);  
  21.     assert(client_socket != -1);  
  22.   
  23.     struct sockaddr_in server_addr;  
  24.     memset(&server_addr, 0, sizeof(server_addr));  
  25.     server_addr.sin_family = AF_INET;  
  26.     server_addr.sin_port = htons(SERVER_PORT);  
  27.     server_addr.sin_addr.s_addr = inet_addr(server_ip);  
  28.   
  29.     assert(connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) != -1);  
  30.       
  31.     while(1)  
  32.     {  
  33.         assert(send(client_socket, buffSend, strlen(buffSend), 0) != -1);  
  34.         sleep(5);  
  35.     }  
  36.     close(client_socket);  
  37.   
  38.     return 0;  
  39. }  


客户端2:

[cpp] view plaincopy
  1. #include <cstdio>  
  2. #include <unistd.h>  
  3. #include <stdlib.h>  
  4. #include <cstring>  
  5. #include <cassert>  
  6. #include <sys/types.h>  
  7. #include <sys/socket.h>  
  8. #include <netinet/in.h>  
  9. #include <arpa/inet.h>  
  10.   
  11. const int BUFFER_SIZE = 4096;  
  12. const int SERVER_PORT = 2222;  
  13.   
  14. int main()  
  15. {  
  16.     int client_socket;  
  17.     const char *server_ip = "127.0.0.1";  
  18.     char buffSend[BUFFER_SIZE];  
  19.   
  20.     client_socket = socket(AF_INET, SOCK_STREAM, 0);  
  21.     assert(client_socket != -1);  
  22.   
  23.     struct sockaddr_in server_addr;  
  24.     memset(&server_addr, 0, sizeof(server_addr));  
  25.     server_addr.sin_family = AF_INET;  
  26.     server_addr.sin_port = htons(SERVER_PORT);  
  27.     server_addr.sin_addr.s_addr = inet_addr(server_ip);  
  28.   
  29.     assert(connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) != -1);  
  30.       
  31.     while(1)  
  32.     {  
  33.         fgets(buffSend, BUFFER_SIZE, stdin);  
  34.         assert(send(client_socket, buffSend, strlen(buffSend), 0) != -1);  
  35.     }  
  36.     close(client_socket);  
  37.   
  38.     return 0;  
  39. }  

以上三份代码有缺陷,代码没有很好的结束方式,都是 while(1) 死循环,运行的结束需要用 Ctrl + c  ⊙﹏⊙






poll()的使用


select() 和 poll() 系统调用的本质一样,前者在 BSD UNIX 中引入的,后者在 System V 中引入的。poll() 的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)poll() 和 select() 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。


所需头文件:

[cpp] view plaincopy
  1. #include <poll.h>  

函数原型:

[cpp] view plaincopy
  1. int poll(struct pollfd *fds, nfds_t nfds, int timeout);  

函数描述:

监视并等待多个文件描述符的属性变化。


函数参数:

1)fds:与 select() 使用三个 fd_set 的方式不同,poll() 使用一个 pollfd 的指针实现。一个 pollfd 结构体数组,其中包括了你想监视的文件描述符和事件, 事件由结构中事件域 events 来确定,调用后实际发生的事件将被填写在结构体的 revents 域。


[cpp] view plaincopy
  1. struct pollfd{  
  2. int fd;         /* 文件描述符 */  
  3. short events;   /* 等待的事件 */  
  4. short revents;  /* 实际发生了的事件 */  
  5. };   

_1、fd:每一个 pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示 poll() 监视多个文件描述符。


_2、events:每个结构体的 events 域是监视该文件描述符的事件掩码,由用户来设置这个域。


_3、revents:revents 域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回。


事件的掩码取值如下:


POLLIN | POLLPRI 等价于 select() 的读事件,POLLOUT | POLLWRBAND 等价于 select() 的写事件。POLLIN 等价于 POLLRDNORM | POLLRDBAND,而 POLLOUT 则等价于 POLLWRNORM 。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events 为 POLLIN | POLLOUT。

每个结构体的 events 域是由用户来设置,告诉内核我们关注的是什么,而 revents 域是返回时内核设置的,以说明对该描述符发生了什么事件。

2)nfds:用来指定第一个参数数组元素个数。


3)timeout: 指定等待的毫秒数,无论 I/O 是否准备好,poll() 都会返回。





函数返回值:

成功时,poll() 返回结构体中 revents 域不为 0 的文件描述符个数,如果在超时前没有任何事件发生,poll()返回 0;

失败时,poll() 返回 -1。



此处我们将上面的例子用 poll() 重新实现如下,只用修改服务器端代码:

[cpp] view plaincopy
  1. #include <cstdio>  
  2. #include <poll.h>  
  3. #include <unistd.h>  
  4. #include <stdlib.h>  
  5. #include <cstring>  
  6. #include <cassert>  
  7. #include <sys/types.h>  
  8. #include <sys/socket.h>  
  9. #include <netinet/in.h>  
  10. #include <arpa/inet.h>  
  11.   
  12. const int BUFFER_SIZE = 4096;  
  13. const int SERVER_PORT = 2222;  
  14.   
  15. int main()  
  16. {  
  17.     int server_socket;  
  18.     char buff1[BUFFER_SIZE];  
  19.     char buff2[BUFFER_SIZE];  
  20.     struct timeval tv;  
  21.     int ret;  
  22.     int n;  
  23.   
  24.     server_socket = socket(AF_INET, SOCK_STREAM, 0);  
  25.     assert(server_socket != -1);  
  26.   
  27.     struct sockaddr_in server_addr;  
  28.     memset(&server_addr, 0, sizeof(server_addr));  
  29.     server_addr.sin_family = AF_INET;  
  30.     server_addr.sin_port = htons(SERVER_PORT);  
  31.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  32.   
  33.     assert(bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) != -1);  
  34.     assert(listen(server_socket, 5) != -1);  
  35.       
  36.     struct sockaddr_in client_addr1, client_addr2;  
  37.     socklen_t client_addr_len = sizeof(struct sockaddr_in);  
  38.       
  39.     printf("waiting...\n");  
  40.   
  41.     int connfd1 = accept(server_socket, (struct sockaddr*)&client_addr1, &client_addr_len);  
  42.     assert(connfd1 != -1);  
  43.     printf("connect from %s:%d\n", inet_ntoa(client_addr1.sin_addr), ntohs(client_addr1.sin_port));  
  44.     int connfd2 = accept(server_socket, (struct sockaddr*)&client_addr2, &client_addr_len);  
  45.     assert(connfd2 != -1);  
  46.     printf("connect from %s:%d\n", inet_ntoa(client_addr2.sin_addr), ntohs(client_addr2.sin_port));  
  47.   
  48.     struct pollfd rfds[2];  
  49.     rfds[0].fd = connfd1;  
  50.     rfds[0].events = POLLIN;  
  51.     rfds[1].fd = connfd2;  
  52.     rfds[1].events = POLLIN;  
  53.     tv.tv_sec = 10;  
  54.     tv.tv_usec = 0;  
  55.       
  56.     while(1)  
  57.     {  
  58.         printf("poll...\n");  
  59.         ret = poll(rfds, 2, -1);  
  60.           
  61.         if(ret == -1)  
  62.             perror("poll()");  
  63.         else if(ret > 0)  
  64.         {     
  65.             if((rfds[0].revents & POLLIN) == POLLIN)  
  66.             {     
  67.                 n = recv(connfd1, buff1, BUFFER_SIZE, 0);  
  68.                 buff1[n] = '\0';  
  69.                 printf("connfd1: %s\n", buff1);  
  70.             }  
  71.             if((rfds[1].revents & POLLIN) == POLLIN)  
  72.             {  
  73.                 n = recv(connfd2, buff2, BUFFER_SIZE, 0);  
  74.                 buff2[n] = '\0';  
  75.                 printf("connfd2: %s\n", buff2);  
  76.             }     
  77.         }  
  78.         else  
  79.             printf("time out\n");  
  80.     }  
  81.   
  82.     return 0;  
  83. }  







epoll()的使用



epoll 是在内核 2.6 中提出的,是之前的 select() 和 poll() 的增强版本。相对于 select() 和 poll() 来说,epoll 更加灵活,没有描述符限制。epoll 使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的 copy 只需一次。


epoll 操作过程需要三个接口,分别如下:

[cpp] view plaincopy
  1. #include <sys/epoll.h>    
  2. int epoll_create(int size);    
  3. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);    
  4. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);    



int epoll_create(int size);

功能:

该函数生成一个 epoll 专用的文件描述符。

参数:

size: 用来告诉内核这个监听的数目一共有多大,参数 size 并不是限制了 epoll 所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。自从 linux 2.6.8 之后,size 参数是被忽略的,也就是说可以填只有大于 0 的任意值


返回值:
成功:epoll 专用的文件描述符
失败:-1




int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能:

epoll 的事件注册函数,它不同于 select() 是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

参数:

epfd: epoll 专用的文件描述符,epoll_create()的返回值

op: 表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的 fd 到 epfd 中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从 epfd 中删除一个 fd;

fd: 需要监听的文件描述符

event: 告诉内核要监听什么事件,struct epoll_event 结构如:

[cpp] view plaincopy
  1. // 保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)    
  2. typedef union epoll_data {    
  3.     void *ptr;    
  4.     int fd;    
  5.     __uint32_t u32;    
  6.     __uint64_t u64;    
  7. } epoll_data_t;    
  8.     
  9. // 感兴趣的事件和被触发的事件    
  10. struct epoll_event {    
  11.     __uint32_t events; /* Epoll events */    
  12.     epoll_data_t data; /* User data variable */    
  13. };    

events 可以是以下几个宏的集合:

EPOLLIN :表示对应的文件描述符可以读(包括对端 SOCKET 正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET :将 EPOLL 设为边缘触发(Edge Trigger)模式,这是相对于水平触发(Level Trigger)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列里

返回值:

成功:0

失败:-1



int epoll_wait( int epfd, struct epoll_event * events, int maxevents, int timeout );

功能:

等待事件的产生,收集在 epoll 监控的事件中已经发送的事件,类似于 select() 调用。

参数:

epfd: epoll 专用的文件描述符,epoll_create()的返回值

events: 分配好的 epoll_event 结构体数组,epoll 将会把发生的事件赋值到events 数组中(events 不可以是空指针,内核只负责把数据复制到这个 events 数组中,不会去帮助我们在用户态中分配内存)。

maxevents: maxevents 告之内核这个 events 有多少个 。

timeout: 超时时间,单位为毫秒,为 -1 时,函数为阻塞。

返回值:

成功:返回需要处理的事件数目,如返回 0 表示已超时

失败:-1


epoll 对文件描述符的操作有两种模式:LT(level trigger)和 ET(edge trigger)。LT 模式是默认模式LT 模式与 ET 模式的区别如下

LT 模式:支持block和no-block socket。当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用 epoll_wait 时,会再次响应应用程序并通知此事件。效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。

ET 模式:只支持no-block socket。当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用 epoll_wait 时,不会再次响应应用程序并通知此事件。该模式效率非常高,尤其在高并发,大流量的情况下,会比LT少很多epoll的系统调用。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。


接下来,我们将上面的例子,改为用 epoll 实现:

[cpp] view plaincopy
  1. #include <cstdio>  
  2. #include <sys/epoll.h>  
  3. #include <unistd.h>  
  4. #include <stdlib.h>  
  5. #include <cstring>  
  6. #include <cassert>  
  7. #include <sys/types.h>  
  8. #include <sys/socket.h>  
  9. #include <netinet/in.h>  
  10. #include <arpa/inet.h>  
  11.   
  12. const int BUFFER_SIZE = 4096;  
  13. const int SERVER_PORT = 2222;  
  14.   
  15. int main()  
  16. {  
  17.     int server_socket;  
  18.     char buff1[BUFFER_SIZE];  
  19.     char buff2[BUFFER_SIZE];  
  20.     struct timeval tv;  
  21.     int ret;  
  22.     int n, i;  
  23.   
  24.     server_socket = socket(AF_INET, SOCK_STREAM, 0);  
  25.     assert(server_socket != -1);  
  26.   
  27.     struct sockaddr_in server_addr;  
  28.     memset(&server_addr, 0, sizeof(server_addr));  
  29.     server_addr.sin_family = AF_INET;  
  30.     server_addr.sin_port = htons(SERVER_PORT);  
  31.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  32.   
  33.     assert(bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) != -1);  
  34.     assert(listen(server_socket, 5) != -1);  
  35.       
  36.     struct sockaddr_in client_addr1, client_addr2;  
  37.     socklen_t client_addr_len = sizeof(struct sockaddr_in);  
  38.       
  39.     printf("waiting...\n");  
  40.   
  41.     int connfd1 = accept(server_socket, (struct sockaddr*)&client_addr1, &client_addr_len);  
  42.     assert(connfd1 != -1);  
  43.     printf("connect from %s:%d\n", inet_ntoa(client_addr1.sin_addr), ntohs(client_addr1.sin_port));  
  44.     int connfd2 = accept(server_socket, (struct sockaddr*)&client_addr2, &client_addr_len);  
  45.     assert(connfd2 != -1);  
  46.     printf("connect from %s:%d\n", inet_ntoa(client_addr2.sin_addr), ntohs(client_addr2.sin_port));  
  47.   
  48.     tv.tv_sec = 10;  
  49.     tv.tv_usec = 0;  
  50.       
  51.     struct epoll_event event;  
  52.     struct epoll_event wait_event[2];  
  53.       
  54.     int epfd = epoll_create(10);  
  55.     assert(epfd != -1);  
  56.       
  57.     event.data.fd = connfd1;  
  58.     event.events = EPOLLIN;  
  59.     assert(epoll_ctl(epfd, EPOLL_CTL_ADD, connfd1, &event) != -1);  
  60.     event.data.fd = connfd2;  
  61.     event.events = EPOLLIN;  
  62.     assert(epoll_ctl(epfd, EPOLL_CTL_ADD, connfd2, &event) != -1);  
  63.   
  64.       
  65.     while(1)  
  66.     {  
  67.         printf("epoll...\n");  
  68.         ret = epoll_wait(epfd, wait_event, 2, -1);  
  69.           
  70.         if(ret == -1)  
  71.             perror("epoll()");  
  72.         else if(ret > 0)  
  73.         {     
  74.             for(i = 0; i < ret; ++i)  
  75.             {  
  76.                 if(wait_event[i].data.fd == connfd1 && (wait_event[i].events & EPOLLIN) == EPOLLIN)  
  77.                 {     
  78.                     n = recv(connfd1, buff1, BUFFER_SIZE, 0);  
  79.                     buff1[n] = '\0';  
  80.                     printf("connfd1: %s\n", buff1);  
  81.                 }  
  82.                 else if(wait_event[i].data.fd == connfd2 && (wait_event[i].events & EPOLLIN) == EPOLLIN)  
  83.                 {  
  84.                     n = recv(connfd2, buff2, BUFFER_SIZE, 0);  
  85.                     buff2[n] = '\0';  
  86.                     printf("connfd2: %s\n", buff2);  
  87.                 }     
  88.             }  
  89.         }  
  90.         else  
  91.             printf("time out\n");  
  92.     }  
  93.   
  94.     return 0;  
  95. }  


在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而 epoll() 事先通过 epoll_ctl() 来注册一个文件描述符,一旦某个文件描述符就绪时,内核会采用类似 callback 的回调机制(软件中断 ),迅速激活这个文件描述符,当进程调用 epoll_wait() 时便得到通知。





下面分析 select、poll、epoll之间的优缺点:


select:

缺点:

1)每次调用select,都存在 fd 集合在用户态与内核态之间的拷贝,I/O 的效率会随着监视 fd 的数量的增长而线性下降。
2)select()调用的内部,需要用轮询的方式去完整遍历每一个 fd,如果遍历完所有 fd 后没有发现就绪 fd,则挂起当前进程,直到有 fd 就绪或者主动超时(使用 schedule_timeout 实现睡一会儿,判断一次(被定时器唤醒,注意与 select() 函数里面的 timeout 参数区分作用)的效果),被唤醒后它又要再次遍历 fd (直到有 fd 就绪或 select() 函数超时)。这个过程经历了多次无谓的遍历。CPU的消耗会随着监视 fd 的数量的增长而线性增加

[cpp] view plaincopy
  1. 步骤总结如下:  
  2. 1)先把全部fd扫一遍;  
  3. 2)如果发现有可用的fd,跳到5;  
  4. 3)如果没有,当前进程去睡觉xx秒(schedule_timeout机制);  
  5. 4)xx秒后自己醒了,或者状态变化的fd唤醒了自己,跳到1;  
  6. 5)结束循环体,返回。(注意函数的返回也可能是 timeout 的超时)  

3)select支持的文件描述符数量太小了,默认是1024。

4)由于 select 参数输入和输出使用同样的 fd_set ,导致每次 select()  之前都要重新初始化要监视的 fd_set,开销也会比较大。


poll:

poll 的实现和 select 非常相似,它同样存在 fd 集合在用户态和内核态间的拷贝,且在函数内部需要轮询整个 fd 集合。区别于select 的只是描述fd集合的方式不同,poll使用pollfd数组而不是select的fd_set结构,所以poll克服了select文件描述符数量的限制,此外,poll 的 polldf 结构体中分别用 events 和 revents 来存储输入和输出,较之 select() 不用每次调用 poll() 之前都要重新初始化需要监视的事件。



epoll:

epoll是一种 Reactor 模式,提供了三个函数,epoll_create(),epoll_ctl() 和 epoll_wait()。

优点:

1)对于上面的第一个缺点,epoll 的解决方案在 epoll_ctl() 函数中。每次注册新的事件到 epoll 描述符中时,会把该 fd 拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll 保证了每个fd在整个过程中只会拷贝一次。
2)对于第二个缺点,epoll 的解决方案不像 select 或 poll 一样轮询 fd,而只在 epoll_ctl 时把要监控的 fd 挂一遍,并为每个 fd 指定一个回调函数,当设备就绪,这个回调函数把就绪的 fd 加入一个就绪链表。epoll_wait 的工作实际上就是在这个就绪链表中查看有没有就绪的 fd,也即 epoll_wait 只关心“活跃”的描述符,而不用像 select() 和 poll() 需要遍历所有 fd,它需要不断轮询就绪链表,期间也可能多次睡眠和唤醒(类似与 select, poll),但终究它的轮询只用判断就续表是否为空即可,其CPU的消耗不会随着监视 fd 的数量的增长而线性增加,这就是回调机制的优势,也正是 epoll 的魅力所在。


同理,select() 和 poll() 函数返回后, 处理就绪 fd 的方法还是轮询,如下:

[cpp] view plaincopy
  1. int res = select(maxfd+1, &readfds, NULL, NULL, 120);    
  2. if (res > 0)    
  3. {    
  4.     for (int i = 0; i < MAX_CONNECTION; i++)    
  5.     {    
  6.         if (FD_ISSET(allConnection[i], &readfds))    
  7.         {    
  8.             handleEvent(allConnection[i]);    
  9.         }    
  10.     }    
  11. }    
  12. // if(res == 0) handle timeout, res < 0 handle error   


而 epoll() 只需要从就绪链表中处理就绪的 fd:

[cpp] view plaincopy
  1. int res = epoll_wait(epfd, events, 20, -1);    
  2. for (int i = 0; i < res;i++)    
  3. {    
  4.     handleEvent(events[n]);    
  5. }    

此处的效率对比也是高下立判。




3)对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个和系统限制有关,linux里面可以用ulimit查看文件打开数限制。


缺点:epoll是 linux 特有的,而 select 和 poll 是在 POSIX 中规定的,跨平台支持更好。





综上:

select 、poll、epoll 的使用要根据具体的使用场合,并不是 epoll 的性能就一定好,因为回调函数也是有消耗的,当 socket 连接较少时或者是即使 socket 连接很多,但是连接基本都是活跃的情况下,select / poll 的性能与 epoll 是差不多的。即如果没有大量的 idle-connection 或者 dead-connection,epoll 的效率并不会比 select/poll 高很多,但是当遇到大量的 idle-connection,就会发现epoll 的效率大大高于 select/poll。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383958.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浅谈传输层

1. 传输层的作用 在传输层中有两个特别重要的协议 TCP/UDP . 以快递员送快递为例说明这个问题吧. 在进行包裹传输的过程中快递员需要根据快递上的目的地址(目的计算机)来投递包裹(IP数据报), 加入在快递单上只写了收件人的所在地, 所在单位, 而只写了收件人的姓没有写收件人的…

I/O复用的 select poll和epoll的简单实现

http://www.cnblogs.com/wj9012/p/3876734.html 一个tcp的客户端服务器程序 服务器端不变&#xff0c;客户端通过I/O复用轮询键盘输入与socket输入&#xff08;接收客户端的信息&#xff09; 服务器端&#xff1a; 1 /*服务器:2 1.客户端关闭后&#xff0c;服务器再向客户端发送…

TCP相关代码

TCP 基础代码 //tcp_server.c #include<stdio.h> #include<error.h> #include<sys/types.h> #include<string.h> #include<unistd.h> #include<sys/socket.h> #include<netinet/in.h> #include <arpa/inet.h> #include<st…

几种并发服务器模型的实现:多线程,多进程,select,poll,epoll

http://www.cnblogs.com/wj9012/p/3879605.html 客户端使用select模型&#xff1a; 1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 #include <errno.h>5 #include <sys/types.h>6 #include <sys/socket.h>7 #include …

linux进程通信---几个发送信号的函数(kill,raise,alarm,pause)

http://blog.csdn.net/zzyoucan/article/details/9235685 信号&#xff1a;信号是unix中最古老的进程通信的一种方式&#xff0c;他是软件层次上对中断机制的模拟&#xff0c;是一种异步通信方式&#xff0c;信号可以实现用户空间进程和内核空间进程的交互&#xff0c;内核进程…

Linux epoll模型

http://www.cnblogs.com/venow/archive/2012/11/30/2790031.html 定义&#xff1a; epoll是Linux内核为处理大批句柄而作改进的poll&#xff0c;是Linux下多路复用IO接口select/poll的增强版本&#xff0c;它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利…

Linux IO模式及 select、poll、epoll详解

https://segmentfault.com/a/1190000003063859 同步IO和异步IO&#xff0c;阻塞IO和非阻塞IO分别是什么&#xff0c;到底有什么区别&#xff1f;不同的人在不同的上下文下给出的答案是不同的。所以先限定一下本文的上下文。 本文讨论的背景是Linux环境下的network IO。一 概念…

mysql思维导图

后期会不断进行更新

C++第一节课

C数据类型 几个概念 命名空间是C标准库引入的,其中命名空间可以解决变量冲突问题,当出现局部变量和全局变量同名的时候, 局部变量优先被访问.同时命名空间的格式如同一下代码 namespace name1 { int a 0; }namespace name2 { int a 2; } 注意C中的所有组件都是在一个叫做s…

【C/C++】关键字static

http://blog.csdn.net/woxiaohahaa/article/details/51014224 参考自&#xff1a;http://www.cnblogs.com/biyeymyhjob/archive/2012/07/19/2598815.html &#xff08;华山大师兄&#xff09; 这里我们只讨论了C语言的static 首先我们回顾一下各种变量在内存中的位置&#xff1…

IO多路复用之epoll总结

http://www.cnblogs.com/Anker/p/3263780.html 1、基本知识 epoll是在2.6内核中提出的&#xff0c;是之前的select和poll的增强版本。相对于select和poll来说&#xff0c;epoll更加灵活&#xff0c;没有描述符限制。epoll使用一个文件描述符管理多个描述符&#xff0c;将用户关…

3_V1-类和对象 -- 默认成员函数

定义一个日期类 #include <iostream> #include <assert.h> using namespace std;class Date { public:void Display(); private:int _year;int _month;int _day; }; 注意: 在定义一个类的时候往往会将其成员变量定义为私有,成员函数定义为公有.这是为了达到软件…

HDU - 2973威尔逊定理

核心问题就是那个等式 我们观察到等式可以写成(n-1)!-1/n-[(n-1)!/n]的形式&#xff0c;这样就应该联想到威尔逊定理了。 回顾一下威尔逊定理的内容&#xff1a;当且仅当n为素数的时候n|(n-1)!-1&#xff0c;n为合数且大于4的时候n|(n-1)!【参见威尔逊定理的证明】 对于这个…

linux网络编程之posix 线程(四):posix 条件变量与互斥锁 示例生产者--消费者问题

http://blog.csdn.net/jnu_simba/article/details/9129939 一、posix 条件变量 一种线程间同步的情形&#xff1a;线程A需要等某个条件成立才能继续往下执行&#xff0c;现在这个条件不成立&#xff0c;线程A就阻塞等待&#xff0c;而线程B在执行过程中使这个条件成立了&#x…

3-V2-类和对象 -- const内联 静态成员 友元

const修饰成员函数 在成员函数后面加一个const, const修饰this指针指向的对象, 保证调用这个const成员函数的对象在函数内不会被改变 注意:成员函数如果不修改成员变量,则加上const,成员函数如果要修改成员变量,此时就不能给其加上const修饰了 1.const对象不能调用非const…

C语言 二级指针内存模型混合实战

http://www.cnblogs.com/zhanggaofeng/p/5485833.html //二级指针内存模型混合实战 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h>//将内存模型①和内存模型②的数据拷贝到内存模型③ char ** threemodel(ch…

Linux 网络编程八(epoll应用--大并发处理)

http://www.cnblogs.com/zhanggaofeng/p/5901316.html //头文件 pub.h #ifndef _vsucess#define _vsucess#ifdef __cplusplus extern "C" {#endif //服务器创建socket int server_socket(int port);//设置非阻塞 int setnonblock(int st);//接收客户端socket int ser…

【数据结构与算法】内部排序之三:堆排序(含完整源码)

转载请注明出处&#xff1a;http://blog.csdn.net/ns_code/article/details/20227303 前言 堆排序、快速排序、归并排序&#xff08;下篇会写这两种排序算法&#xff09;的平均时间复杂度都为O&#xff08;n*logn&#xff09;。要弄清楚堆排序&#xff0c;就要先了解下二叉堆这…

模线性方程(中国剩余定理+扩展中国剩余定理)

已知一系列除数和模数,求最小的满足条件的数 我们先考虑一般的情况&#xff0c;即模数不互质。&#xff08;扩展中国剩余定理&#xff09; 我们考虑两个方程的情况 x%MR xk1∗MRxk1 * MRxk1∗MR x%mr xk2∗mrxk2 * mrxk2∗mr 所以k1∗MRk2∗mrk1 * MRk2 * mrk1∗MRk2∗mr 即…

【数据结构】(面试题)使用两个栈实现一个队列(详细介绍)

http://blog.csdn.net/hanjing_1995/article/details/51539578 使用两个栈实现一个队列 思路一&#xff1a; 我们设定s1是入栈的&#xff0c;s2是出栈的。 入队列&#xff0c;直接压到s1即可 出队列&#xff0c;先把s1中的元素倒入到s2中&#xff0c;弹出s2中的栈顶元素&#x…