fcntl(socket_fd, F_SETFL, fcntl (socket_fd, F_GETFL,0) | O_NONBLOCK)
非阻塞模式下错误处理:
EAGAIN和EWOULDBLOCK(windows下)错误,这表明你在非阻塞模式下调用了阻塞 *** 作,在该 *** 作没有完成就返回这个错误,关于此错误一种说法是此错误表示目前无端口可用,另一种说法说的是发送缓冲区已满,遇到这两种错误不能当作错误处理,一种处理方法是采用延时处理稍后发送/接收,另一种是在类似poll/select/epoll中继续监听下次继续发送/接收,很显然第一种方法不可取,影响性能。当发送大量数据时,可以通过缓存保存数据。如果出现EINTR错误,错误描述为Interrupted system call, *** 作也应该继续。如果recv的返回值为0,那表明连接已经断开,我们的接收 *** 作也应该结束。
发送数据:
阻塞与非阻塞send返回值没有区分,
<0,出错,
=0,连接关闭,
>0,发送数据大小。
非阻塞模式下返回值 <0时并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的, 继续发送。
阻塞模式下send会阻塞着发送数据,非阻塞模式下如果暂时无法发送数据会返回,不会阻塞着 send,因此需要循环发送。
接收数据:
阻塞与非阻塞recv返回值没有区分,
<0,出错,
=0,连接关闭,
>0,接收到数据大小,
非阻塞模式下返回 值 <0时并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况 下认为连接是正常的,继续接收。
阻塞模式下recv会阻塞着接收数据,非阻塞模式下如果没有数据会返回,不会阻塞着读,因此需要 循环读取。
连接:
TCP socket 被设为非阻塞后调用 connect ,connect 函数如果没有马上成功,会立即返回EINPROCESS(如果被中断返回EINTR) ,但 TCP 的 3 次握手还在继续进行。之后可以用 select /epoll检查连接是否建立成功(但不能再次调用connect,这样会返回错误EADDRINUSE)。
非阻塞 connect 有3 种用途:
(1). 在3 次握手的同时做一些其他的处理。
(2). 可以同时建立多个连接。
(3). 在利用 select/epoll 等待的时候,可以给 select/epoll 设定一个时间,从而可以缩短 connect 的超时时间。
使用非阻塞 connect 需要注意的问题是:
(1). 很可能 调用 connect 时会立即建立连接(比如,客户端和服务端在同一台机子上),必须处理这种情况。
(2). Posix 定义了两条与 select/epoll 和 非阻塞 connect 相关的规定:
连接成功建立时,socket 描述字变为可写。(连接建立时,写缓冲区空闲,所以可写)
连接建立失败时,socket 描述字既可读又可写。 (由于有未决的错误,从而可读又可写)
另外对于无连接的socket类型(SOCK_DGRAM),客户端也可以调用connect进行连接,此连接实际上并不建立类似SOCK_STREAM的连接,而仅仅是在本地保存了对端的地址,这样后续的读写 *** 作可以默认以连接的对端为 *** 作对象。
一、前言
初期学习socket的时候,为了方便理解,使用默认的阻塞模式比较多。而实际做项目时,我们必须考虑程序的并发性,非阻塞模式在其中担任着很重要的角色,是必会的点之一。本文不对阻塞IO和非阻塞IO的概念做说明,不了解的请自行了解。下文代码以linux平台为例。
二、设置非阻塞模式
设置非阻塞模式,通过fcntl方法设置,为了保存socket其他设置,一般选择先获取 status flags, 并在其基础上设置O_NONBLOCK属性, 代码如下:
fcntl失败返回值为-1, 同时errno会被设置成对应的错误码。(errno在此不做说明,不了解的自行了解。) 考虑失败的情况,个人注意到网上有些例子(包括ss-libev项目)在 F_GETFL 失败后,给了flags默认值,代码如下:
经过测试,默认情况下,flags得到的值为2,也就是O_RDWR 读写, 而 0 对应的相关宏为O_RDONLY只读,明显不合理。个人感觉,对于一个正常的socket来说,F_GETFL 出错的机会不大吧, 至少我是没遇到过。如果实在出错了,还是建议走错误流程而不是给个默认值。
三、 非阻塞server
server端通常在accept后,我们为客户端连接的fd设置为非阻塞。设置O_NONBLOCK后,recv和send发生了变化。默认阻塞模式下,recv在没有数据可以接收(对方未发数据,或者缓冲区的数据已读完对方没有继续发)情况下,recv会阻塞等待,直到下次有数据发送过来。而非阻塞模式下,recv在没有数据可以接收的时候, recv会直接返回-1, 同时errno会被设置为EAGAIN/EWOULDBLOCK 。同理,非阻塞send也会在对方缓冲区满的情况下直接返回-1并设置errno, 而不是阻塞等待。 非阻塞模式下server代码大致如下:
四、非阻塞client
client除了在send/recv, 还可以在connect前设置非阻塞模式,这样在connect时候可以直接返回。
client 非阻塞connect的时候,如果返回0表示连接成功,如果返回-1, 则需要判断errno 是否为EINPROGRESS,EINPROGRESS表示非阻塞连接不能立刻获取connect结果,后面可使用select/poll/epoll等对socket 可写性进行判断,如果socket已可写,使用 getsockopt(iSocket, SOL_SOCKET, SO_ERROR ,&err, &len)进行判断。。。好像挺麻烦是不是,但是我还是建议在大部分项目中connect前设置非阻塞(小工具之类的就无所谓了,项目中一定要保证效率)。如果使用阻塞模式,有可能的问题:
下面是个非阻塞connect的部分代码, 使用select, 至于poll/epoll请自行搜索代码,跟非阻塞逻辑无关:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)