C++ socket非阻塞模式

C++ socket非阻塞模式,第1张

一、前言

初期学习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请自行搜索代码,跟非阻塞逻辑无关:

1. 设置socket

int oldOption = fcntl(sockfd, F_GETFL)

int newOption = oldOption | O_NONBLOCK

//设置sockfd非阻塞

fcntl(sockfd, F_SETFL, newOption)12345

2. 执行connect

如果返回0,表示连接成功,这种情况一般在本机上连接时会出现(否则怎么可能那么快)

否则,查看error是否等于EINPROGRESS(表明正在进行连接中),如果不等于,则连接失败

int ret = connect(sockfd, (struct sockaddr*)&addr, sizeof(addr))

if(ret == 0)

{

//连接成功

fcntl(sockfd, F_SETFL, oldOption)

return sockfd

}

else if(errno != EINPROGRESS)

{

//连接没有立即返回,此时errno若不是EINPROGRESS,表明错误

perror("connect error != EINPROGRESS")

return -1

}12345678910111213141516

3. 使用select,如果没用过select可以去看看

用select对socket的读写进行监听

那么监听结果有四种可能

1. 可写(当连接成功后,sockfd就会处于可写状态,此时表示连接成功)

2. 可读可写(在出错后,sockfd会处于可读可写状态,但有一种特殊情况见第三条)

3. 可读可写(我们可以想象,在我们connect执行完到select开始监听的这段时间内,

如果连接已经成功,并且服务端发送了数据,那么此时sockfd就是可读可写的,

因此我们需要对这种情况特殊判断)

说白了,在可读可写时,我们需要甄别此时是否已经连接成功,我们采用这种方案:

再次执行connect,然后查看error是否等于EISCONN(表示已经连接到该套接字)。

4. 错误

if(FD_ISSET(sockfd, &writeFds))

{

//可读可写有两种可能,一是连接错误,二是在连接后服务端已有数据传来

if(FD_ISSET(sockfd, &readFds))

{

if(connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) != 0)

{

int error=0

socklen_t length = sizeof(errno)

//调用getsockopt来获取并清除sockfd上的错误.

if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &length) <0)

{

printf("get socket option failed\n")

close(sockfd)

return -1

}

if(error != EISCONN)

{

perror("connect error != EISCONN")

close(sockfd)

return -1

}

}

}

//此时已排除所有错误可能,表明连接成功

fcntl(sockfd, F_SETFL, oldOption)

return sockfd

}12345678910111213141516171819202122232425262728293031323334353637383940

4. 恢复socket

因为我们只是需要将连接 *** 作变为非阻塞,并不包括读写等,所以我们吃醋要将socket重新设置。

fcntl(sockfd, F_SETFL, oldOption)关于Linux命令的介绍,看看《linux就该这么学》,具体关于这一章地址3w(dot)linuxprobe/chapter-02(dot)html

原因如下:

1、调用read的时候,实际上调用的是socketchannel的read方法,而设置的阻塞模式是ServerSocketChannel的模式,所以调用read时依然会采用默认的阻塞方式。

2、默认情况下,read的方法是阻塞模式的,没有办法设置超时时间,出现客户端异常退出,当前的连接将永远等待下去。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/6954277.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-03-30
下一篇 2023-03-30

发表评论

登录后才能评论

评论列表(0条)

保存