当使用socket()函数和WSASocket()函数创建套接字时,默认都是阻塞的。在创建套接字之后,通过调用ioctlsocket()函数,将该套接字设置为非阻塞模式。函数的第一个参数是套接字,第二个参数设置为FIONBIO,第三个参数设置为unsigned long类型的非零值。下面代码清单演示了如何用ioctlsocket()函数,设置套接字为非阻塞模式。
SOCKET s; //套接字
unsigned long ul = 1; //设置套接字选项
int nRet; //返回值
s = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
nRet = ioctlsocket(s, FIONBIO, (unsigned long*)&ul); //设置套接字非阻塞模式
if (nRet == SOCKET_ERROR)
{
//设置套接字非阻塞模式,失败处理
}
套接字设置为非阻塞模式后,在调用Windows Sockets API函数时,调用函数会立即返回。大多数情况下,这些函数调用都会调用“失败”,并返回WSAEWOULDBLOCK错误代码。说明请求的操作在调用期间内没有时间完成。通常,应用程序需要重复调用该函数,直到获得成功返回代码。下面程序清单示例了一个在非阻塞套接字上反复调用recv()函数,直到收到1024个字节的数据。
#define NUM_REQUIRED 1024 //需要读入数据的大小
#define MAX_SIZE 2048 //输入缓冲区的大小
TCHAR buff[MAX_SIZE]; //输入缓冲区
bool close; //对方关闭了连接
SOCKET sock; //Windows sockets
void ReadData(void)
{
int nTotal = 0; //已经读入缓冲区字节数
int nRead = 0; //在调用recv时实际读入字节数
int nLeft = 0; //剩下数据的字节数
int nBytes = 0; //当前已读数据在缓冲区的位置
nLeft = NUM_REQUIRED;
while (nTotal != NUM_REQUIRED)//已经读入缓冲区的字节数不等于需要读入的大小时
{
nRead = recv(sock, &buff[MAX_SIZE - nBytes], nLeft, 0); //接收数据
if(SOCKET_ERROR == nRead) //读操作失败
{
int err = WSAGetLastError();
if(WSAEWOULDBLOCK == err) //接收数据缓冲区不可用
{
continue; //接着读取数据
}else if(WSAETIMEDOUT == err || WSAENETDOWN == err) //连接已经断开
{
close = TRUE; //函数退出
break;
}
}
if( 0 == nRead) //客户端关闭了连接
{
close = TRUE; //函数退出
break;
}
nTotal += nRead;
nLeft -= nRead;
nBytes += nRead;
}
return;
}
在该程序中,通过调用WSAGetLastError()函数获得recv()函数返回的错误代码。当返回WSAEWOULDBLOCK错误时,说明此时套接字的缓冲区还没有准备好的数据。需要继续调用该函数。
在该程序中,还对recv()函数返回的其他错误代码进行处理。WSAETIMEDOUT和WSAENETDOWN错误说明,此时由于网络系统的原因与对方的连接已经断开了。当函数返回0时,说明对方关闭了连接。在程序中通过设置close布尔变量值为TRUE,表明与对方的连接已经断开。调用break语句跳出while循环体,函数退出。在开发中,应该根据具体情况对函数返回的错误值进行具体处理。
不同的Windows Sockets API函数,在调用失败时返回的WSAEWOULDBLOCK错误代码具有不同的含义。表对几个Windows Sockets API函数返回WSAEWOULDBLOCK错误的含义进行了总结。
表 WSAEWOULDBLOCK的含义
函数名 |
说明 |
accept()和WSAAcept() |
应用程序没有收到连接请求 |
recv()、WSARecv()、recvfrom()和WSARecvfrom() |
接收缓冲区没有收到数据 |
send()、WSASend()、sendfrom()和WSASendfrom() |
发送缓冲区此时不可用 |
connect()和WSAConnect() |
连接未能立即完成 |
closescoket() |
通常情况下意味着应用程序使用SO_LINGER选项并且设置了一个非零的超时值,调用了setsocketopt()函数 |
需要说明的是并非所有的Windows Sockets API在非阻塞模式下调用,都会返回WSAEWOULDBLOCK错误。例如,以非阻塞模式的套接字为参数调用bind()函数时,就不会返回该错误代码。当然,在调用WSAStartup()函数时更不会返回该错误代码,因为该函数是应用程序第一调用的函数,当然不会返回这样的错误代码。
要将套接字设置为非阻塞模式,除了使用ioctlsocket()函数之外,还可以使用WSAAsyncselect()和WSAEventselect()函数。当调用该函数时,套接字会自动地设置为非阻塞方式。在后续章节中,讲解该函数的使用方法。