TCP/IP网络编程 --优于select的epoll

网络 网络管理
关于并发服务器中的I/O复用实现方式,前面我们讲过select的方式,但select的性能比较低,并不适合以Web服务器端开发为主流的现代开发环境。因此就有了Linux下的epoll,BSD的kqueue,Solaris的/dev/poll和Windows的IOCP等复用技术。本章就来讲讲Linux下的epoll技术。

关于并发服务器中的I/O复用实现方式,前面我们讲过select的方式,但select的性能比较低,并不适合以Web服务器端开发为主流的现代开发环境。因此就有了Linux下的epoll,BSD的kqueue,Solaris的/dev/poll和Windows的IOCP等复用技术。本章就来讲讲Linux下的epoll技术。

epoll理解及应用

基于select的I/O复用技术速度慢的原因:

1,调用select函数后常见的针对所有文件描述符的循环语句。它每次事件发生需要遍历所有文件描述符,找出发生变化的文件描述符。(以前写的示例没加循环)

2,每次调用select函数时都需要向该函数传递监视对象信息。即每次调用select函数时向操作系统传递监视对象信息,至于为什么要传?是因为我们监视的套接字变化的函数,而套接字是操作系统管理的。(这个才是最耗效率的)

注释:基于这样的原因并不是说select就没用了,在这样的情况下就适合选用select:1,服务端接入者少 2,程序应具有兼容性。

epoll是怎么优化select问题的:

1,每次发生事件它不需要循环遍历所有文件描述符,它把发生变化的文件描述符单独集中到了一起。

2,仅向操作系统传递1次监视对象信息,监视范围或内容发生变化时只通知发生变化的事项。

实现epoll时必要的函数和结构体

  1. 函数: 
  2.  
  3. epoll_create:创建保存epoll文件描述符的空间,该函数也会返回文件描述符,所以终止时,也要调用close函数。(创建内存空间) 
  4.  
  5. epoll_ctl:向空间注册,添加或修改文件描述符。(注册监听事件) 
  6.  
  7. epoll_wait:与select函数类似,等待文件描述符发生变化。(监听事件回调) 
  8.  
  9. 结构体: 
  10.  
  11. struct epoll_event  
  12.   
  13. {  
  14.   
  15. __uint32_t events;  
  16.   
  17. epoll_data_t data;  
  18.   
  19. }  
  20.   
  21. typedef union epoll_data  
  22.   
  23. {  
  24.   
  25. void *ptr;  
  26.   
  27. int fd;  
  28.   
  29. __uinit32_t u32;  
  30.   
  31. __uint64_t u64;  
  32.   
  33. } epoll_data_t;  

基于epoll的回声服务器端

  1. // 
  2.  
  3. // main.cpp 
  4.  
  5. // hello_server 
  6.  
  7. // 
  8.  
  9. // Created by app05 on 15-10-19. 
  10.  
  11. // Copyright (c) 2015年 app05. All rights reserved. 
  12.  
  13. // 
  14.  
  15. #include 
  16.  
  17. #include 
  18.  
  19. #include 
  20.  
  21. #include 
  22.  
  23. #include 
  24.  
  25. #include 
  26.  
  27. #include 
  28.  
  29. #define BUF_SIZE 100 
  30.  
  31. #define EPOLL_SIZE 50 
  32.  
  33. void error_handling(char *buf); 
  34.  
  35. int main(int argc, const char * argv[]) { 
  36.  
  37. int serv_sock, clnt_sock; 
  38.  
  39. struct sockaddr_in serv_adr, clnt_adr; 
  40.  
  41. socklen_t adr_sz; 
  42.  
  43. int str_len, i; 
  44.  
  45. char buf[BUF_SIZE]; 
  46.  
  47. //类似select的fd_set变量查看监视对象的状态变化,epoll_event结构体将发生变化的文件描述符单独集中到一起 
  48.  
  49. struct epoll_event *ep_events; 
  50.  
  51. struct epoll_event event; 
  52.  
  53. int epfd, event_cnt; 
  54.  
  55. if(argc != 2) 
  56.  
  57.  
  58. printf("Usage: %s \n", argv[0]); 
  59.  
  60. exit(1); 
  61.  
  62.  
  63. serv_sock = socket(PF_INET, SOCK_STREAM, 0); 
  64.  
  65. if(serv_sock == -1) 
  66.  
  67. error_handling("socket() error"); 
  68.  
  69. memset(&serv_adr, 0, sizeof(serv_adr)); 
  70.  
  71. serv_adr.sin_family = AF_INET; 
  72.  
  73. serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); 
  74.  
  75. serv_adr.sin_port = htons(atoi(argv[1])); 
  76.  
  77. if(bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1) 
  78.  
  79. error_handling("bind() error"); 
  80.  
  81. if(listen(serv_sock, 5) == -1) 
  82.  
  83. error_handling("listen() error"); 
  84.  
  85. //创建文件描述符的保存空间称为“epoll例程” 
  86.  
  87. epfd = epoll_create(EPOLL_SIZE); 
  88.  
  89. ep_events = malloc(sizeof(struct epoll_event) *EPOLL_SIZE); 
  90.  
  91. //添加读取事件的监视(注册事件) 
  92.  
  93. event.events = EPOLLIN; //读取数据事件 
  94.  
  95. event.data.fd = serv_sock; 
  96.  
  97. epoll_ctl(epdf, EPOLL_CTL_ADD, serv_sock, &event); 
  98.  
  99. while (1) 
  100.  
  101.  
  102. //响应事件,返回发生事件的文件描述符数 
  103.  
  104. event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); //传-1时,一直等待直到事件发生 
  105.  
  106. if(event_cnt == -1) 
  107.  
  108.  
  109. puts("epoll_wait() error"); 
  110.  
  111. break
  112.  
  113.  
  114. //服务端套接字和客服端套接字 
  115.  
  116. for (i = 0; i < event_cnt; i++) { 
  117.  
  118. if(ep_events[i].data.fd == serv_sock)//服务端与客服端建立连接 
  119.  
  120.  
  121. adr_sz = sizeof(clnt_adr); 
  122.  
  123. clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz); 
  124.  
  125. event.events = EPOLLIN; 
  126.  
  127. event.data.fd = clnt_sock; 
  128.  
  129. epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); 
  130.  
  131. printf("connected client: %d \n", clnt_sock); 
  132.  
  133.  
  134. else //连接之后传递数据 
  135.  
  136.  
  137. str_len = read(ep_events[i].data.fd, buf, BUF_SIZE); 
  138.  
  139. if(str_len == 0) 
  140.  
  141.  
  142. //删除事件 
  143.  
  144. epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); 
  145.  
  146. close(ep_events[i].data.fd); 
  147.  
  148. printf("closed client: %d \n", ep_events[i].data.fd); 
  149.  
  150.  
  151. else 
  152.  
  153.  
  154. write(ep_events[i].data.fd, buf, str_len); 
  155.  
  156.  
  157.  
  158.  
  159.  
  160. close(serv_sock); 
  161.  
  162. close(epfd); 
  163.  
  164. return 0; 
  165.  
  166.  
  167. void error_handling(char *message) 
  168.  
  169.  
  170. fputs(message, stderr); 
  171.  
  172. fputc('\n', stderr); 
  173.  
  174. exit(1); 
  175.  
  176. }

条件触发和边缘触发

什么是条件触发和边缘触发?它们是指事件响应的方式,epoll默认是条件触发的方式。条件触发是指:只要输入缓冲中有数据就会一直通知该事件,循环响应epoll_wait。而边缘触发是指:输入缓冲收到数据时仅注册1次该事件,即使输入缓冲中还留有数据,也不会再进行注册,只响应一次。

边缘触发相对条件触发的优点:可以分离接收数据和处理数据的时间点,从实现模型的角度看,边缘触发更有可能带来高性能。

将上面epoll实例改为边缘触发:

1,首先改写 event.events = EPOLLIN | EPOLLET; (EPOLLIN:读取数据事件 EPOLLET:边缘触发方式)

2,边缘触发只响应一次接收数据事件,所以要一次性全部读取输入缓冲中的数据,那么就需要判断什么时候数据读取完了?Linux声明了一个全局的变量:int errno; (error.h中),它能记录发生错误时提供额外的信息。这里就可以用它来判断是否读取完数据:

  1. str_len = read(...); 
  2.  
  3. if(str_len < 0) 
  4.  
  5.  
  6. if(errno == EAGAIN) //读取输入缓冲中的全部数据的标志 
  7.  
  8. break
  9.  
  10. }

3,边缘触发方式下,以阻塞方式工作的read&write有可能会引起服务端的长时间停顿。所以边缘触发一定要采用非阻塞的套接字数据传输形式。那么怎么将套接字的read,write数据传输形式修改为非阻塞模式呢?

//fd套接字文件描述符,将此套接字数据传输模式修改为非阻塞

  1. void setnonblockingmode(int fd) 
  2.  
  3.  
  4. int flag = fcntl(fd, F_GETFL,0); //得到套接字原来属性 
  5.  
  6. fcntl(fd, F_SETFL, flag | O_NONBLOCK);//在原有属性基础上设置添加非阻塞模式 
  7.  
责任编辑:何妍 来源: CSDN博客
相关推荐

2019-09-18 20:07:06

AndroidTCP协议

2015-04-24 09:48:59

TCPsocketsocket编程

2019-11-08 14:47:49

TCPIP网络

2015-10-19 09:34:42

TCPIP网络协议

2015-10-16 09:33:26

TCPIP网络协议

2010-09-09 16:28:19

2009-04-09 10:11:00

TCPIP通讯

2019-04-08 08:44:10

TCPIP网络协议

2015-10-27 09:40:31

TCPIP网络协议

2010-09-09 16:21:32

TCP IP网络协议

2012-09-24 15:13:50

C#网络协议TCP

2023-06-01 07:55:56

TCP/IP网络模型

2021-05-31 06:50:47

SelectPoll系统

2013-10-28 09:24:34

SDN软件定义网络TCP

2022-02-22 08:55:29

SelectPoll/ Epoll

2012-12-03 11:50:44

TCPIP网络流量

2022-10-08 00:00:00

DNS地址网关

2009-01-18 09:28:00

TCPIP路由器

2019-07-16 11:06:09

TCP四次挥手半关闭

2021-03-17 09:51:31

网络编程TCP网络协议
点赞
收藏

51CTO技术栈公众号