简约而不简单epoll之EPOLLOUT

随笔记录

select模型,poll模型,epoll模型对比

selectpollepoll
支持最大连接数1024无上限无上限
IO效率每次调用都要进行遍历,时间复杂度O(n)每次调用都要进行遍历,时间复杂度O(n)使用事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到rdllist里面,这样epoll_wait返回的时候我们就拿到了就绪的fd,时间复杂度O(1)
fd拷贝每次select都拷贝,fd数组每次poll都拷贝fd数组调用epoll_ctl时拷贝进内核并由内核保存,之后每次epoll_wait不拷贝

    本篇主要分析epoll边缘触发,通过模拟各种场景,来介绍EPOLLOUT,不涉及epoll底层源码实现。

一、序

epoll_wait返回的条件

1、等待时间到期

2、发生信号事件,例如ctrl+c

3、The associated file is available for read(2) operations,如果注册了EPOLLIN, socket接收缓冲区,有新的数据到来

4、The associated file is available for write(2) operations,如果注册了EPOLLOUT, socket发送缓冲区可写时

前三种场景比较容易理解,但是第4种场景却需要深入研究一下,通过man epoll_ctl手册,可以得到官方对于EPOLLOUT解释,就是上面英文,那么在写代码的时候我们应该如何考虑呢,EPOLLOUT呢?。

二、EPOLLIN|EPOLLOUT同时注册到epoll事件模型

结论1:将EPOLLET|EPOLLIN|EPOLLOUT 注册到epoll中, 然后调用epoll_wait会立刻返回,并且只有EPOLLOUT事件,这个属于第一次EPOLLOUT事件,原因是缓冲区可写。

模拟场景1: client 调用connect后,直接睡眠10s,不发送也不接受任何数据,10s后直接退出。观察server端会有EPOLLOUT打印

代码1:

服务端代码:

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string.h>

  4. #include <strings.h>

  5. #include <unistd.h>

  6. #include <fcntl.h>

  7. #include <errno.h>

  8. #include <netinet/in.h>

  9. #include <arpa/inet.h>

  10. #include <sys/socket.h>

  11. #include <sys/epoll.h>

  12. #define SERVPORT 9527

  13. #define MAXBUF 8192

  14. #define MAXFDS 5000

  15. #define EVENTSIZE 100

  16. char sndMsg[MAXBUF] = {0};

  17. int setnonblocking(int fd)

  18. {

  19. int opts;

  20. if( (opts = fcntl(fd, F_GETFL, 0)) == -1) {

  21. perror("fcntl");

  22. return -1;

  23. }

  24. opts = opts | O_NONBLOCK;

  25. if( (opts = fcntl(fd, F_SETFL, opts)) == -1) {

  26. perror("fcntl");

  27. return -1;

  28. }

  29. return 0;

  30. }

  31. int main(void)

  32. {

  33. char buf[MAXBUF];

  34. int len, n;

  35. struct sockaddr_in servaddr;

  36. int sockfd, listenfd, epollfd, nfds;

  37. struct epoll_event ev;

  38. struct epoll_event events[EVENTSIZE];

  39. bzero(&servaddr, sizeof(servaddr));

  40. servaddr.sin_family = AF_INET;

  41. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  42. servaddr.sin_port = htons(SERVPORT);

  43. if( (epollfd = epoll_create(MAXFDS)) == -1) {

  44. perror("epoll");

  45. exit(1);

  46. }

  47. if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

  48. perror("socket");

  49. exit(1);

  50. }

  51. if(setnonblocking(listenfd) == -1){

  52. perror("setnonblocking");

  53. exit(1);

  54. }

  55. if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {

  56. perror("bind");

  57. exit(1);

  58. }

  59. if(listen(listenfd, 10) == -1) {

  60. perror("listen");

  61. exit(1);

  62. }

  63. // listen fd只注册EPOLLIN事件, EPOLLOUT不需要注册

  64. ev.events = EPOLLIN | EPOLLET;

  65. ev.data.fd = listenfd;

  66. if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {

  67. perror("epoll_ctl");

  68. exit(1);

  69. }

  70. for( ; ; ) {

  71. if( (nfds = epoll_wait(epollfd, events, EVENTSIZE, -1)) == -1) {

  72. perror("epoll_wait");

  73. exit(1);

  74. }

  75. for(n = 0; n < nfds; n++) {

  76. if(events[n].data.fd == listenfd) {

  77. while( (sockfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) > 0) {

  78. if(setnonblocking(sockfd) == -1) {

  79. perror("setnonblocking");

  80. exit(1);

  81. }

  82. //新的fd: 采用边缘触发且注册IN、OUT事件

  83. ev.events = EPOLLIN | EPOLLOUT | EPOLLET;

  84. ev.data.fd = sockfd;

  85. if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {

  86. perror("epoll_ctl");

  87. exit(1);

  88. } else {

  89. printf("new socketfd = %d register epoll success\n", sockfd);

  90. }

  91. }

  92. continue;

  93. }

  94. printf("Events = 0x%x\n", events[n].events);

  95. if (events[n].events & (EPOLLIN | EPOLLOUT) == (EPOLLIN | EPOLLOUT)) {

  96. printf(">> EPOLLIN And EPOLLOUT event, socketfd = %d\n", events[n].data.fd);

  97. }

  98. else if (events[n].events & EPOLLIN) {

  99. printf(">> Only EPOLLIN event, socketfd = %d\n", events[n].data.fd);

  100. }

  101. else if (events[n].events & EPOLLOUT) {

  102. printf(">> Only EPOLLOUT, socketfd = %d\n", events[n].data.fd);

  103. }

  104. }

  105. }

  106. }

客户端代码:

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string.h>

  4. #include <unistd.h>

  5. #include <strings.h>

  6. #include <netinet/in.h>

  7. #include <arpa/inet.h>

  8. #include <sys/socket.h>

  9. #define SERVPORT 9527

  10. #define MAXBUF 1024

  11. int main(void)

  12. {

  13. char buf[MAXBUF];

  14. struct sockaddr_in servaddr;

  15. int fd;

  16. int n, len;

  17. bzero(&servaddr, sizeof(servaddr));

  18. servaddr.sin_family = AF_INET;

  19. servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

  20. servaddr.sin_port = htons(SERVPORT);

  21. if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

  22. perror("socket");

  23. exit(1);

  24. }

  25. if (connect(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {

  26. perror("connect");

  27. exit(1);

  28. }

  29. printf("Connection ok, Send data after sleep 10s.\n");

  30. sleep(10);

  31. printf(">> bye bye\n");

  32. close(fd);// 会出发server端 EPOLLIN和EPOLLOUT

  33. return 0;

  34. }

输出结果:

结论2:在注册的时候,将EPOLLET|EPOLLIN|EPOLLOUT注册到epoll中,然后客户端client发送数据,server就会触发EPOLLIN|EPOLLOUT, 原因还是缓冲区是可以写的。

模拟场景2:建立连接后clien立即发送数据,每次间隔5秒,server端不调用recv函数也不调用send函数。

输出结果:执行了两次,两次基本相同,只不过是第二次多出现一个Only EPOLLOUT。

代码:

服务端-与结论1中代码相同,此处不在罗列

客户端:

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string.h>

  4. #include <unistd.h>

  5. #include <strings.h>

  6. #include <netinet/in.h>

  7. #include <arpa/inet.h>

  8. #include <sys/socket.h>

  9. #define SERVPORT 9527

  10. #define MAXBUF 1024

  11. int main(void)

  12. {

  13. char buf[MAXBUF];

  14. struct sockaddr_in servaddr;

  15. int fd;

  16. int n, len;

  17. bzero(&servaddr, sizeof(servaddr));

  18. servaddr.sin_family = AF_INET;

  19. servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

  20. servaddr.sin_port = htons(SERVPORT);

  21. if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

  22. {

  23. perror("socket");

  24. exit(1);

  25. }

  26. if (connect(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)

  27. {

  28. perror("connect");

  29. exit(1);

  30. }

  31. printf(">> Send \"helloworld-1\"\n");

  32. send(fd, "helloworld-1", sizeof("helloworld-1") - 1, 0);

  33. sleep(5);

  34. printf(">> Send \"helloworld-2\"\n");

  35. send(fd, "helloworld-2", sizeof("helloworld-2") - 1, 0);

  36. printf("Will colse after sleep 8s.\n");

  37. sleep(8);

  38. close(fd);

  39. return 0;

  40. }

结论3:在注册的时候,将EPOLLET|EPOLLIN|EPOLLOUT注册到epoll中, client不发送也不接收数据,sever一直
发送数据,直到将socket发送缓冲区打满, 此时client发送数据到server端, 这时server只会触发EPOLLIN事件
没有EPOLLOUT,原因是发送缓冲区是满的,不可写,所以不会触发EPOLLOUT。

模拟场景:设置server端socket 发送缓冲区是4096(4k), 设置client端接收rcvbuf缓冲区3072(3k),客户端不接收数据即不调用recv函数,服务端一直发送数据,最终会把服务端发送缓冲区打满,我们这里改变缓冲区大小,只是为了更快打满缓冲区。

输出数据:

代码:

服务端代码:

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string.h>

  4. #include <strings.h>

  5. #include <unistd.h>

  6. #include <fcntl.h>

  7. #include <errno.h>

  8. #include <netinet/in.h>

  9. #include <arpa/inet.h>

  10. #include <sys/socket.h>

  11. #include <sys/epoll.h>

  12. #define SERVPORT 9527

  13. #define MAXBUF 1024*1024 //1MB

  14. #define MAXFDS 5000

  15. #define EVENTSIZE 100

  16. int setnonblocking(int fd)

  17. {

  18. int opts;

  19. if( (opts = fcntl(fd, F_GETFL, 0)) == -1)

  20. {

  21. perror("fcntl");

  22. return -1;

  23. }

  24. opts = opts | O_NONBLOCK;

  25. if( (opts = fcntl(fd, F_SETFL, opts)) == -1)

  26. {

  27. perror("fcntl");

  28. return -1;

  29. }

  30. return 0;

  31. }

  32. int main(void)

  33. {

  34. int len = 0, n = 0, once = 0;

  35. int hasSend;

  36. char buf[MAXBUF] = {0};

  37. char sndMsg[MAXBUF] = {1};

  38. struct sockaddr_in servaddr;

  39. int sockfd, listenfd, epollfd, nfds;

  40. struct epoll_event ev;

  41. struct epoll_event events[EVENTSIZE];

  42. bzero(&servaddr, sizeof(servaddr));

  43. servaddr.sin_family = AF_INET;

  44. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  45. servaddr.sin_port = htons(SERVPORT);

  46. if( (epollfd = epoll_create(MAXFDS)) == -1) {

  47. perror("epoll");

  48. exit(1);

  49. }

  50. if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

  51. perror("socket");

  52. exit(1);

  53. }

  54. if(setnonblocking(listenfd) == -1) {

  55. perror("setnonblocking");

  56. exit(1);

  57. }

  58. if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {

  59. perror("bind");

  60. exit(1);

  61. }

  62. if(listen(listenfd, 10) == -1) {

  63. perror("listen");

  64. exit(1);

  65. }

  66. ev.events = EPOLLIN | EPOLLET;

  67. ev.data.fd = listenfd;

  68. if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {

  69. perror("epoll_ctl");

  70. exit(1);

  71. }

  72. for( ; ; ) {

  73. if( (nfds = epoll_wait(epollfd, events, EVENTSIZE, -1)) == -1) {

  74. perror("epoll_wait");

  75. exit(1);

  76. }

  77. for(n = 0; n < nfds; n++) {

  78. if(events[n].data.fd == listenfd) {

  79. while( (sockfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) > 0) {

  80. if(setnonblocking(sockfd) == -1) {

  81. perror("setnonblocking");

  82. exit(1);

  83. }

  84. ev.events = EPOLLIN | EPOLLOUT | EPOLLET;

  85. ev.data.fd = sockfd;

  86. int nSendBuf = 4096; //上层应用设置成4096 但是底层内核实际是4096*2 = 8192

  87. socklen_t opt_len = sizeof(int);

  88. int ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &nSendBuf, sizeof(int));

  89. if (ret == -1) {

  90. perror("setsockopt");

  91. }

  92. getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &nSendBuf, &opt_len);

  93. printf("SndBuf-new = %d\n", nSendBuf);

  94. if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) == -1)

  95. {

  96. perror("epoll_ctl");

  97. exit(1);

  98. } else {

  99. printf("new socketfd = %d register epoll success\n", sockfd);

  100. }

  101. }

  102. continue;

  103. }

  104. printf("Events = 0x%x\n", events[n].events);

  105. if (events[n].events & EPOLLIN && events[n].events & EPOLLOUT) {

  106. printf(">> EPOLLIN And EPOLLOUT event, socketfd = %d\n", events[n].data.fd);

  107. len = recv(events[n].data.fd, buf, MAXBUF, 0);

  108. len = send(events[n].data.fd, sndMsg + hasSend, MAXBUF-hasSend, 0);

  109. if (len > 0) {// 说明发送成功

  110. hasSend += len;

  111. once = 0;

  112. printf("EPOLLIN | EPOLLOUT ==》 Send ok, length = %d.\n", len);

  113. } else if (len == -1) {

  114. if (errno == EAGAIN) {//说明发送缓冲区已经满

  115. printf("EPOLLIN | EPOLLOUT ==》 Socket SndBuf is fulled. \n");

  116. }

  117. } else {//出现错误

  118. perror("EPOLLIN | EPOLLOUT ==》 Send failed.\n");

  119. exit(-1);

  120. }

  121. }

  122. else if (events[n].events & EPOLLIN) {

  123. printf(">> Only EPOLLIN event, socketfd = %d\n", events[n].data.fd);

  124. }

  125. else if (events[n].events & EPOLLOUT) {

  126. printf(">> Only EPOLLOUT, socketfd = %d\n", events[n].data.fd);

  127. while (1) {

  128. len = send(events[n].data.fd, sndMsg + hasSend, MAXBUF-hasSend, 0);

  129. if (len > 0) {// 说明发送成功

  130. hasSend += len;

  131. once = 0;

  132. printf("Send ok, length = %d.\n", len);

  133. } else if (len == -1) {

  134. if (errno == EAGAIN) {//说明发送缓冲区已经满

  135. printf(" EPOLLOUT ==》 Socket SndBuf is fulled. \n");

  136. break;

  137. }

  138. } else {//出现错误

  139. perror(" EPOLLOUT ==》 Send failed.\n");

  140. exit(-1);

  141. }

  142. }

  143. }

  144. }

  145. }

  146. }

客户端代码:

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string.h>

  4. #include <unistd.h>

  5. #include <strings.h>

  6. #include <netinet/in.h>

  7. #include <arpa/inet.h>

  8. #include <sys/socket.h>

  9. #define SERVPORT 9527

  10. #define MAXBUF 1024

  11. int main(void)

  12. {

  13. char buf[MAXBUF];

  14. struct sockaddr_in servaddr;

  15. int fd;

  16. int n, len;

  17. bzero(&servaddr, sizeof(servaddr));

  18. servaddr.sin_family = AF_INET;

  19. servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

  20. servaddr.sin_port = htons(SERVPORT);

  21. if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

  22. {

  23. perror("socket");

  24. exit(1);

  25. }

  26. int nSendBuf = 0;

  27. socklen_t opt_len = sizeof(int);

  28. nSendBuf = 3072;

  29. int ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nSendBuf, sizeof(int));

  30. if (ret == -1) {

  31. perror("setsockopt");

  32. }

  33. getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nSendBuf, &opt_len);

  34. printf("Client recvbuf len = %d\n", nSendBuf);

  35. if (connect(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)

  36. {

  37. perror("connect");

  38. exit(1);

  39. }

  40. printf("Connection ok, sleep 30s\n");

  41. sleep(30); //睡眠60秒保证 服务端缓冲区被打满

  42. printf("Send msg==>""hello server!! do you sndbuf is fulled?\n");

  43. send(fd, "hello server!! do you sndbuf is fulled?", strlen("hello server!! do you sndbuf is fulled?"), 0);

  44. sleep(10);

  45. send(fd, "hello server!! do you sndbuf is fulled?", strlen("hello server!! do you sndbuf is fulled?"), 0);

  46. sleep(10);

  47. close(fd);

  48. return 0;

  49. }

结论4:在注册的时候,将EPOLLET|EPOLLIN|EPOLLOUT注册到epoll中, client不发送也不接收数据,sever一直发送数据,直到将socket 发送缓冲区打满, 此时client 开始连续接收n次数据(不发送数据), 此时server又会产生EPOLLOUT,因为发送缓冲区变成可写(由满->不满),由于服务端要发送1MB数据,客户端每次只读取256字节,所有服务端发送缓冲区状态变化是:满->不满->满(不可写->可写->不可写)。

输出数据:

代码:

服务端,与结论3中服务端代码一样

客户端代码:

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string.h>

  4. #include <strings.h>

  5. #include <unistd.h>

  6. #include <fcntl.h>

  7. #include <errno.h>

  8. #include <netinet/in.h>

  9. #include <arpa/inet.h>

  10. #include <sys/socket.h>

  11. #include <sys/epoll.h>

  12. #define SERVPORT 9527

  13. #define MAXBUF 1024

  14. int setnonblocking(int fd)

  15. {

  16. int opts;

  17. if( (opts = fcntl(fd, F_GETFL, 0)) == -1)

  18. {

  19. perror("fcntl");

  20. return -1;

  21. }

  22. opts = opts | O_NONBLOCK;

  23. if( (opts = fcntl(fd, F_SETFL, opts)) == -1)

  24. {

  25. perror("fcntl");

  26. return -1;

  27. }

  28. return 0;

  29. }

  30. int main(void)

  31. {

  32. char buf[MAXBUF];

  33. struct sockaddr_in servaddr;

  34. int fd;

  35. int n, len;

  36. bzero(&servaddr, sizeof(servaddr));

  37. servaddr.sin_family = AF_INET;

  38. servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

  39. servaddr.sin_port = htons(SERVPORT);

  40. if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

  41. {

  42. perror("socket");

  43. exit(1);

  44. }

  45. int nSendBuf = 0;

  46. socklen_t opt_len = sizeof(int);

  47. nSendBuf = 3072;

  48. int ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nSendBuf, sizeof(int));

  49. if (ret == -1) {

  50. perror("setsockopt");

  51. }

  52. getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nSendBuf, &opt_len);

  53. printf("Client recvbuf len = %d\n", nSendBuf);

  54. if (connect(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)

  55. {

  56. perror("connect");

  57. exit(1);

  58. }

  59. setnonblocking(fd); //设置成非阻塞

  60. printf("Connection ok, sleep 10s\n");

  61. sleep(10); //睡眠10秒保证 服务端缓冲区被打满

  62. while(1) {

  63. len = recv(fd, buf, 256, 0);// 由于服务端发送的数据是1MB所以需要多次调用

  64. if (len > 0) {

  65. n += len;

  66. } else if (len == -1 && errno == EAGAIN) {

  67. printf("EAGAIN, Has recv n = %d\n", n);

  68. sleep(3);

  69. } else {

  70. printf("Recv failed.\n");

  71. exit(-1);

  72. }

  73. }

  74. close(fd);

  75. return 0;

  76. }

结论5:在注册的时候,将EPOLLET|EPOLLIN|EPOLLOUT注册到epoll中, client不发送也不接收数据,sever一直发送数据,直到将socket 发送缓冲区打满,打满之后server再也不会发送数据。 此时client 开始连续接收n次数据(不发送数据), 此时server又会产生EPOLLOUT,因为发送缓冲区变成可写(由满->不满)。

输出数据:

代码:

服务端代码:

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string.h>

  4. #include <strings.h>

  5. #include <unistd.h>

  6. #include <fcntl.h>

  7. #include <errno.h>

  8. #include <netinet/in.h>

  9. #include <arpa/inet.h>

  10. #include <sys/socket.h>

  11. #include <sys/epoll.h>

  12. #define SERVPORT 9527

  13. #define MAXBUF 1024*1024 //1MB

  14. #define MAXFDS 5000

  15. #define EVENTSIZE 100

  16. int setnonblocking(int fd)

  17. {

  18. int opts;

  19. if( (opts = fcntl(fd, F_GETFL, 0)) == -1)

  20. {

  21. perror("fcntl");

  22. return -1;

  23. }

  24. opts = opts | O_NONBLOCK;

  25. if( (opts = fcntl(fd, F_SETFL, opts)) == -1)

  26. {

  27. perror("fcntl");

  28. return -1;

  29. }

  30. return 0;

  31. }

  32. int main(void)

  33. {

  34. int len = 0, n = 0, once = 0;

  35. int hasSend;

  36. char buf[MAXBUF] = {0};

  37. char sndMsg[MAXBUF] = {1};

  38. struct sockaddr_in servaddr;

  39. int sockfd, listenfd, epollfd, nfds;

  40. struct epoll_event ev;

  41. struct epoll_event events[EVENTSIZE];

  42. bzero(&servaddr, sizeof(servaddr));

  43. servaddr.sin_family = AF_INET;

  44. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  45. servaddr.sin_port = htons(SERVPORT);

  46. if( (epollfd = epoll_create(MAXFDS)) == -1) {

  47. perror("epoll");

  48. exit(1);

  49. }

  50. if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

  51. perror("socket");

  52. exit(1);

  53. }

  54. if(setnonblocking(listenfd) == -1) {

  55. perror("setnonblocking");

  56. exit(1);

  57. }

  58. if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {

  59. perror("bind");

  60. exit(1);

  61. }

  62. if(listen(listenfd, 10) == -1) {

  63. perror("listen");

  64. exit(1);

  65. }

  66. ev.events = EPOLLIN | EPOLLET;

  67. ev.data.fd = listenfd;

  68. if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {

  69. perror("epoll_ctl");

  70. exit(1);

  71. }

  72. for( ; ; ) {

  73. if( (nfds = epoll_wait(epollfd, events, EVENTSIZE, -1)) == -1) {

  74. perror("epoll_wait");

  75. exit(1);

  76. }

  77. for(n = 0; n < nfds; n++) {

  78. if(events[n].data.fd == listenfd) {

  79. while( (sockfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) > 0) {

  80. if(setnonblocking(sockfd) == -1) {

  81. perror("setnonblocking");

  82. exit(1);

  83. }

  84. ev.events = EPOLLIN | EPOLLOUT | EPOLLET;

  85. ev.data.fd = sockfd;

  86. int nSendBuf = 4096; //上层应用设置成4096 但是底层内核实际是4096*2 = 8192

  87. socklen_t opt_len = sizeof(int);

  88. int ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &nSendBuf, sizeof(int));

  89. if (ret == -1) {

  90. perror("setsockopt");

  91. }

  92. getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &nSendBuf, &opt_len);

  93. printf("SndBuf-new = %d\n", nSendBuf);

  94. if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) == -1)

  95. {

  96. perror("epoll_ctl");

  97. exit(1);

  98. } else {

  99. printf("new socketfd = %d register epoll success\n", sockfd);

  100. }

  101. }

  102. continue;

  103. }

  104. printf("Events = 0x%x\n", events[n].events);

  105. if (events[n].events & EPOLLIN && events[n].events & EPOLLOUT) {

  106. printf(">> EPOLLIN And EPOLLOUT event, socketfd = %d\n", events[n].data.fd);

  107. len = recv(events[n].data.fd, buf, MAXBUF, 0);

  108. len = send(events[n].data.fd, sndMsg + hasSend, MAXBUF-hasSend, 0);

  109. if (len > 0) {// 说明发送成功

  110. hasSend += len;

  111. once = 0;

  112. printf("EPOLLIN | EPOLLOUT ==》 Send ok, length = %d.\n", len);

  113. } else if (len == -1) {

  114. if (errno == EAGAIN) {//说明发送缓冲区已经满

  115. printf("EPOLLIN | EPOLLOUT ==》 Socket SndBuf is fulled. \n");

  116. }

  117. } else {//出现错误

  118. perror("EPOLLIN | EPOLLOUT ==》 Send failed.\n");

  119. exit(-1);

  120. }

  121. }

  122. else if (events[n].events & EPOLLIN) {

  123. printf(">> Only EPOLLIN event, socketfd = %d\n", events[n].data.fd);

  124. }

  125. else if (events[n].events & EPOLLOUT) {

  126. printf(">> Only EPOLLOUT, socketfd = %d\n", events[n].data.fd);

  127. while (!once) {

  128. len = send(events[n].data.fd, sndMsg + hasSend, MAXBUF-hasSend, 0);

  129. if (len > 0) {// 说明发送成功

  130. hasSend += len;

  131. printf("Send ok, length = %d.\n", len);

  132. } else if (len == -1) {

  133. if (errno == EAGAIN) {//说明发送缓冲区已经满

  134. printf(" EPOLLOUT ==》 Socket SndBuf is fulled. \n");

  135. once = 1;// 以后再也不会发送数据了,保证缓冲区不会再满

  136. break;

  137. }

  138. } else {//出现错误

  139. perror(" EPOLLOUT ==》 Send failed.\n");

  140. exit(-1);

  141. }

  142. }

  143. }

  144. }

  145. }

  146. }

客户端代码:

 
  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. #include <string.h>

  4. #include <strings.h>

  5. #include <unistd.h>

  6. #include <fcntl.h>

  7. #include <errno.h>

  8. #include <netinet/in.h>

  9. #include <arpa/inet.h>

  10. #include <sys/socket.h>

  11. #include <sys/epoll.h>

  12. #define SERVPORT 9527

  13. #define MAXBUF 4096

  14. int setnonblocking(int fd)

  15. {

  16. int opts;

  17. if( (opts = fcntl(fd, F_GETFL, 0)) == -1)

  18. {

  19. perror("fcntl");

  20. return -1;

  21. }

  22. opts = opts | O_NONBLOCK;

  23. if( (opts = fcntl(fd, F_SETFL, opts)) == -1)

  24. {

  25. perror("fcntl");

  26. return -1;

  27. }

  28. return 0;

  29. }

  30. int main(void)

  31. {

  32. char buf[MAXBUF];

  33. struct sockaddr_in servaddr;

  34. int fd;

  35. int n, len;

  36. bzero(&servaddr, sizeof(servaddr));

  37. servaddr.sin_family = AF_INET;

  38. servaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

  39. servaddr.sin_port = htons(SERVPORT);

  40. if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)

  41. {

  42. perror("socket");

  43. exit(1);

  44. }

  45. int nSendBuf = 0;

  46. socklen_t opt_len = sizeof(int);

  47. nSendBuf = 3072;

  48. int ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nSendBuf, sizeof(int));

  49. if (ret == -1) {

  50. perror("setsockopt");

  51. }

  52. getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nSendBuf, &opt_len);

  53. printf("Client recvbuf len = %d\n", nSendBuf);

  54. if (connect(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)

  55. {

  56. perror("connect");

  57. exit(1);

  58. }

  59. setnonblocking(fd); //设置成非阻塞

  60. printf("Connection ok, sleep 10s\n");

  61. sleep(10); //睡眠10秒保证 服务端缓冲区被打满

  62. int i = 0;

  63. while(1) {

  64. len = recv(fd, buf, MAXBUF, 0);

  65. if (len > 0) {

  66. n += len;

  67. printf("第%d次,recv len = %d\n", ++i, len);

  68. sleep(10);

  69. } else if (len == -1 && errno == EAGAIN) {

  70. printf("EAGAIN\n", n);

  71. sleep(20);

  72. break;//默认读取完毕

  73. } else {

  74. printf("Recv failed.\n");

  75. exit(-1);

  76. }

  77. }

  78. printf("close\n");

  79. close(fd);

  80. return 0;

  81. }

三、建议

1、对于服务端listen socket不需要将EPOLLOUT注册到epoll事件模型中。因为listen socket只是负责接收数据(接收客户端建立连接请求),不会发送数据,所以不需要注册时EPOLLOUT。

2、按需注册EPOLLOUT。当我们调用send接口时,如果返回的-1且errno=EAGAIN时,再注册EPOLLOUT,后续send发送成功后,再将EPOLLOUT从epoll事件模型中移除,这就是按需注册EPOLLOUT。当然我们也可以不用移除,只不过需要判断是否真的有数据需要发送。大名鼎鼎的nginx的做法是:发送完成后会将发送的回调函数设置成一个空函数(这个函数只是定义里面什么都没有做)。nginx为什么不移除呢?因为反复添加、移除EPOLLOUT性能不友好,总是在用户层和内核层来回切换。

(62条消息) 简约而不简单epoll之EPOLLOUT_程序员的世界-CSDN博客_epollout

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值