免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 3795 | 回复: 19

[其他] 用 epoll 监听 socket的时候,还有必要让 socket 非阻塞吗? [复制链接]

论坛徽章:
12
2015年辞旧岁徽章
日期:2015-03-03 16:54:1515-16赛季CBA联赛之同曦
日期:2017-03-17 19:13:162016科比退役纪念章
日期:2016-11-07 08:28:12luobin
日期:2016-06-17 17:46:36wusuopu
日期:2016-06-17 17:43:4515-16赛季CBA联赛之福建
日期:2016-01-14 12:49:22程序设计版块每日发帖之星
日期:2015-12-13 06:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:002015年亚洲杯之科威特
日期:2015-03-24 14:21:272015年迎新春徽章
日期:2015-03-04 09:57:092016科比退役纪念章
日期:2018-04-10 16:20:18
发表于 2016-06-01 21:44 |显示全部楼层
我感觉没有必要,比如有读事件发生,说明已经有数据可读了,这时候 socket 阻塞和非阻塞貌似没有什么区别。

大牛指教。

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
发表于 2016-06-01 22:21 |显示全部楼层
本帖最后由 yulihua49 于 2016-06-01 22:45 编辑

一般不需要。特殊情况需要。
特殊情况是:
少量线程处理大量连接。其中一个或几个连接正在传输大包数据。恰巧使用了低速,或拥塞网络。比如在车上使用2G或3G网络传大数据到地面数据中心。
阻塞的话,就会长时间占用一个线程,导致影响了别的用户的作业,哪怕人家只传一个小包。
这个场景需要使用NIO(NON block IO)。
但是NIO非常不方便,通过重重回调才能完成一个包,把业务逻辑切的很碎。
这时可能需要协程技术,上层对下层发一个看起来是同步阻塞的函数调用,实现一个完整的包IO。但是在底层,IO暂停期间释放线程,让他为别人服务。等IO具备条件了再有线程继续你的工作(其间不时的挂起(yield),解挂( resume))直至完成返回。函数调用时跟返回时未必是同一个线程。

特别是,如果一个线程处理大量连接,则必须要使用协程技术。

论坛徽章:
12
2015年辞旧岁徽章
日期:2015-03-03 16:54:1515-16赛季CBA联赛之同曦
日期:2017-03-17 19:13:162016科比退役纪念章
日期:2016-11-07 08:28:12luobin
日期:2016-06-17 17:46:36wusuopu
日期:2016-06-17 17:43:4515-16赛季CBA联赛之福建
日期:2016-01-14 12:49:22程序设计版块每日发帖之星
日期:2015-12-13 06:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:002015年亚洲杯之科威特
日期:2015-03-24 14:21:272015年迎新春徽章
日期:2015-03-04 09:57:092016科比退役纪念章
日期:2018-04-10 16:20:18
发表于 2016-06-01 22:43 |显示全部楼层
回复 2# yulihua49


    非常感谢

   

论坛徽章:
12
2015年辞旧岁徽章
日期:2015-03-03 16:54:1515-16赛季CBA联赛之同曦
日期:2017-03-17 19:13:162016科比退役纪念章
日期:2016-11-07 08:28:12luobin
日期:2016-06-17 17:46:36wusuopu
日期:2016-06-17 17:43:4515-16赛季CBA联赛之福建
日期:2016-01-14 12:49:22程序设计版块每日发帖之星
日期:2015-12-13 06:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:002015年亚洲杯之科威特
日期:2015-03-24 14:21:272015年迎新春徽章
日期:2015-03-04 09:57:092016科比退役纪念章
日期:2018-04-10 16:20:18
发表于 2016-06-01 22:49 |显示全部楼层
回复 2# yulihua49


    不过还是会有一个疑问:

    我的理解:如果 epoll 返回一个 socket 的 EPOLLIN 事件,那么说明数据已经到了接收端的缓冲区了,这时候读数据是合情合理,不应该算作影响其他作业。本次读取完毕之后,释放线程,继续调度。所以阻塞非阻塞是不影响的。


    不知道这么理解有什么问题,大牛指教。

论坛徽章:
12
2015年辞旧岁徽章
日期:2015-03-03 16:54:1515-16赛季CBA联赛之同曦
日期:2017-03-17 19:13:162016科比退役纪念章
日期:2016-11-07 08:28:12luobin
日期:2016-06-17 17:46:36wusuopu
日期:2016-06-17 17:43:4515-16赛季CBA联赛之福建
日期:2016-01-14 12:49:22程序设计版块每日发帖之星
日期:2015-12-13 06:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:002015年亚洲杯之科威特
日期:2015-03-24 14:21:272015年迎新春徽章
日期:2015-03-04 09:57:092016科比退役纪念章
日期:2018-04-10 16:20:18
发表于 2016-06-01 23:04 |显示全部楼层
假设一个场景:一个大型的文件,比如几个 G,发送端把文件拆分成块,每块 32M,多线程发送。

接收端接收数据时,如果是非阻塞,那么可能收到假如 100k 数据之后,这个连接上因为网络或者其他原因暂时没有后续数据收到,这时候读的接口就会返回 0 或者非阻塞的 errno,然后跳出线程,线程继续参与调度。

我的问题是:如果这个连接上后续的数据来了,重新开始接收后续数据,那怎么和之前的 100k 对应上呢?

如果是阻塞模式,就不会有这个问题。

论坛徽章:
12
2015年辞旧岁徽章
日期:2015-03-03 16:54:1515-16赛季CBA联赛之同曦
日期:2017-03-17 19:13:162016科比退役纪念章
日期:2016-11-07 08:28:12luobin
日期:2016-06-17 17:46:36wusuopu
日期:2016-06-17 17:43:4515-16赛季CBA联赛之福建
日期:2016-01-14 12:49:22程序设计版块每日发帖之星
日期:2015-12-13 06:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:002015年亚洲杯之科威特
日期:2015-03-24 14:21:272015年迎新春徽章
日期:2015-03-04 09:57:092016科比退役纪念章
日期:2018-04-10 16:20:18
发表于 2016-06-01 23:07 |显示全部楼层
哦,,,,也可以记录每个连接上读取的是第几块数据块,读取了多少数据,这样的话,每个线程都不断片儿了。

这么看的话,还是非阻塞要高效些。

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
发表于 2016-06-03 10:19 |显示全部楼层
本帖最后由 yulihua49 于 2016-06-03 10:48 编辑
VIP_fuck 发表于 2016-06-01 22:49
回复 2# yulihua49

如果传一个大包,只来了一部分数据,就会激活EPOLLIN,但是你真的同步接收,还是要等待全部收完。
下边是一个通用的同步/NIO两用的收发接口,平时就是同步阻塞的。在epoll激活时调用之。稍加修改你就可以使用:
typedef int (*T_YIELD)(int socket,int rwflg,int timeout);
#define TIMEOUTERR -11

  1. /**********************************************
  2. * @(#) SDBC 7.1 TCP RECV/SEND  Tools                      *
  3. * to suport coroutine
  4. **********************************************/
  5. #include <sys/socket.h>
  6. #include <sys/time.h>
  7. #include <unistd.h>
  8. #include <fcntl.h>
  9. #include <sc.h>

  10. #ifndef MIN
  11. #define MIN(a,b) ((a)<(b))?(a):(b)
  12. #endif

  13. static T_YIELD yield=NULL;

  14. T_YIELD get_yield()
  15. {
  16.         return yield;
  17. }

  18. T_YIELD set_yield(T_YIELD new_yield)
  19. {
  20. T_YIELD oldyield=yield;
  21.         yield=new_yield;
  22.         return oldyield;
  23. }

  24. //timeout for second
  25. int RecvNet(int socket,char *buf,int n,int timeout)
  26. {
  27. int bcount=0,br,ret;
  28. int i,repeat=0;
  29. int fflag=-1;

  30.         if(socket<0) return SYSERR;
  31.         if(!buf && n<0) return 0;
  32.         if(yield) {
  33.                 fflag=fcntl(socket,F_GETFL,0);
  34.                 if(fflag!=-1) fcntl(socket,F_SETFL,fflag|O_ASYNC|O_NONBLOCK); //异步操作
  35.         } else if(timeout>0) {
  36.                 struct timeval tmout;
  37.                 tmout.tv_sec=timeout;
  38.                 tmout.tv_usec=0;
  39.                 ret=setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&tmout,sizeof(tmout));
  40.                 if(ret) ShowLog(1,"%s:setsockopt errno=%d,%s",__FUNCTION__,errno,strerror(errno));
  41.         }

  42.         *buf=0;
  43.         br=0;

  44.         while(bcount<n){
  45.                 if((br=read(socket,buf,n-bcount))>0){
  46.                         bcount+=br;
  47.                         buf+=br;
  48.                         repeat=0;
  49.                         continue;
  50.                 }
  51.                 if(fflag==-1 && errno==EAGAIN) return TIMEOUTERR;
  52.                 if(br<=0 && errno && errno != EAGAIN){
  53.                     if(errno!=ECONNRESET)
  54.                         ShowLog(1,"%s:br=%d,err=%d,%s",__FUNCTION__,br,errno,strerror(errno));
  55.                     break;
  56.                 }
  57.                 if(bcount < n && fflag!=-1) { //切换任务
  58.                         if(repeat++>3) return -errno;
  59. ShowLog(5,"%s:tid=%lX,socket=%d,yield to schedle bcount=%d/%d",__FUNCTION__,pthread_self(),socket,bcount,n);
  60.                         i=yield(socket,0,timeout);
  61.                         if(i<0) {
  62.                           if(timeout>0) {
  63.                                 struct timeval tmout;
  64.                                 tmout.tv_sec=timeout;
  65.                                 tmout.tv_usec=0;
  66.                                 ret=setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&tmout,sizeof(tmout));
  67.                           }
  68.                                 fcntl(socket,F_SETFL,fflag);
  69.                                 fflag=-1;
  70.                                 if(i==TIMEOUTERR) return i;
  71.                         }
  72.                 }
  73.         }
  74.         if(fflag!=-1) fcntl(socket,F_SETFL,fflag);
  75.         return bcount==0?-1:bcount;
  76. }

  77. int SendNet(int socket,char *buf,int n,int MTU)
  78. {
  79. int bcount,bw;
  80. int sz,i=0;
  81. int fflag=-1;
  82. size_t SendSize;

  83.         if(socket<0) return SYSERR;
  84.         if(yield) {
  85.                 fflag=fcntl(socket,F_GETFL,0);
  86.                 if(fflag != -1) fcntl(socket,F_SETFL,fflag|O_NONBLOCK); //异步操作
  87.         }
  88.         bcount=0;
  89.         bw=0;
  90.         if(MTU>500) SendSize=MTU;
  91.         else SendSize=n;
  92.         while(bcount<n){
  93.                 sz=MIN(n-bcount,SendSize);
  94.                 if((bw=write(socket,buf,sz))>0){
  95.                         bcount+=bw;
  96.                         buf+=bw;
  97.                 }
  98.                 if(bw<0&&errno!=EAGAIN) {
  99.                         ShowLog(1,"%s:err=%d,%s",__FUNCTION__,errno,strerror(errno));
  100.                         break;
  101.                 }
  102.                 if(bw < sz && fflag != -1) { //切换任务
  103. //ShowLog(5,"%s:tid=%lX,socket=%d,yield bw=%d/%d",__FUNCTION__,pthread_self(),socket,bw,sz);
  104.                     i=yield(socket,1,0);
  105.                     if(i<0) {
  106.                         fcntl(socket,F_SETFL,fflag);
  107.                         fflag = -1;
  108.                     }
  109.                 }
  110.         }
  111.         if(fflag != -1)  fcntl(socket,F_SETFL,fflag);
  112.         return bcount==0?-1:bcount;
  113. }

复制代码

论坛徽章:
12
2015年辞旧岁徽章
日期:2015-03-03 16:54:1515-16赛季CBA联赛之同曦
日期:2017-03-17 19:13:162016科比退役纪念章
日期:2016-11-07 08:28:12luobin
日期:2016-06-17 17:46:36wusuopu
日期:2016-06-17 17:43:4515-16赛季CBA联赛之福建
日期:2016-01-14 12:49:22程序设计版块每日发帖之星
日期:2015-12-13 06:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:00程序设计版块每日发帖之星
日期:2015-06-08 22:20:002015年亚洲杯之科威特
日期:2015-03-24 14:21:272015年迎新春徽章
日期:2015-03-04 09:57:092016科比退役纪念章
日期:2018-04-10 16:20:18
发表于 2016-06-03 10:23 |显示全部楼层
回复 7# yulihua49


    曾经项目里边确实就是同步收,不过 cpu 跑满了,当时还被组长强制科普什么是非阻塞。。。

    我觉得要是不同步收的话,用 6 楼说的办法,应该也行。找时间尝试一下。

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
发表于 2016-06-03 10:29 |显示全部楼层
本帖最后由 yulihua49 于 2016-06-03 10:35 编辑
VIP_fuck 发表于 2016-06-01 23:07
哦,,,,也可以记录每个连接上读取的是第几块数据块,读取了多少数据,这样的话,每个线程都不断片儿了。 ...

用法是这样的,缺省情况,yield函数是NULL,这个就是同步阻塞的,项目一开始先实现简单的功能,如果将来需要异步,写一个协程接口,set到yield,就自动成了异步接口。

typedef int (*T_YIELD)(int socket,int rwflg,int timeout);

可以看出,yield接口非常简单,只要求你的socket,rwflag,0=r,1=w。timeout,in second。返回值0,已经成功resume,其他失败。超时-11。
与协程的实现方法无关,不管你采用什么方法实现按这个接口走就行了。

论坛徽章:
15
射手座
日期:2014-11-29 19:22:4915-16赛季CBA联赛之青岛
日期:2017-11-17 13:20:09黑曼巴
日期:2017-07-13 19:13:4715-16赛季CBA联赛之四川
日期:2017-02-07 21:08:572015年亚冠纪念徽章
日期:2015-11-06 12:31:58每日论坛发贴之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-08-04 06:20:00程序设计版块每日发帖之星
日期:2015-07-12 22:20:002015亚冠之浦和红钻
日期:2015-07-08 10:10:132015亚冠之大阪钢巴
日期:2015-06-29 11:21:122015亚冠之广州恒大
日期:2015-05-22 21:55:412015年亚洲杯之伊朗
日期:2015-04-10 16:28:25
发表于 2016-06-03 10:39 |显示全部楼层
VIP_fuck 发表于 2016-06-01 23:04
假设一个场景:一个大型的文件,比如几个 G,发送端把文件拆分成块,每块 32M,多线程发送。

接收端接收 ...

这就需要contex了,保持一个任务的上下文。在协程(coroutine)里,上下文就是在线程栈里,协程自动保存。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP