一、前言
初期学习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请自行搜索代码,跟非阻塞逻辑无关:
提供借鉴:阻塞SOCKET的非阻塞连接//用非阻塞方式连接,这样的话,一旦连接不上,界面不会出现一段较长时间不响应。
int tcp_connect(char *ip, int port, struct timeval *timeout)
{
struct linger opt
struct sockaddr_in sin
struct linger lg
int sock
int nodelay
unsigned long ul
fd_set fdConnect
memset(&lg, 0, sizeof(struct linger))
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
if (sock == INVALID_SOCKET)
{
TRACE("socket errno:\t%d\n", WSAGetLastError())
return -1
}
ul = 1
if (ioctlsocket(sock, FIONBIO, &ul) == SOCKET_ERROR)
{
TRACE("ioctlsocket errno:\t%d\n", WSAGetLastError())
goto err_exit
}
memset(&sin, 0, sizeof(sin))
sin.sin_family = AF_INET
sin.sin_addr.S_un.S_addr = inet_addr(ip)
sin.sin_port = htons(port)
if (connect(sock, (struct sockaddr*)&sin, sizeof(sin)) == 0)
{
goto ONCE_SUC
}
FD_ZERO(&fdConnect)
FD_SET((unsigned int)sock, &fdConnect)
if (select(0, 0, &fdConnect, 0, timeout) <= 0)
{
TRACE("select errno:\t%d\n", WSAGetLastError())
goto err_exit
}
ONCE_SUC:
ul = 0
if (ioctlsocket(sock, FIONBIO, &ul) == SOCKET_ERROR)
{
TRACE("ioctlsocket errno:\t%d\n", WSAGetLastError())
goto err_exit
}
/*消除滞留*/
memset(&opt, 0, sizeof(struct linger))
if (setsockopt(sock, SOL_SOCKET, SO_LINGER, (char*)&opt, sizeof(opt)) == SOCKET_ERROR)
{
TRACE("SO_LINGER errno:\t%d\n", WSAGetLastError())
}
/*禁用合并*/
nodelay = 1
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*) &nodelay, sizeof(int)) == SOCKET_ERROR)
{
TRACE("TCP_NODELAY errno:\t%d\n", WSAGetLastError())
}
return sock
err_exit:
closesocket(sock)
return -1
}
windows平台上无论利用socket()函数还是WSASocket()函数创建的socket都是阻塞模式的:[cpp] view plain copySOCKET WSAAPI socket(
_In_ int af,
_In_ int type,
_In_ int protocol
)
SOCKET WSASocket(
_In_ intaf,
_In_ inttype,
_In_ intprotocol,
_In_ LPWSAPROTOCOL_INFO lpProtocolInfo,
_In_ GROUP g,
_In_ DWORD dwFlags
)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)