• unix网络编程(三) 两种模式下的epoll服务端


    1.水平触发和边沿触发

    Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。

    Level Triggered (LT) 水平触发只要有数据都会触发。

    LT模式是默认的工作模式,在这种模式下epoll相当于一个效率较高的poll。使用ET模式需要在内核的事件表注册文件描述符的EPOLLET事件。

    2.水平触发下的epoll服务器

    编写epoll服务器的核心步骤:

    • 创建绑定监听IP和端口的套接字lfd
    • 创建内核事件表epfd
    • 注册lfd  到内核事件表epfd
    • 循环监听内核事件触发epoll_wait()
    • 触发事件的套接字连接分为监听套接字和连接套接字
    • 处理监听套接字(accept--->新连接注册到事件表)
    • 处理连接套接字(read读数据、处理数据)
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include "pub.h"
    8. #define OPENMAX 1024
    9. int main(int argc, char *argv[])
    10. {
    11. if(argc<3)
    12. {
    13. printf("usage: ./%s ip_address port_num\n", basename(argv[0]));
    14. return -1;
    15. }
    16. char *ip = argv[1];
    17. int port = atoi(argv[2]);
    18. int lfd = -1, cfd = -1, epfd = -1, nready=-1, ret = -1;
    19. // 绑定、监听
    20. lfd = socketBind(ip, port);
    21. if(-1 == lfd)
    22. return -1;
    23. epfd = epoll_create(OPENMAX);
    24. if(epfd < 0)
    25. {
    26. perror("epoll_create");
    27. return -1;
    28. }
    29. // 向epoll事件表注册lfd和lfd的事件(上树)
    30. struct epoll_event tmp_ev, client_ev[OPENMAX];
    31. memset(client_ev, 0, sizeof(client_ev));
    32. tmp_ev.events = EPOLLIN;
    33. tmp_ev.data.fd = lfd;
    34. ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tmp_ev);
    35. if(-1 == ret)
    36. {
    37. perror("epoll_ctl");
    38. return -1;
    39. }
    40. char buf[1024] = "";
    41. // 循环处理epoll事件
    42. while(1)
    43. {
    44. nready = epoll_wait(epfd, client_ev, OPENMAX, -1);
    45. if(-1 == nready)
    46. {
    47. perror("epoll_wait");
    48. break;
    49. }
    50. for(int i=0; i
    51. {
    52. cfd = client_ev[i].data.fd;
    53. // lfd请求连接事件
    54. if(cfd == lfd)
    55. {
    56. cfd = Accept_with_print(lfd);
    57. if(-1 == cfd)
    58. return -1;
    59. // 注册cfd
    60. tmp_ev.events = EPOLLIN;
    61. tmp_ev.data.fd = cfd;
    62. ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tmp_ev);
    63. if(-1 == ret)
    64. {
    65. perror("epoll_ctl");
    66. return -1;
    67. }
    68. }
    69. // cfd 读取事件
    70. else
    71. {
    72. int num = read(cfd, buf, sizeof(buf));
    73. if(num < 0)
    74. {
    75. perror("read");
    76. return -1;
    77. }
    78. else if(num == 0)
    79. {
    80. // 先将cfd删除注册,再关闭cfd。
    81. ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
    82. if(-1 == ret)
    83. {
    84. perror("epoll_ctl");
    85. return -1;
    86. }
    87. close(cfd);
    88. printf("client closed\n");
    89. }
    90. else
    91. {
    92. write(cfd, buf, num);
    93. }
    94. }
    95. }
    96. }
    97. // 先将lfd删除注册,再关闭lfd
    98. ret = epoll_ctl(epfd, EPOLL_CTL_DEL, lfd, NULL);
    99. if(-1 == ret)
    100. {
    101. perror("epoll_ctl");
    102. return -1;
    103. }
    104. close(lfd);
    105. close(epfd);
    106. return 0;
    107. }

    2.边缘触发下的epoll服务器

    边缘触发与水平触发服务器的核心步骤基本相似,其不同在于注册新连接事件时需要注册边缘EPOLLET模式,此外读写数据需要采用循环的方式,因此文件描述符应该设置为非阻塞模式。

    编写边缘epoll服务器的核心步骤:

    • 创建绑定监听IP和端口的套接字lfd
    • 创建内核事件表epfd
    • 注册lfd  到内核事件表epfd
    • 循环监听内核事件触发epoll_wait()
    • 触发事件的套接字连接分为监听套接字和连接套接字
    • 处理监听套接字(accept--->新连接注册到事件表(注册EPOLLET模式))
    • 处理连接套接字(循环read读数据(非阻塞)、处理数据)

     epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include "pub.h"
    8. #include
    9. #define OPENMAX 1024
    10. // 边缘模式ET,需要将fd设为非阻塞读取,避免循环读数据时阻塞。
    11. int setnonblocking(int fd)
    12. {
    13. int old_flag = fcntl(fd, F_GETFL);
    14. int new_flag = old_flag | O_NONBLOCK;
    15. fcntl(fd, F_SETFL, new_flag);
    16. return new_flag;
    17. }
    18. int main(int argc, char *argv[])
    19. {
    20. if(argc<3)
    21. {
    22. printf("usage: ./%s ip_address port_num\n", basename(argv[0]));
    23. return -1;
    24. }
    25. char *ip = argv[1];
    26. int port = atoi(argv[2]);
    27. int lfd = -1, cfd = -1, epfd = -1, nready=-1, ret = -1;
    28. // 绑定、监听
    29. lfd = socketBind(ip, port);
    30. if(-1 == lfd)
    31. return -1;
    32. epfd = epoll_create(OPENMAX);
    33. if(epfd < 0)
    34. {
    35. perror("epoll_create");
    36. return -1;
    37. }
    38. // 向epoll事件表注册lfd和lfd的事件(上树)
    39. struct epoll_event tmp_ev, client_ev[OPENMAX];
    40. memset(client_ev, 0, sizeof(client_ev));
    41. tmp_ev.events = EPOLLIN;
    42. tmp_ev.data.fd = lfd;
    43. ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tmp_ev);
    44. if(-1 == ret)
    45. {
    46. perror("epoll_ctl");
    47. return -1;
    48. }
    49. char buf[10] = "";
    50. // 循环处理epoll事件
    51. while(1)
    52. {
    53. nready = epoll_wait(epfd, client_ev, OPENMAX, -1);
    54. if(-1 == nready)
    55. {
    56. perror("epoll_wait");
    57. break;
    58. }
    59. for(int i=0; i
    60. {
    61. cfd = client_ev[i].data.fd;
    62. // lfd请求连接事件
    63. if(cfd == lfd)
    64. {
    65. cfd = Accept_with_print(lfd);
    66. if(-1 == cfd)
    67. return -1;
    68. //cfd设为非阻塞
    69. setnonblocking(cfd);
    70. // 注册cfd 使用边沿触发的方式
    71. tmp_ev.events = EPOLLIN | EPOLLET;
    72. tmp_ev.data.fd = cfd;
    73. ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tmp_ev);
    74. if(-1 == ret)
    75. {
    76. perror("epoll_ctl");
    77. return -1;
    78. }
    79. }
    80. // cfd 读取事件
    81. else
    82. {
    83. while(1)
    84. {
    85. int num = read(cfd, buf, sizeof(buf));
    86. if(num < 0)
    87. {
    88. /* 数据读取完毕返回值小于0,errno设置为EAGAIN或者EWOULDBLOCK */
    89. if((errno == EAGAIN) || (errno == EWOULDBLOCK))
    90. {
    91. break;
    92. }
    93. perror("read");
    94. return -1;
    95. }
    96. else if(num == 0)
    97. {
    98. // 先将cfd删除注册,再关闭cfd。
    99. ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
    100. if(-1 == ret)
    101. {
    102. perror("epoll_ctl");
    103. return -1;
    104. }
    105. close(cfd);
    106. printf("client closed\n");
    107. break;
    108. }
    109. else
    110. {
    111. write(cfd, buf, num);
    112. }
    113. }
    114. }
    115. }
    116. }
    117. // 先将lfd删除注册,再关闭lfd
    118. ret = epoll_ctl(epfd, EPOLL_CTL_DEL, lfd, NULL);
    119. if(-1 == ret)
    120. {
    121. perror("epoll_ctl");
    122. return -1;
    123. }
    124. close(lfd);
    125. close(epfd);
    126. return 0;
    127. }

  • 相关阅读:
    idea 导入项目
    Python Flask框架-开发简单博客-定义和操作数据库
    8/2 训练日志(dp+思维+字典树)
    Mac笔记本安装maven
    RISC Zero项目简介
    结合原理图关于STM32后期例程的更新说明
    spring之基于p命名c命名空间的注入
    【UNR #6 E】神隐(交互)(二进制分组)
    postgresql源码学习(51)—— 提交日志CLOG 提交日志CLOG 原理 用途 管理函数
    【搜索】—— 迭代加深/双向DFS/IDA*
  • 原文地址:https://blog.csdn.net/qq_55796594/article/details/127847276