经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
模拟epoll的饥饿场景
来源:cnblogs  作者:by_mzy  时间:2024/6/12 19:47:19  对本文有异议

说明

一直听说epoll的饥饿模式,但是从未在实际环境中面对过,那么能不能模拟出来呢?实际的情况是怎样呢?

模拟步骤

  • 基于epoll写一个简单的tcp echo server,将每次read返回的字节数打印出来
  • 模拟一个客户端大量写入
  • 测试其他客户端能否正常返回

Server代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <fcntl.h>
  6. #include <sys/epoll.h>
  7. #include <sys/socket.h>
  8. #include <netinet/in.h>
  9. #include <arpa/inet.h>
  10. #define MAX_EVENTS 1024
  11. #define LISTEN_BACKLOG 10
  12. int epoll_fd;
  13. void do_read(int fd);
  14. int main() {
  15. int server_fd, nfds, i;
  16. struct epoll_event event, events[MAX_EVENTS];
  17. struct sockaddr_in server_addr, client_addr;
  18. socklen_t client_addr_len = sizeof(client_addr);
  19. int client_fd;
  20. // 创建 socket
  21. server_fd = socket(AF_INET, SOCK_STREAM, 0);
  22. if (server_fd == -1) {
  23. perror("socket");
  24. return 1;
  25. }
  26. // 设置 socket 选项
  27. int opt = 1;
  28. if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
  29. perror("setsockopt");
  30. close(server_fd);
  31. return 1;
  32. }
  33. // 绑定 socket
  34. memset(&server_addr, 0, sizeof(server_addr));
  35. server_addr.sin_family = AF_INET;
  36. server_addr.sin_addr.s_addr = INADDR_ANY;
  37. server_addr.sin_port = htons(8080);
  38. if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
  39. perror("bind");
  40. close(server_fd);
  41. return 1;
  42. }
  43. // 监听 socket
  44. if (listen(server_fd, LISTEN_BACKLOG) == -1) {
  45. perror("listen");
  46. close(server_fd);
  47. return 1;
  48. }
  49. // 创建 epoll 实例
  50. epoll_fd = epoll_create1(0);
  51. if (epoll_fd == -1) {
  52. perror("epoll_create1");
  53. close(server_fd);
  54. return 1;
  55. }
  56. // 注册服务器 socket
  57. event.events = EPOLLIN;
  58. event.data.fd = server_fd;
  59. if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
  60. perror("epoll_ctl");
  61. close(server_fd);
  62. close(epoll_fd);
  63. return 1;
  64. }
  65. printf("Server listening on port 8080...\n");
  66. while (1) {
  67. // 等待事件就绪
  68. nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
  69. if (nfds == -1) {
  70. perror("epoll_wait");
  71. close(server_fd);
  72. close(epoll_fd);
  73. return 1;
  74. }
  75. // 处理就绪事件
  76. for (i = 0; i < nfds; i++) {
  77. if (events[i].data.fd == server_fd) {
  78. // 接受新连接
  79. client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
  80. if (client_fd == -1) {
  81. perror("accept");
  82. continue;
  83. }
  84. if (fcntl(client_fd , F_SETFL, O_NONBLOCK) == -1) {
  85. perror("fcntl");
  86. close(client_fd);
  87. continue;
  88. }
  89. // 注册客户端 socket
  90. event.events = EPOLLIN;
  91. event.data.fd = client_fd;
  92. if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {
  93. perror("epoll_ctl");
  94. close(client_fd);
  95. continue;
  96. }
  97. printf("New connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
  98. } else {
  99. do_read(events[i].data.fd);
  100. }
  101. }
  102. }
  103. close(server_fd);
  104. close(epoll_fd);
  105. return 0;
  106. }
  107. void do_read(int fd) {
  108. // 处理客户端数据
  109. char buf[1024];
  110. while(1) {
  111. ssize_t bytes_read = read(fd, buf, sizeof(buf));
  112. if (bytes_read == -1) {
  113. perror("read");
  114. close(fd);
  115. if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
  116. perror("epoll_ctl");
  117. }
  118. break;
  119. } else if (bytes_read == 0) {
  120. printf("Client disconnected\n");
  121. close(fd);
  122. if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
  123. perror("epoll_ctl");
  124. }
  125. break;
  126. } else {
  127. printf("Received data: %d\n", bytes_read);
  128. if (write(fd, buf, bytes_read) != bytes_read) {
  129. perror("write");
  130. close(fd);
  131. if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) {
  132. perror("epoll_ctl");
  133. }
  134. break;
  135. }
  136. if (bytes_read < 1024) {
  137. break;
  138. }
  139. }
  140. }
  141. }

模拟客户端

客户端1:大量写入客户端:

  1. cat /dev/random 2>/dev/null | nc 127.0.0.1 8080 >/dev/null

客户端2:其他写入客户端,少量写入检查返回值

  1. nc 127.0.0.1 8080

模拟结果

  • server端收到大量的数据,每次read返回1024个字节,句柄非常忙碌
    image
  • 客户端2往server发送的数据一直没有返回【处于饥饿状态】
    image
  • 一旦客户端1断开,客户端2就收到回复了
    image

结果分析

从代码中可以知道,read一直都有数据读取,一直在处理数据,导致其他句柄无法处理数据。也就是说,其实是我们的代码造成了所谓的饥饿,那么也可以从我们的代码层面上去解决这个问题,思路官方man page中已经提到了,将fd维护一个list,均匀的读写数据即可。

原文链接:https://www.cnblogs.com/bymzy/p/18244598

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号