- Visual C++ 2017网络编程实战
- 朱晨冰
- 1815字
- 2021-04-03 19:55:06
5.7 I/O控制命令
套接字的I/O控制主要用于设置套接字的工作模式(阻塞模式还是非阻塞模式)。另外,也可以用来获取与套接字相关的I/O操作的参数信息。
Winsock提供了函数ioctlsocket和WSAIoctl来发送I/O控制命令,前者源自Winsock1版本,后者是前者的扩展版本,源自Winsock2版本。函数ioctlsocket声明如下:
int ioctlsocket( SOCKET s, long cmd, u_long* argp);
其中,s为要设置I/O模式的套接字的描述符。cmd表示发给套接字的I/O控制命令,通常取值如下:
·FIONBIO:表示设置或清除阻塞模式的命令,当argp作为输入参数为0的时候,套接字将设置为阻塞模式;当argp作为输入参数为非0的时候,套接字将设置为非阻塞模式。有一种情况要注意:函数WSAAsynSelect会将套接字自动设置为非阻塞模式,而且如果对某个套接字调用了WSAAsynSelect函数,再想用ioctlsocket函数把套接字重新设置为阻塞模式,ioctlsocket会返回WSAEINVAL错误,此时如果想把套接字重新设置为阻塞模式,应该依旧调用WSAAsynSelect函数,并把其参数IEvent设置为0,这样套接字就又可变为阻塞模式了。大家今后在使用WSAAsynSelect函数的时候要做到心中有数,别想当然地以为通过ioctlsocket函数一定能把套接字设为阻塞模式。
·FIONREAD:用于确定套接字s自动读入数据量的命令,若s是流套接字(SOCET_STREAM)类型,则argp得到函数recv调用一次时可读入的数据量,通常和套接字中排队的数据总量相同;若s是数据报套接字(SOCK_DGRAM),则argp返回套接字排队的第一个数据报的大小。
·FIOASYNC:表示设置或清除异步I/O的命令。
argp为命令参数,是一个输入输出参数。如果函数成功就返回零,否则返回SOCKET_ERROR,此时可以用函数WSAGetLastError获取错误码。
比如下面的代码设置套接字为阻塞模式:
u_long iMode = 0; ioctlsocket(m_socket, FIONBIO, &iMode);
如果参数iMode传入的是0,就设置阻塞,否则设置为非阻塞。
函数WSAIoctl是Winsock2中的I/O控制命令函数,功能更为强大,增加了一些输入参数,添加了一些新选项,并增加了一些输出函数以获得更多的信息,函数声明如下:
int WSAIoctl( SOCKET s, DWORD dwIoControlCode, LPVOID lpvInBuffer, DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer, LPDWORD lpcbBytesReturned, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
·s:[in]套接字描述符(句柄)。
·dwIoControlCode:[in]存放用于操作的控制码,比如SIO_RCVALL(接收全部数据包的选项)。
·lpvInBuffer:[in]指向输入缓冲区地址。
·cbInBuffer:[in]输入缓冲区的字节大小。
·lpvOutBuffer:[out]指向输出缓冲区地址。
·cbOutBuffer:[in]输出缓冲区的字节大小。
·lpcbBytesReturned:[out]指向存放实际输出数据的字节大小的变量地址。
·lpOverlapped:[in]指向WSAOVERLAPPED结构体的地址(若是非重叠套接字则忽略该参数)。
·lpCompletionRoutine:[in]指向一个例程函数,该函数会在操作结束后调用(若是非重叠套接字则忽略该参数)。
如果函数成功就返回0,否则返回SOCKET_ERROR,可用WSAGetLastError获取错误码。
【例5.9】设置阻塞套接字为非阻塞套接字
(1)打开VC2017,新建一个控制台工程test。
(2)在test.cpp中输入如下代码:
#include "stdafx.h" #define _WINSOCK_DEPRECATED_NO_WARNINGS // 为了使用inet_ntoa时不出现警告 #include <Winsock2.h> #pragma comment(lib, "ws2_32.lib") //Winsock库的引入库 #include <assert.h> #include <stdio.h> int main(int argc, char* argv[]) { u_long argp; int res; char ip[] = "120.4.6.99"; //120.4.6.99是和本机同一网段的地址 ,但并不存在 int port = 13334; struct sockaddr_in server_address; // Initialize Winsock WSADATA wsaData; int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != NO_ERROR) printf("Error at WSAStartup()\n"); memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; DWORD dwIP = inet_addr(ip); server_address.sin_addr.s_addr = dwIP; server_address.sin_port = htons(port); SOCKET sock = socket(PF_INET, SOCK_STREAM, 0); assert(sock >= 0); long t1 = GetTickCount(); int ret = connect(sock, (struct sockaddr*)&server_address, sizeof(server_address)); printf("connect ret code is: %d\n", ret); if (ret == -1) { long t2 = GetTickCount(); printf("time used:%dms\n", t2 - t1); printf("connect failed...\n"); if (errno == EINPROGRESS) { printf("unblock mode ret code...\n"); } } else { printf("ret code is: %d\n", ret); } argp = 1; res = ioctlsocket(sock, FIONBIO, (u_long FAR*)&argp); if (SOCKET_ERROR == res) { printf("Error at ioctlsocket(): %ld\n", WSAGetLastError()); WSACleanup(); return -1; } puts("设置非阻塞模式后:\n"); memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; dwIP = inet_addr(ip); server_address.sin_addr.s_addr = dwIP; server_address.sin_port = htons(port); t1 = GetTickCount(); ret = connect(sock, (struct sockaddr*)&server_address, sizeof(server_address)); printf("connect ret code is: %d\n", ret); if (ret == -1) { long t2 = GetTickCount(); printf("time used:%dms\n", t2 - t1); printf("connect failed...\n"); if (errno == EINPROGRESS) { printf("unblock mode ret code...\n"); } } else { printf("ret code is: %d\n", ret); } closesocket(sock); WSACleanup(); //释放套接字库 return 0; }
在代码中,我们首先创建了一个套接字sock,刚开始默认是阻塞的,然后用connect函数去连接一个和本机IP同一子网的不真实存在的IP,会发现用了20多秒。接着我们用ioctlsocket函数把套接字sock设置为非阻塞,再同样用connect函数去连接一个和本机IP同一子网的不真实存在的IP,会发现connect立即返回了,这就说明我们设置套接字为非阻塞成功了。
(3)保存工程并运行,运行结果如图5-17所示。
![](https://epubservercos.yuewen.com/F84FA5/18225432201803906/epubprivate/OEBPS/Images/Figure-P196_26247.jpg?sign=1739365906-1zsnrsyg84ihbbx9UPjP6utyhZDbSYI1-0-dac56d18e4339711d72924caf6a1a871)
图5-17
可以看到,大概等了20多秒后才提示connect失败。
把套接字设为非阻塞模式后,很多winsock api函数就会立即返回,但并不意味着操作已经完成。我们可以通过下例感受这一点。
函数WSAIoctl主要用于控制套接字的工作模式,声明如下:
int WSAIoctl( SOCKET s, DWORD dwIoControlCode, LPVOID lpvInBuffer,DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer,LPDWORD lpcbBytesReturned, LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
其中,参数s表示套接字描述符;dwIoControlCode表示要执行操作的控制码;lpvInBuffer指向输入缓冲区;cbInBuffer表示输入缓冲区的字节大小;lpvOutBuffer[out]指向输出缓冲区;cbOutBuffer表示输出缓冲区的字节大小;lpcbBytesReturned[out]指向实际输出数据的字节大小;lpOverlapped指向WSAOVERLAPPED结构,该参数用于重叠套接字,非重叠套接字则忽略该参数;lpCompletionRoutine指向操作结束后调用例程,非重叠套接字则忽略该参数。如果函数执行成功就返回0,否则返回SOCKET_ERROR,此时可以用WSAGetLastError获取错误码。
需要注意的是,当套接字处于阻塞模式时,该函数可能阻塞线程;若套接字处于非阻塞模式且指定的操作不能及时完成时,WSAGetLastError将返回WSAEWOULDBLOCK错误码,此时程序可以将套接字改为阻塞模式后再次发送请求。