http://blog.csdn.net/chenxun_2010/article/details/50493481

1、epoll是什么?

epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll 在Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO multiplexing)技术

Linux下设计并发网络程序,常用的模型有:

Apache模型(Process Per Connection,简称PPC)

TPC(Thread PerConnection)模型

select模型和poll模型。

epoll模型

2、常用模型的缺点

 PPC/TPC模型

这两种模型思想类似,就是让每一个到来的连接都有一个进程/线程来服务。这种模型的代价是它要时间和空间。连接较多时,进程/线程切换的开销比较大。因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

select模型

最大并发数限制:因为一个进程所打开的fd(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此select模型的最大并发数就被相应限制了。

效率问题:select每次调用都会线性扫描全部的fd集合,这样效率就会呈现线性下降,把FD_SETSIZE改大可能造成这些fd都超时了。

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

poll模型

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

epoll的改进

对比其他模型的问题,epoll的改进如下:

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

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

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

3、 epoll为什么高效

epoll的高效和其数据结构的设计是密不可分的。

首先回忆一下select模型,当有I/O事件到来时,select通知应用程序有事件到了,应用程序必须轮询所有的fd集合,测试每个fd是否有事件发生,并处理事件;代码像下面这样:

int  res = select(maxfd+1, &readfds, NULL, NULL, 120);

if (res > 0)

{

for (int i = 0; i < MAX_CONNECTION; i++)

{

if (FD_ISSET(allConnection[i],&readfds))

{

handleEvent(allConnection[i]);

}

}

}

// if(res == 0) handle timeout, res < 0 handle error

epoll不仅会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。

int res = epoll_wait(epfd, events, 20, 120);

for (int i = 0; i < res;i++)

{

handleEvent(events[n]);

}

epoll关键数据结构

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

struct  epoll_event {

__uint32_t      events;  //epoll events

epoll_data_t   data;   //user data variable

};

typedef  union epoll_data {

void*   ptr;

int      fd;

__uint32_t  u32;

__uint64_t  u64;

}epoll_data_t;

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

使用epoll

epoll的API:

int  epoll_create(int  size);

int  epoll_ctl(int epfd, int op, int fd, structepoll_event *event);

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

int  epoll_create(int size);

创建一个epoll的文件描述符,参数size告诉内核这个监听的数目共有多大。

int  epoll_ctl(int epfd, int op, int fd, structepoll_event *event);

epoll的事件注册函数。

参数epfd是epoll_create返回值。

参数op为

EPOLL_CTL_ADD 注册新的fd到epfd中

EPOLL_CTL_MOD 修改已经注册的fd的监听事件

EPOLL_CTL_DEL 从epfd中删除一个fd

参数fd是需要监听文件描述符。

参数event是告诉内核需要监听什么事件。event->events的不同的值表示对应的文件描述符的不同事件:

EPOLLIN  可以读(包括对端Socket正常关闭)

EPOLLOUT 可以写

EPOLLPRI有紧急的数据可读(有带外数据OOB到来,TCP中的URG包)

EPOLLERR该文件描述符发生错误

EPOLLHUP该文件描述符被挂断

EPOLLET     将epoll设置为边缘触发(Edge Triggered)模式。

EPOLLONESHOT只监听一次事件,监听完之后,如果还想监听需要再次把该文件描述符加入到epoll队列中

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

等待事件的产生。

参数events用来从内核得到事件的集合

参数maxevents告之内核这个events有多大(maxevents不能大于size)

参数timeout是超时时间(毫秒)

epoll的模式:

LT模式:Level Triggered水平触发

这个是缺省的工作模式。同时支持block socket和non-block socket。内核会告诉程序员一个文件描述符是否就绪了。如果程序员不作任何操作,内核仍会通知。

ET模式:Edge Triggered 边缘触发

是一种高速模式。仅当状态发生变化的时候才获得通知。这种模式假定程序员在收到一次通知后能够完整地处理事件,于是内核不再通知这一事件。注意:缓冲区中还          有未处理的数据不算状态变化,所以ET模式下程序员只读取了一部分数据就再也得不到通知了,正确的用法是程序员自己确认读完了所有的字节(一直调用read/write直到          出错EAGAIN为止)。

一个例子:

[cpp] view plaincopy
  1. #include <netdb.h>
  2. #include <sys/socket.h>
  3. #include <sys/epoll.h>
  4. #include <netinet/in.h>
  5. #include <arpa/inet.h>
  6. #include <fcntl.h>
  7. #include <unistd.h>
  8. #include <stdio.h>
  9. #include <string.h>
  10. #include <stdlib.h>
  11. #include <errno.h>
  12. /*创建并绑定一个socket作为服务器。 */
  13. static int  create_and_bind (char *port){
  14. struct  addrinfo hints;
  15. struct  addrinfo *result, *rp;
  16. int  s, sfd;
  17. memset (&hints, 0, sizeof (struct addrinfo));
  18. hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
  19. hints.ai_socktype = SOCK_STREAM; /* 设置为STREAM模式,即TCP链接 */
  20. hints.ai_flags = AI_PASSIVE;     /* All interfaces */
  21. s = getaddrinfo (NULL, port, &hints, &result);//获得本地主机的地址
  22. if (s != 0){
  23. fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
  24. return -1;
  25. }
  26. for (rp = result; rp != NULL; rp = rp->ai_next){//本地主机地址可能有多个,任意绑定一个即可
  27. sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); //创建socket
  28. if (sfd == -1)
  29. continue;
  30. s = bind (sfd, rp->ai_addr, rp->ai_addrlen); //并绑定socket
  31. if (s == 0)
  32. {
  33. /* 绑定成功 */
  34. break;
  35. }
  36. close (sfd);
  37. }
  38. if (rp == NULL){
  39. fprintf (stderr, "Could not bind\n");
  40. return -1;
  41. }
  42. freeaddrinfo (result);
  43. return sfd;
  44. }
  45. /*
  46. 设置socket为非阻塞模式。
  47. 先get flag,或上O_NONBLOCK 再set flag。
  48. */
  49. static  int   make_socket_non_blocking (int sfd) {
  50. int flags, s;
  51. flags = fcntl (sfd, F_GETFL, 0);
  52. if (flags == -1){
  53. perror ("fcntl");
  54. return -1;
  55. }
  56. flags |= O_NONBLOCK;
  57. s = fcntl (sfd, F_SETFL, flags);
  58. if (s == -1){
  59. perror ("fcntl");
  60. return -1;
  61. }
  62. return 0;
  63. }
  64. #define  MAXEVENTS 64
  65. /*
  66. 用法: ./epoll_test 8080
  67. */
  68. int  main (int argc, char *argv[]) {
  69. int sfd, s;
  70. int efd;
  71. struct  epoll_event event;
  72. struct  epoll_event *events;
  73. if (argc != 2) {
  74. fprintf (stderr, "Usage: %s [port]\n", argv[0]);
  75. exit (EXIT_FAILURE);
  76. }
  77. sfd = create_and_bind (argv[1]); //sfd为绑定后等待连接接入的文件描述符
  78. s = make_socket_non_blocking (sfd);
  79. s = listen (sfd, SOMAXCONN);
  80. efd = epoll_create1 (0);
  81. event.data.fd = sfd;
  82. event.events = EPOLLIN | EPOLLET;
  83. s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);
  84. /* Buffer where events are returned,为events数组分配内存 */
  85. events = (struct  epoll_event*)calloc (MAXEVENTS, sizeof event);
  86. /* The event loop 事件循环*/
  87. while (1) {
  88. int n, i;
  89. n = epoll_wait (efd, events, MAXEVENTS, -1);
  90. for (i = 0; i < n; i++) {
  91. if ((events[i].events & EPOLLERR) ||  (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) {
  92. /* 发生了错误或者被挂断,或者没有数据可读  An error has occured on this fd, or the socket is not ready for reading (why were we notified then?) */
  93. fprintf (stderr, "epoll error\n");
  94. close (events[i].data.fd);
  95. continue;
  96. }else if (sfd == events[i].data.fd) {//新连接
  97. /* sfd上有数据可读,则表示有新连接
  98. * We have a notification on the listening socket,
  99. * which means one or more incoming connections. */
  100. printf("Incoming connection !\n");
  101. while (1) {
  102. struct sockaddr in_addr;
  103. socklen_t in_len;
  104. int infd;
  105. char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
  106. in_len = sizeof in_addr;
  107. infd = accept (sfd, &in_addr, &in_len); //读取到来的连接socket fd。
  108. if (infd == -1) {
  109. if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
  110. /* 已经读完了sfd上的所有数据(所有连接)。最后一次读(非阻塞读)会返回EAGAIN(=EWOULDBLOCK)
  111. * We have processed all incoming connections. */
  112. break;
  113. } else  {
  114. perror ("accept");
  115. break;
  116. }
  117. }
  118. s = getnameinfo (&in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV);
  119. if (s == 0) {
  120. printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", infd, hbuf, sbuf);
  121. }
  122. s = make_socket_non_blocking (infd);  //设置socket为非阻塞模式
  123. event.data.fd = infd;  //将data部分设置为fd
  124. event.events = EPOLLIN | EPOLLET;  //监听EPOLLIN事件,使用边缘触发模式
  125. s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
  126. }
  127. continue;
  128. } else
  129. {//有客户端发来数据
  130. /* 有客户端发来数据,因为处于ET模式,所以必须完全读取所有数据(要不然,剩下一部分数据后,就无法再收到内核通知了)。*/
  131. int conn = events[i].data.fd;
  132. if (conn < 0)
  133. continue;
  134. char recvbuf[1024] = {0};
  135. int ret = read(conn, recvbuf, 1024);
  136. if (ret == -1)
  137. //ERR_EXIT("readline");
  138. if (ret == 0)
  139. {
  140. printf("client close\n");
  141. close(conn);
  142. event = events[i];
  143. epoll_ctl(efd, EPOLL_CTL_DEL, conn, &event);
  144. //clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end());
  145. }
  146. fputs(recvbuf, stdout);
  147. write(conn, recvbuf, strlen(recvbuf));
  148. }
  149. }
  150. }
  151. free (events);//释放内存
  152. close (sfd);   //关闭sfd
  153. return EXIT_SUCCESS;
  154. }

epoll用法整理 实现回声服务端相关推荐

  1. 02回声服务端与客户端(瑕疵版)

    产生问题的原因是TCP不存在数据边界(粘包问题) client.c #include <stdio.h> #include <stdlib.h> #include <st ...

  2. 【亲测】回合制手游魔力宝贝【法兰城的回忆】最新整理Linux手工服务端+视频教程+GM授权后台

  3. 仙侠手游【一问多情/魔藏仙缘】最新整理Linux手工服务端+视频教程+GM授权后台+本地热新资源

  4. 服务端主动推送数据,除了 WebSocket 你还能想到啥?

    在上篇文章中,松哥和大家分享了 WebFlux 的基本用法,小伙伴们已经了解到使用 WebFlux 我们的返回值可以是 Mono 也可以是 Flux,如果是 Flux,由于 Flux 中包含多个元素, ...

  5. 计算机网络拓跋结构,实战 | 服务端开发与计算机网络结合的完美案例

    前言 大家好,我是阿秀 后端,可以说是仅次于算法岗之外竞争最为激烈的岗位,而其中的服务端开发也是很多人会选择在秋招中投递的一个岗位,我想对于很多人来说,走上服务端开发之路的起点就是一个回声服务器了. ...

  6. TCP/IP网络编程之多进程服务端(二)

    TCP/IP网络编程之多进程服务端(二) 信号处理 本章接上一章TCP/IP网络编程之多进程服务端(一),在上一章中,我们介绍了进程的创建和销毁,以及如何销毁僵尸进程.前面我们讲过,waitpid是非 ...

  7. TCP/IP网络编程之基于TCP的服务端/客户端(二)

    回声客户端问题 上一章TCP/IP网络编程之基于TCP的服务端/客户端(一)中,我们解释了回声客户端所存在的问题,那么单单是客户端的问题,服务端没有任何问题?是的,服务端没有问题,现在先让我们回顾下服 ...

  8. web服务端的架构演变

    此文已由作者肖凡授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 最近Lofter项目碰到很多性能上的问题,特别是数据库相关的,每次推送后,告警就会第一时间到来.这些问题随着产 ...

  9. flux服务器推消息,服务端主动推送数据,除了 WebSocket 你还能想到啥?

    原标题:服务端主动推送数据,除了 WebSocket 你还能想到啥? 来自公众号: 江南一点雨 在 上篇文章 中,松哥和大家分享了 WebFlux 的基本用法,小伙伴们已经了解到使用 WebFlux ...

最新文章

  1. 美妙的模电2013/4/18
  2. lofter 爬虫_200627 | Lofter格兰芬多标签日榜
  3. react 原生html 插件,纯原生JS的瀑布流插件Macy.js,前端必备插件
  4. onvirt安装linux系统
  5. JAVA元注解@interface详解(@Target,@Documented,@Retention,@Inherited)
  6. 内部服务器如何提供访问服务
  7. Netty : netty 4如何解决空轮询bug
  8. 《设计模式详解》软件设计原则
  9. 父子组件如何实现通信
  10. 求助动态贝叶斯网络参数学习函数的使用方法
  11. 客户需求分析8个维度_电商数据分析的4大思维和8个指标
  12. 南开大学校园邮箱pop3地址
  13. x86架构应用如何向Arm架构低成本迁移
  14. CSU 1337 费马大定理
  15. 网页打开速度慢的原因及N种解决方法
  16. NEO的至暗时刻 |链捕手
  17. html动画如何延迟,css3animation延迟
  18. Win7 X64 SQL SERVER 2000企业管理器无法建立新表
  19. 解决Adobe Acobat设置了背景色,显示出现白条的问题!
  20. 英语发音规则---Q字母

热门文章

  1. android使用bintray发布aar到jcenter
  2. poj 1862 Stripies/优先队列
  3. php 类静态变量 和 常量消耗内存及时间对比
  4. 程序开发基础学习四(boost::signal2 函数学习)
  5. COM, COM+ and .NET 的区别
  6. MVC学习笔记1 MVC概述
  7. php网页的注册界面设计,HTML开发博客之注册页面设计(一)
  8. 数据结构 算法与应用C 语言描述答案,数据结构算法与应用-C语言描述.pdf
  9. python import 类 继承_python学习之类的继承
  10. uvm 形式验证_这究竟属于下一代验证的方法、语言还是工具?||路科验证