对应Linux下的epoll,windows下也有实现IO复用的方法,那就是IOCP,又叫重叠IO,其实和异步IO没什么区别,《TCPIP网络编程》这本书也没有对重叠IP和异步IO做明确的区分。
首先,创建重叠IO的套接字。
SOKET WSAocket(int af,int type,int protocol,LPWSAPROTOCOL_INFO lpProtocolInfo,GROUP g,DWORD dwFlags);
成功时返回套接字句柄,失败时返回INVALID_SOKET。
af 协议族信息
前三个参数和普通套接字一致,后三个分别为
lpProtocolInfo 包含创建的套接字信息的WSAPROTOCOL_INFO结构体变量地址值,不需要时传递NULL。
g 为扩展函数而预约的参数,可以使用0。
dwFlags 套接字属性信息。
执行重叠IO的WSASend函数
int WSASend(SOKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesSent,DWORD dwFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
1-7参数含义分别为:
1:套接字句柄
2:WSABUF结构体变量数组的地址值,其中存有待传输数据。
3:第二个参数中的数组长度
4:用于保存实际发送字节数的变量地址值
5:用于更改数据传输特性
6:WSAOVERLAPPED结构体变量的地址值,使用事件对象,用于确认完成数据传输。
7.传入Completion Routine函数的入口地址值,可以通过该函数确认是否完成数据传输。
接下来介绍第二个结构体参数类型
typedef struct __WSABUF
{
u_long len;
char FAR*buf;
}
可见其中存有缓冲数据的内容和长度。
第六个参数结构体如下:
typedef struct _WSAOVERLAPPE
{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED,*LPWSAOVERLAPPED;
大家想,WSASEND函数中参数lpNumberOfBytesSent可以获得实际传输数据的大小,既然WSASEND能实现异步调用,那么它就要在调用后立即返回,这时候可能还没传输完呢,那么它是如何得到传输数据的大小的呢?因此,在介绍WSARecv之前,我先介绍一个小函数:
BOOL WSAGetOverlappedResult( SOCKET s, // SOCKET,不用说了 LPWSAOVERLAPPED lpOverlapped, // 这里是我们想要查询结果的那个重叠结构的指针 LPDWORD lpcbTransfer, // 本次重叠操作的实际接收(或发送)的字节数 BOOL fWait, // 设置为TRUE,除非重叠操作完成,否则函数不会返回 // 设置FALSE,而且操作仍处于挂起状态,那么函数就会返回FALSE // 错误为WSA_IO_INCOMPLETE // 不过因为我们是等待事件传信来通知我们操作完成,所以我们这里设 // 置成什么都没有作用 LPDWORD lpdwFlags // 指向DWORD的指针,负责接收结果标志 );
进行重叠IO的WSARecv函数
int WSARecv(SOCKET s,LPWSABUF lpBuffers,DWORD dwBufferCount,LPDWORD lpNumberOfBytesRecvd,LPDWORD lpFlags,LPWSAOVERLAPPED lpOverlapped,LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
参数含义与WSASend基本相同,不再赘述。
综合运用实例
#include "stdafx.h" #include <winsock2.h> #include <stdio.h> #include <iostream> using namespace::std; #define BUF_SIZE 1024 void CALLBACK ReadCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD); void CALLBACK WriteCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD); void ErrorHanding(char * message); typedef struct { SOCKET hClntSock; //套接字句柄 char buf[BUF_SIZE]; //缓冲 WSABUF wsaBuf; //缓冲相关信息 }PER_IO_DATA,*LPPER_IO_DATA; void main() { WSADATA wsaData; SOCKET hLisnSock, hRecvSock; SOCKADDR_IN lisnAdr, recvAdr; LPWSAOVERLAPPED lpOvlap; DWORD recvBytes; LPPER_IO_DATA hbInfo; int recvAdrSz; DWORD flagInfo = 0; u_long mode = 1; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)//加载库并获取库信息填至wsaData ErrorHanding("socket start error!"); //参数:协议族,套接字传输方式,使用的协议,WSA_PROTOCOL_INFO结构体地址/不需要时传null,扩展保留参数,套接字属性信息 hLisnSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); //将hLisnSock句柄的套接字I/O模式(FIONBIO)改为mode中指定的形式:非阻塞模式 ioctlsocket(hLisnSock, FIONBIO, &mode); //设置目标地址端口 memset(&lisnAdr, 0, sizeof(lisnAdr)); lisnAdr.sin_family = AF_INET; lisnAdr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); lisnAdr.sin_port = htons(6000); //套接字绑定 if (bind(hLisnSock, (SOCKADDR*)&lisnAdr, sizeof(lisnAdr)) == SOCKET_ERROR) ErrorHanding("socket bind error!"); //设置为监听模式 if (listen(hLisnSock, 5) == SOCKET_ERROR) ErrorHanding("socket listen error!"); recvAdrSz = sizeof(recvAdr); while (1) { //进入短暂alertable wait 模式,运行ReadCompRoutine、WriteCompRoutine函数 SleepEx(100, TRUE); //非阻塞套接字,需要处理INVALID_SOCKET //返回的新的套接字也是非阻塞的 hRecvSock = accept(hLisnSock, (SOCKADDR*)&recvAdr, &recvAdrSz); if (hRecvSock == INVALID_SOCKET) { //无客户端连接时,accept返回INVALID_SOCKET,WSAGetLastError()返回WSAEWOULDBLOCK if (WSAGetLastError() == WSAEWOULDBLOCK) continue; else ErrorHanding("accept() error"); } puts("Client connected"); //申请重叠I/O需要使用的结构体变量的内存空间并初始化 //在循环内部申请:每个客户端需要独立的WSAOVERLAPPED结构体变量 lpOvlap = (LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED)); memset(lpOvlap, 0, sizeof(WSAOVERLAPPED)); hbInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA)); hbInfo->hClntSock = (DWORD)hRecvSock; (hbInfo->wsaBuf).buf = hbInfo->buf; (hbInfo->wsaBuf).len = BUF_SIZE; //基于CR的重叠I/O不需要事件对象,故可以用来传递其他信息 lpOvlap->hEvent = (HANDLE)hbInfo; //接收第一条信息 WSARecv(hRecvSock, &(hbInfo->wsaBuf), 1, &recvBytes, &flagInfo, lpOvlap, ReadCompRoutine); } closesocket(hRecvSock); closesocket(hLisnSock); WSACleanup(); return; } //参数:错误信息,实际收发字节数,OVERLAPPED类型对象,调用I/O函数时传入的特性信息 void CALLBACK ReadCompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags) { //从lpoverlapped中恢复传递的信息 LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent); SOCKET hSock = hbInfo->hClntSock; LPWSABUF bufInfo = &(hbInfo->wsaBuf); DWORD sentBytes; //接收到EOF,断开连接 if (szRecvBytes == 0) { closesocket(hSock); free(hbInfo); free(lpOverlapped); puts("Client disconnected"); } else { bufInfo->len = szRecvBytes; //将接收到的信息回传回去,传递完毕执行WriteCompRoutine(): 接收信息 WSASend(hSock, bufInfo, 1, &sentBytes, 0, lpOverlapped, WriteCompRoutine); } } //参数:错误信息,实际收发字节数,OVERLAPPED类型对象,调用I/O函数时传入的特性信息 void CALLBACK WriteCompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags) { //从lpoverlapped中恢复传递的信息 LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent); SOCKET hSock = hbInfo->hClntSock; LPWSABUF bufInfo = &(hbInfo->wsaBuf); DWORD recvBytes; DWORD flagInfo = 0; //接收数据,接收完毕执行ReadCompRoutine:发送数据 WSARecv(hSock, bufInfo, 1, &recvBytes, &flagInfo, lpOverlapped, ReadCompRoutine); } void ErrorHanding(char * message) { cout << message << endl; exit(1); }