Csocket非阻塞模式
一、前言
初期学习socket的时候,为了方便理解,使用默认的阻塞模式比较多。而实际做项目时,我们必须考虑程序的并发性,非阻塞模式在其中担任着很重要的角色,是必会的点之一。本文不对阻塞IO和非阻塞IO的概念做说明,不了解的请自行了解。下文代码以linux平台为例。
二、设置非阻塞模式
设置非阻塞模式,通过fcntl方法设置,为了保存socket其他设置,一般选择先获取statusflags,并在其基础上设置ONONBLOCK属性,代码如下:intflagsfcntl(fd,FGETFL,0);fcntl(fd,FSETFL,flagsONONBLOCK);
fcntl失败返回值为1,同时errno会被设置成对应的错误码。(errno在此不做说明,不了解的自行了解。)考虑失败的情况,个人注意到网上有些例子(包括sslibev项目)在FGETFL失败后,给了flags默认值,代码如下:intflags;if(1(flagsfcntl(fd,FGETFL,0))){flags0;}fcntl(fd,FSETFL,flagsONONBLOCK);
经过测试,默认情况下,flags得到的值为2,也就是ORDWR读写,而0对应的相关宏为ORDONLY只读,明显不合理。个人感觉,对于一个正常的socket来说,FGETFL出错的机会不大吧,至少我是没遇到过。如果实在出错了,还是建议走错误流程而不是给个默认值。
三、非阻塞server
server端通常在accept后,我们为客户端连接的fd设置为非阻塞。设置ONONBLOCK后,recv和send发生了变化。默认阻塞模式下,recv在没有数据可以接收(对方未发数据,或者缓冲区的数据已读完对方没有继续发)情况下,recv会阻塞等待,直到下次有数据发送过来。而非阻塞模式下,recv在没有数据可以接收的时候,recv会直接返回1,同时errno会被设置为EAGAINEWOULDBLOCK。同理,非阻塞send也会在对方缓冲区满的情况下直接返回1并设置errno,而不是阻塞等待。非阻塞模式下server代码大致如下:intclifdaccept(listenfd,NULL,NULL);。。。省略出错判断和设置非阻塞。。。while(true){intnrecv(clifd,buf,buflen,0);if(n0){if(errnoEAGAINerrnoEWOULDBLOCK){无数据,做其他业务逻辑或继续下一轮逻辑,这里sleep一秒并接着等数据sleep(1);continue;}else{错误,可利用errno判断出错原因,这里直接结束close(clifd);break;}}elseif(n0){对方关闭close(clifd);break;}正常读到数据,处理buf}
四、非阻塞client
client除了在sendrecv,还可以在connect前设置非阻塞模式,这样在connect时候可以直接返回。
client非阻塞connect的时候,如果返回0表示连接成功,如果返回1,则需要判断errno是否为EINPROGRESS,EINPROGRESS表示非阻塞连接不能立刻获取connect结果,后面可使用selectpollepoll等对socket可写性进行判断,如果socket已可写,使用getsockopt(iSocket,SOLSOCKET,SOERROR,err,len)进行判断好像挺麻烦是不是,但是我还是建议在大部分项目中connect前设置非阻塞(小工具之类的就无所谓了,项目中一定要保证效率)。如果使用阻塞模式,有可能的问题:如果是桌面程序,你的程序可能会卡住无响应。如果你单独为connect开一个线程,可能加大资源消耗,特别是需要connect多个对象的时候。如果你使用线程池,非阻塞connect会导致线程处理效率下降。
下面是个非阻塞connect的部分代码,使用select,至于pollepoll请自行搜索代码,跟非阻塞逻辑无关:intfd;。。。省略N行代码,socket的初始化,准备服务器信息等setnonblocking(fd);intretconnect(fd,(structsockaddr)servaddr,servlen);if(0ret){连接成功,直接进入正常业务逻辑}else{if(EINPROGRESS!errno){出错,结束close(fd);return;}EINPROGRESS继续判断}使用select对fd进行可写判断structtimevaltv;tv。tvsectimeout;tv。tvusec0;fdsetsets;FDZERO(sets);FDSET(fd,sets);retselect(fd1,NULL,wds,NULL,tv);if(ret0){if(FDISSET(fd,sets)){interr1;socklentlensizeof(int);retgetsockopt(fd,SOLSOCKET,SOERROR,err,len)if(ret0err){close(fd);return;}连接成功其实个人觉得如果可写后需要直接send,不需要getsockopt直接通过send也可以知道结果}}elseif(ret0){超时逻辑}else{select出错逻辑}