windows下的IOCP
对应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);
}