`
king_c
  • 浏览: 223754 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Linux下select, poll和epoll IO模型的详解

 
阅读更多

一).Epoll 介绍

Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 ,并没有什么神秘的。其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 PPC ), TPC ( Thread Per Connection )模型,以及 select 模型和 poll 模型,那为何还要再引入 Epoll 这个东东呢?那还是有得说说的 …

二). 常用模型的缺点

如果不摆出来其他模型的缺点,怎么能对比出 Epoll 的优点呢。

① PPC/TPC 模型

这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我 。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程 / 线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

② select 模型

1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 …

2. 效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了。

3. 内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法。

总结为:1.连接数受限  2.查找配对速度慢 3.数据由内核拷贝到用户态

③ poll 模型

基本上效率和 select 是相同的, select 缺点的 2 和 3 它都没有改掉。

三). Epoll 的提升

把其他模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来那就是 Epoll 的优点了。

①. Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。

②. 效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。

③. 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。

 四). Epoll 为什么高效

Epoll 的高效和其数据结构的设计是密不可分的,这个下面就会提到。

首先回忆一下 select 模型,当有 I/O 事件到来时, select 通知应用程序有事件到了快去处理,而应用程序必须轮询所有的 FD 集合,测试每个 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 不仅会告诉应用程序有I/0 事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个FD 集合。

 

 

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

五). Epoll 关键数据结构

 

前面提到 Epoll 速度快和其数据结构密不可分,其关键数据结构就是:

 

[cpp] view plaincopy
 
  1. struct epoll_event {  
  2.     __uint32_t events;      // Epoll events  
  3.     epoll_data_t data;      // User data variable  
  4. };  
  5. typedef union epoll_data {  
  6.     void *ptr;  
  7.     int fd;  
  8.     __uint32_t u32;  
  9.     __uint64_t u64;  
  10. } epoll_data_t;  

可见 epoll_data 是一个 union 结构体 , 借助于它应用程序可以保存很多类型的信息 :fd 、指针等等。有了它,应用程序就可以直接定位目标了。

 

六). 使用 Epoll

既然 Epoll 相比 select 这么好,那么用起来如何呢?会不会很繁琐啊 … 先看看下面的三个函数吧,就知道 Epoll 的易用了。 

int epoll_create(int size);  

生成一个 Epoll 专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的 socket fd 上是否发生以及发生了什么事件。 size 就是你在这个 Epoll fd 上能关注的最大 socket fd 数,大小自定,只要内存足够。

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

控制某个 Epoll 文件描述符上的事件:注册、修改、删除。其中参数 epfd 是 epoll_create() 创建 Epoll 专用的文件描述符。相对于 select 模型中的 FD_SET 和 FD_CLR 宏。

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

 

等待 I/O 事件的发生;参数说明:

epfd: 由 epoll_create() 生成的 Epoll 专用的文件描述符;

epoll_event: 用于回传代处理事件的数组;

maxevents: 每次能处理的事件数;

timeout: 等待 I/O 事件发生的超时值;

返回发生事件数。

相对于 select 模型中的 select 函数。

七). 例子程序

下面是一个简单 Echo Server 的例子程序,麻雀虽小,五脏俱全,还包含了一个简单的超时检查机制,简洁起见没有做错误处理。

 

[cpp] view plaincopy
 
  1. //   
  2. // a simple echo server using epoll in linux  
  3. //   
  4. // 2009-11-05  
  5. // by sparkling  
  6. //   
  7. #include <sys/socket.h>  
  8. #include <sys/epoll.h>  
  9. #include <netinet/in.h>  
  10. #include <arpa/inet.h>  
  11. #include <fcntl.h>  
  12. #include <unistd.h>  
  13. #include <stdio.h>  
  14. #include <errno.h>  
  15. #include <iostream>  
  16. using namespace std;  
  17. #define MAX_EVENTS 500  
  18. struct myevent_s  
  19. {  
  20.     int fd;  
  21.     void (*call_back)(int fd, int events, void *arg);  
  22.     int events;  
  23.     void *arg;  
  24.     int status; // 1: in epoll wait list, 0 not in  
  25.     char buff[128]; // recv data buffer  
  26.     int len;  
  27.     long last_active; // last active time  
  28. };  
  29. // set event  
  30. void EventSet(myevent_s *ev, int fd, void (*call_back)(int, int, void*), void *arg)  
  31. {  
  32.     ev->fd = fd;  
  33.     ev->call_back = call_back;  
  34.     ev->events = 0;  
  35.     ev->arg = arg;  
  36.     ev->status = 0;  
  37.     ev->last_active = time(NULL);  
  38. }  
  39. // add/mod an event to epoll  
  40. void EventAdd(int epollFd, int events, myevent_s *ev)  
  41. {  
  42.     struct epoll_event epv = {0, {0}};  
  43.     int op;  
  44.     epv.data.ptr = ev;  
  45.     epv.events = ev->events = events;  
  46.     if(ev->status == 1){  
  47.         op = EPOLL_CTL_MOD;  
  48.     }  
  49.     else{  
  50.         op = EPOLL_CTL_ADD;  
  51.         ev->status = 1;  
  52.     }  
  53.     if(epoll_ctl(epollFd, op, ev->fd, &epv) < 0)  
  54.         printf("Event Add failed[fd=%d]/n", ev->fd);  
  55.     else  
  56.         printf("Event Add OK[fd=%d]/n", ev->fd);  
  57. }  
  58. // delete an event from epoll  
  59. void EventDel(int epollFd, myevent_s *ev)  
  60. {  
  61.     struct epoll_event epv = {0, {0}};  
  62.     if(ev->status != 1) return;  
  63.     epv.data.ptr = ev;  
  64.     ev->status = 0;  
  65.     epoll_ctl(epollFd, EPOLL_CTL_DEL, ev->fd, &epv);  
  66. }  
  67. int g_epollFd;  
  68. myevent_s g_Events[MAX_EVENTS+1]; // g_Events[MAX_EVENTS] is used by listen fd  
  69. void RecvData(int fd, int events, void *arg);  
  70. void SendData(int fd, int events, void *arg);  
  71. // accept new connections from clients  
  72. void AcceptConn(int fd, int events, void *arg)  
  73. {  
  74.     struct sockaddr_in sin;  
  75.     socklen_t len = sizeof(struct sockaddr_in);  
  76.     int nfd, i;  
  77.     // accept  
  78.     if((nfd = accept(fd, (struct sockaddr*)&sin, &len)) == -1)  
  79.     {  
  80.         if(errno != EAGAIN && errno != EINTR)  
  81.         {  
  82.             printf("%s: bad accept", __func__);  
  83.         }  
  84.         return;  
  85.     }  
  86.     do  
  87.     {  
  88.         for(i = 0; i < MAX_EVENTS; i++)  
  89.         {  
  90.             if(g_Events[i].status == 0)  
  91.             {  
  92.                 break;  
  93.             }  
  94.         }  
  95.         if(i == MAX_EVENTS)  
  96.         {  
  97.             printf("%s:max connection limit[%d].", __func__, MAX_EVENTS);  
  98.             break;  
  99.         }  
  100.         // set nonblocking  
  101.         if(fcntl(nfd, F_SETFL, O_NONBLOCK) < 0) break;  
  102.         // add a read event for receive data  
  103.         EventSet(&g_Events[i], nfd, RecvData, &g_Events[i]);  
  104.         EventAdd(g_epollFd, EPOLLIN|EPOLLET, &g_Events[i]);  
  105.         printf("new conn[%s:%d][time:%d]/n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), g_Events[i].last_active);  
  106.     }while(0);  
  107. }  
  108. // receive data  
  109. void RecvData(int fd, int events, void *arg)  
  110. {  
  111.     struct myevent_s *ev = (struct myevent_s*)arg;  
  112.     int len;  
  113.     // receive data  
  114.     len = recv(fd, ev->buff, sizeof(ev->buff)-1, 0);    
  115.     EventDel(g_epollFd, ev);  
  116.     if(len > 0)  
  117.     {  
  118.         ev->len = len;  
  119.         ev->buff[len] = '/0';  
  120.         printf("C[%d]:%s/n", fd, ev->buff);  
  121.         // change to send event  
  122.         EventSet(ev, fd, SendData, ev);  
  123.         EventAdd(g_epollFd, EPOLLOUT|EPOLLET, ev);  
  124.     }  
  125.     else if(len == 0)  
  126.     {  
  127.         close(ev->fd);  
  128.         printf("[fd=%d] closed gracefully./n", fd);  
  129.     }  
  130.     else  
  131.     {  
  132.         close(ev->fd);  
  133.         printf("recv[fd=%d] error[%d]:%s/n", fd, errno, strerror(errno));  
  134.     }  
  135. }  
  136. // send data  
  137. void SendData(int fd, int events, void *arg)  
  138. {  
  139.     struct myevent_s *ev = (struct myevent_s*)arg;  
  140.     int len;  
  141.     // send data  
  142.     len = send(fd, ev->buff, ev->len, 0);  
  143.     ev->len = 0;  
  144.     EventDel(g_epollFd, ev);  
  145.     if(len > 0)  
  146.     {  
  147.         // change to receive event  
  148.         EventSet(ev, fd, RecvData, ev);  
  149.         EventAdd(g_epollFd, EPOLLIN|EPOLLET, ev);  
  150.     }  
  151.     else  
  152.     {  
  153.         close(ev->fd);  
  154.         printf("recv[fd=%d] error[%d]/n", fd, errno);  
  155.     }  
  156. }  
  157. void InitListenSocket(int epollFd, short port)  
  158. {  
  159.     int listenFd = socket(AF_INET, SOCK_STREAM, 0);  
  160.     fcntl(listenFd, F_SETFL, O_NONBLOCK); // set non-blocking  
  161.     printf("server listen fd=%d/n", listenFd);  
  162.     EventSet(&g_Events[MAX_EVENTS], listenFd, AcceptConn, &g_Events[MAX_EVENTS]);  
  163.     // add listen socket  
  164.     EventAdd(epollFd, EPOLLIN|EPOLLET, &g_Events[MAX_EVENTS]);  
  165.     // bind & listen  
  166.     sockaddr_in sin;  
  167.     bzero(&sin, sizeof(sin));  
  168.     sin.sin_family = AF_INET;  
  169.     sin.sin_addr.s_addr = INADDR_ANY;  
  170.     sin.sin_port = htons(port);  
  171.     bind(listenFd, (const sockaddr*)&sin, sizeof(sin));  
  172.     listen(listenFd, 5);  
  173. }  
  174. int main(int argc, char **argv)  
  175. {  
  176.     short port = 12345; // default port  
  177.     if(argc == 2){  
  178.         port = atoi(argv[1]);  
  179.     }  
  180.     // create epoll  
  181.     g_epollFd = epoll_create(MAX_EVENTS);  
  182.     if(
  183. g_epollFd <= 0) printf("create epoll failed.%d/n", g_epollFd);  
  184.     // create & bind listen socket, and add to epoll, set non-blocking  
  185.     InitListenSocket(g_epollFd, port);  
  186.     // event loop  
  187.     struct epoll_event events[MAX_EVENTS];  
  188.     printf("server running:port[%d]/n", port);  
  189.     int checkPos = 0;  
  190.     while(1){  
  191.         // a simple timeout check here, every time 100, better to use a mini-heap, and add timer event  
  192.         long now = time(NULL);  
  193.         for(int i = 0; i < 100; i++, checkPos++) // doesn't check listen fd  
  194.         {  
  195.             if(checkPos == MAX_EVENTS) checkPos = 0; // recycle  
  196.             if(g_Events[checkPos].status != 1) continue;  
  197.             long duration = now - g_Events[checkPos].last_active;  
  198.             if(duration >= 60) // 60s timeout  
  199.             {  
  200.                 close(g_Events[checkPos].fd);  
  201.                 printf("[fd=%d] timeout[%d--%d]./n", g_Events[checkPos].fd, g_Events[checkPos].last_active, now);  
  202.                 EventDel(g_epollFd, &g_Events[checkPos]);  
  203.             }  
  204.         }  
  205.         // wait for events to happen  
  206.         int fds = epoll_wait(g_epollFd, events, MAX_EVENTS, 1000);  
  207.         if(fds < 0){  
  208.             printf("epoll_wait error, exit/n");  
  209.             break;  
  210.         }  
  211.         for(int i = 0; i < fds; i++){  
  212.             myevent_s *ev = (struct myevent_s*)events[i].data.ptr;  
  213.             if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN)) // read event  
  214.             {  
  215.                 ev->call_back(ev->fd, events[i].events, ev->arg);  
  216.             }  
  217.             if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT)) // write event  
  218.             {  
  219.                 ev->call_back(ev->fd, events[i].events, ev->arg);  
  220.             }  
  221.         }  
  222.     }  
  223.     // free resource  
  224.     return 0;  
  225. }  

Epoll的优点:
1.支持一个进程打开大数目的socket描述符(FD)
    select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

2.IO效率不随FD数目增加而线性下降
    传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

3.使用mmap加速内核与用户空间的消息传递。
    这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。

4.内核微调
    这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。

 

########################################################

 

select/epoll的特点

 

select的特点:select 选择句柄的时候,是遍历所有句柄,也就是说句柄有事件响应时,select需要遍历所有句柄才能获取到哪些句柄有事件通知,因此效率是非常低。但是如果连接很少的情况下, select和epoll的LT触发模式相比, 性能上差别不大。
这 里要多说一句,select支持的句柄数是有限制的, 同时只支持1024个,这个是句柄集合限制的,如果超过这个限制,很可能导致溢出,而且非常不容易发现问题, TAF就出现过这个问题, 调试了n天,才发现:)当然可以通过修改linux的socket内核调整这个参数。
epoll的特点:epoll对于句柄事件的选择不是遍历的,是事件响应的,就是句柄上事件来就马上选择出来,不需要遍历整个句柄链表,因此效率非常高,内核将句柄用红黑树保存的。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE    1024
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。


对于epoll而言还有ET和LT的区别,LT表示水平触发,ET表示边缘触发,两者在性能以及代码实现上差别也是非常大的。

epoll的LT和ET的区别

LT:水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。
ET:边缘触发,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。

epoll相关API

epoll的接口非常简单,一共就三个函数:

1. int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

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

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;


struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};

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

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

等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

 

示例代码

epoll服务器

 

  1. #include <sys/epoll.h>  
  2. #include <netinet/in.h>  
  3. #include <sys/types.h>          /* See NOTES */  
  4. #include <sys/socket.h>  
  5. #include <string.h>  
  6. #include <stdio.h>  
  7. #include <unistd.h>  
  8. #include <fcntl.h>  
  9.   
  10. #include <errno.h>  
  11. #include <stdlib.h>  
  12. typedef struct sockaddr_in sockaddr_in ;  
  13. typedef struct sockaddr     sockaddr ;  
  14.   
  15. #define SER_PORT    8080  
  16.   
  17. int nonblock(int fd){  
  18.     int opt ;  
  19.     opt = fcntl(fd,F_GETFL);  
  20.     opt |= O_NONBLOCK ;  
  21.     return fcntl(fd,F_SETFL,opt);  
  22. }  
  23.   
  24. int main(int argc,char**argv){  
  25.     sockaddr_in srv, cli ;  
  26.     int listen_fd ,con_fd ;  
  27.     socklen_t  len;  
  28.     int res ,nsize,ws;  
  29.     char buf[255];  
  30.   
  31.     int epfd,ers;  
  32.     struct epoll_event evn,events[50];  
  33.     int i;  
  34.   
  35.     bzero(&srv,sizeof(srv));  
  36.     bzero(&cli,sizeof(cli));  
  37.     srv.sin_port= SER_PORT ;  
  38.     srv.sin_family = AF_INET ;  
  39.     listen_fd = socket(AF_INET,SOCK_STREAM,0);  
  40.   
  41.     int yes = 1;  
  42.     setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int));  
  43.   
  44.     if(bind(listen_fd,(sockaddr*)&srv,sizeof(sockaddr))<0)  {  
  45.         perror("bind");  
  46.         exit(0);  
  47.     }  
  48.     listen(listen_fd,100);  
  49.     nonblock(listen_fd);  
  50.     epfd = epoll_create(200);  
  51.     evn.events = EPOLLIN|EPOLLET ;  
  52.     evn.data.fd = listen_fd;   
  53.     epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&evn);  
  54.     static int count ;  
  55.     while(1){  
  56.         ers = epoll_wait(epfd,events,100,5000);  
  57.         if(ers<0 ){  
  58.             perror("epoll_wait:");exit(0);  
  59.         }else if(ers==0){  
  60.             printf("time out:%d\n",count++);  
  61.             continue ;  
  62.         }  
  63.         for(i=0;i<ers;i++){  
  64.             if(events[i].data.fd == listen_fd){  
  65.                 con_fd = accept(listen_fd,(sockaddr*)&cli ,&len);  
  66.                 nonblock(con_fd);  
  67.                 printf("connect from:%s\n",inet_ntoa(cli.sin_addr));  
  68.                 evn.data.fd = con_fd;  
  69.                 evn.events = EPOLLIN | EPOLLET ;  
  70.                 epoll_ctl(epfd,EPOLL_CTL_ADD,con_fd,&evn);  
  71.   
  72.             }else if(events[i].events & EPOLLIN){     
  73.                   
  74.                 nsize = 0;  
  75.                 while((res=read(events[i].data.fd,buf+nsize,sizeof(buf)-1))>0){  
  76.                     nsize+= res;  
  77.                 }  
  78.                 if(res==0){  
  79.                     epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);     
  80.                     printf("a client over\n");  
  81.                     close(con_fd);  
  82.                     continue ;  
  83.                 }else if(res<0 && errno!=EAGAIN){  
  84.                     perror("read");  
  85.                     continue ;  
  86.                 }  
  87.                 buf[nsize]=0;  
  88.                 evn.data.fd = events[i].data.fd;  
  89.                 evn.events=EPOLLOUT|EPOLLET ;  
  90.                 epoll_ctl(epfd,EPOLL_CTL_MOD,events[i].data.fd,&evn);                 
  91.                   
  92.             }else if(events[i].events & EPOLLOUT){  
  93.                 nsize = strlen(buf);  
  94.                 ws = 0;  
  95.                 while(nsize>0){  
  96.                      ws=write(events[i].data.fd,buf,nsize);  
  97.                     nsize-=ws;  
  98.                 }  
  99.                 evn.data.fd = events[i].data.fd;  
  100.                 evn.events=EPOLLIN|EPOLLET ;  
  101.                 epoll_ctl(epfd,EPOLL_CTL_MOD,events[i].data.fd,&evn);     
  102.             }else{  
  103.                 printf("others\n");  
  104.                   
  105.             }             
  106.         }  
  107.   
  108.     }  
  109.   
  110.     close(listen_fd);  
  111.       
  112.     return 0;  
  113. }  

 

客户端测试代码:

 

 

 

  1. #include <sys/epoll.h>  
  2. #include <netinet/in.h>  
  3. #include <sys/types.h>          /* See NOTES */  
  4. #include <sys/socket.h>  
  5. #include <strings.h>  
  6. #include <stdio.h>  
  7. #include <stdlib.h>  
  8. #include <unistd.h>  
  9. typedef struct sockaddr_in sockaddr_in ;  
  10. typedef struct sockaddr     sockaddr ;  
  11.   
  12. #define SER_PORT    8080  
  13. #define IP_ADDR     "10.33.28.230"  
  14.   
  15. int main(int argc,char**argv){  
  16.     sockaddr_in srv, cli ;  
  17.     int listen_fd ,con_fd ;  
  18.     socklen_t  len;  
  19.     int res,ws ;  
  20.     char buf[255];  
  21.   
  22.     bzero(&srv,sizeof(srv));  
  23.     bzero(&cli,sizeof(cli));  
  24.     srv.sin_port= SER_PORT ;  
  25.     srv.sin_family = AF_INET ;  
  26.     inet_pton(AF_INET,IP_ADDR,&srv.sin_addr);  
  27.   
  28.     listen_fd = socket(AF_INET,SOCK_STREAM,0);  
  29.     if(connect(listen_fd,(sockaddr*)&srv,sizeof(sockaddr))<0){  
  30.         perror("connect");  
  31.         exit(0);  
  32.     }  
  33.     while(1){  
  34.         res = read(STDIN_FILENO,buf,sizeof(buf)-1);  
  35.         ws = write(listen_fd,buf,res);  
  36.         res = read(listen_fd,buf,sizeof(buf)-1);  
  37.         ws = write(STDOUT_FILENO,buf,res);  
  38.     }  
  39.   
  40.     close(listen_fd);  
  41.       
  42.     return 0;  
  43. }  

 

分享到:
评论

相关推荐

    IO多路复用之select_poll_epoll详解1

    【网络通信】IO多路复用技术是一种高效处理并发I/O操作的方法,它允许一个进程同时监控多个I/O事件,...但在跨平台需求和小规模并发场景下,select和poll也有其适用价值。选择哪种方法取决于具体的应用场景和性能需求。

    LinuxIO模式及select、poll、epoll详解

    看到这篇文章说明你已经从老版本升级到 Ubuntu16.04或进行了全新安装,在安装好Ubuntu...同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的。所以先限定一下本

    EPOLL模型详解

    EPOLL对比传统的select和poll模型,具有以下显著优点: 1. **不受FD数量限制**: select模型的一个关键限制是其FD_SETSIZE宏定义的最大句柄数,通常是2048。而EPOLL则没有这样的限制,它可以处理的FD上限取决于...

    linux epoll机制详解

    select()和poll() IO多路复用模型 select的缺点: 1.单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差...

    IO多路复用之epoll实例

    在Linux系统中,epoll是实现IO多路复用的一种高效方法,它克服了早期的poll和select方法在处理大量并发连接时性能下降的问题。 epoll的核心概念包括: 1. **Epoll结构**:epoll_create()函数创建一个epoll实例,...

    Linux中select函数使用详解.zip_Linux中select函数使用详解

    总结来说,`select`函数是Linux下多路IO编程的基础,虽然在处理大量并发时效率较低,但在理解和学习多路复用I/O时仍然有着重要的地位。通过合理使用,`select`可以在有限的资源下有效地处理多个I/O事件,是初学者...

    Linux 下的五种 IO 模型详细介绍

    接下来我们将详细介绍Linux下的五种IO模型:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO和异步IO。 **1. 阻塞IO (Blocking IO)** 阻塞IO是最简单的IO模型。当一个进程发起一个读或写的IO请求时,它会一直等待直到IO...

    linux c select(IO多工机制)

    ### Linux C Select (IO多工机制) #### 一、引言 ...然而,值得注意的是,虽然`select`功能强大,但在处理大量文件描述符时效率较低,现代操作系统中通常还会采用更高效的替代方案,如`poll`和`epoll`等。

    python 并发编程 多路复用IO模型详解

    我们都知道,select/epoll的好处就在于单个进程process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它...

    IO多路复用之select实例

    在Unix/Linux系统中,最常用的IO多路复用方法包括select、poll和epoll。本示例主要关注的是select函数的使用。 **一、select函数介绍** select函数是C语言中的一个标准库函数,定义在`&lt;sys/select.h&gt;`头文件中。它...

    多路IO复用并发服务器模型

    3. **I/O多路复用**:利用`select`、`poll`或`epoll`等函数,可以同时监控多个文件描述符的状态,当某个描述符准备好时,才会进行下一步操作。 4. **信号驱动I/O**:使用信号来通知进程I/O事件的发生,通常用于非...

    linux异步IO.pdf

    3. **异步阻塞I/O**:结合了非阻塞I/O和阻塞通知机制,如select/poll/epoll,用于检测I/O描述符的状态变化。虽然提供了多描述符通知的便利,但在高性能场景下效率不足。 4. **异步非阻塞I/O(AIO)**:真正实现了I/O...

    详细介绍IO管理器的一篇报告

    4. **多路复用IO**(如 select、poll、epoll 等):允许多个描述符在同一时间被监视,当有数据准备好时,系统通知应用程序。 四、IO管理器在不同操作系统中的实现 1. **Windows**:Windows的IO管理器主要基于完成...

    Linux高性能服务器编程,linux高性能服务器编程 pdf,C,C++

    3. **异步I/O模型**:深入理解各种I/O模型,如阻塞I/O、非阻塞I/O、IO复用(select、poll、epoll)、信号驱动I/O和异步I/O,以及它们在服务器编程中的应用场景和优缺点。 4. **网络编程**:详细讲解TCP/IP协议栈,...

    Linux网络编程之IO复用循环服务器

    ### Linux网络编程之IO复用循环服务器 #### 一、引言 在现代网络应用开发中,服务器的设计面临着越来越高的并发请求处理需求。传统的简单循环服务器每次只能处理一个客户端请求,这种方式显然无法满足高并发场景的...

    1、丰巢科技(51问)1

    此外,还提到了Java中的IO多路复用技术,如select、poll和epoll,以及ZooKeeper的工作原理和在分布式系统中的应用。 【知识点详解】: 1. **BIO(Blocking I/O)**: BIO是同步阻塞I/O模型,意味着在数据读取或...

    month02_IO多路复用方法对比.md

    `epoll` 是 Linux 内核提供的另一种高效的 IO 多路复用机制,它解决了 `select` 和 `poll` 的大部分问题,成为了现代 Linux 系统中首选的 IO 多路复用方法。`epoll` 的特点如下: - **平台支持度**:`epoll` 只在 ...

Global site tag (gtag.js) - Google Analytics