重叠IO模型

同一个线程内部向多个目标传输(或接收)数据引起的IO重叠现象称为重叠IO。调用IO函数应该立即返回,IO函数以非阻塞的模式工作。
除了IO本身,如何确定IO完成时的状态也是十分重要的。
创建重叠IO套接字
使用WSASocket
函数创建重叠IO套接字。第四个参数指定``
执行重叠IO的函数
WSASend
和WSARecv
通过WSAGetOverlappedResult
函数获得实际传输数据的大小
重叠IO的完成确认
有两种方法确认重叠IO是否完成:
- 利用
WSASend
和WSARecv
的第六个参数,基于事件对象 - 利用
WSASend
和WSARecv
的第七个参数,基于completion routine
第一种
使用WSAWaitForMultipleEvents
和WSAGetOverlappedResult
函数确认IO是否完成。详情参见如下代码:
c
/*
该程序使用重叠IO模型实现发送数据的客户端
*/
#include <stdio.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#define BUF_SIZE 50
void ErrorHandling(const char* msg);
int main(int argc, char* argv[])
{
if (argc != 3)
ErrorHandling("usage error");
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup error");
SOCKET hSock;
//创建重叠IO套接字
hSock = WSASocket(
AF_INET, SOCK_STREAM, 0,
NULL,//包含创建套接字时的信息的结构体的地址
0, //为拓展函数使用的参数
WSA_FLAG_OVERLAPPED //套接字属性信息,传递该参数可以赋予套接字重叠IO的特性
);
sockaddr_in servAdr;
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = PF_INET;
//servAdr.sin_addr.s_addr = inet_addr(argv[1]);
inet_pton(PF_INET, argv[1], &servAdr.sin_addr.s_addr);
servAdr.sin_port = htons(atoi(argv[2]));
//连接服务器
if (connect(hSock, (sockaddr*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
ErrorHandling("connect error");
int nAdrSize; //sockaddr结构体大小
//为了进行重叠IO,WSASend函数的lpOverlapped参数中应该传递具有有效的结构体变量地址
//而不是NULL,如果传递NULL,WSASend()第一个参数指定的套接字将以阻塞模式工作
//如果使用WSASend函数对不同目标进行数据传输,需要传递不同的WSAOVERLAPPED结构体
WSAOVERLAPPED overlapped;
memset(&overlapped, 0, sizeof(overlapped));
WSAEVENT evObj = WSACreateEvent();
overlapped.hEvent = evObj; //初始化该字段
WSABUF dataBuf; //包含两个字段:待传输数据大小,缓冲区地址
char msg[] = "Network is Computer";
dataBuf.len = strlen(msg);
dataBuf.buf = msg;
unsigned long LSendBytes = 0;
//使用该函数进行重叠IO--发送数据,如果WSASend函数在返回前恰好完成了数据传输
//则返回值为0,LSendBytes中将保存事件传输数据的大小,如果该函数返回时没有完成数据传输,
//则返回SOCKET_ERROR,并将WSA_IO_PENDING注册为错误代码
if (WSASend(
hSock, //需要发送信息的套接字的句柄
&dataBuf, //结构体数组首地址,该结构体中存有待传输的数据和大小
1, //第二个参数中数组的长度
&LSendBytes, //用于保存实际发送的字节数
0, //数据传输属性
&overlapped, //该结构体用于确认完成了数据传输
NULL //completion routine函数的地址,可使用该函数确认数据传输是否完成
) == SOCKET_ERROR)
{
//该函数返回时数据没有全部移动到输出缓冲中时
if (WSAGetLastError() == WSA_IO_PENDING)
{
puts("Background data send");
//该函数用于验证事件状态是否转换为signaled
WSAWaitForMultipleEvents(
1, //需要验证的事件数
&evObj, //需要验证的事件对象的句柄的数组
TRUE, //所有事件对象转换为signaled状态时便返回
WSA_INFINITE, //time-out
FALSE //传递TRUE时进入可等待可警告状态
);
//该函数用于获取WSASend函数实际发送数据的大小
WSAGetOverlappedResult(
hSock, //进行重叠IO的套接字的句柄
&overlapped, //进行重叠IO时使用的结构体
&LSendBytes, //用于保存实际传输数据的大小
FALSE, //如果该函数调用时仍在进行IO,该参数为TRUE时等待IO完成
NULL //如果不需要获取附加信息,传递NULL
);
}
else
ErrorHandling("WSASend() error");
}
printf("send data size: %d\n", LSendBytes);
WSACloseEvent(evObj);
closesocket(hSock);
WSACleanup();
return 0;
}
void ErrorHandling(const char* msg)
{
fprintf_s(stderr, "%s\n", msg);
exit(1);
}
第二种
Pending的IO完成时才会调用CR函数,只有请求IO的线程处于alertable wait状态时才可以调用CR函数。该状态是等待接收操作系统消息的线程状态。调用下列函数时进入该状态:
WaitForSingleObjectEx
WaitForMultipleObjectEx
WSAWaitForMultipleEvents
SleepEx
上述函数中的第一、二、四个与对应的非Ex函数功能相同,只是多了一个参数用于决定是否让线程进入alertable wait状态。启动重叠IO任务后,上述函数可验证IO是否完成,如果有已完成的IO任务,则会调用CR函数。调用后均返回WAIT_IO_COMPLETION
。
下面是具体代码:
c
#include <stdio.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#define BUF_SIZE 50
DWORD dwRecvBytes;
char buf[BUF_SIZE];
void ErrorHandling(const char* msg)
{
fprintf_s(stderr, "%s\n", msg);
exit(1);
}
void CALLBACK CmpRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
if (dwError != 0)
ErrorHandling("CmpRoutine() error");
else
{
dwRecvBytes = szRecvBytes;
printf_s("recv data size: %d\n", dwRecvBytes);
}
}
int main(int argc, char* argv[])
{
if (argc != 2)
ErrorHandling("Usage error");
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error");
SOCKET hServSock, hClntSock;
hServSock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
if (hServSock == INVALID_SOCKET)
ErrorHandling("WSASocket() error");
sockaddr_in servAdr, clntAdr;
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = PF_INET;
servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
servAdr.sin_port = htons(atoi(argv[1]));
if (bind(hServSock, (sockaddr*)&servAdr, sizeof(sockaddr)) == SOCKET_ERROR)
ErrorHandling("bind() error");
if (listen(hServSock, 5) == SOCKET_ERROR)
ErrorHandling("listen() error");
int szAdr, idx;
unsigned long flags;
while (1)
{
WSAEVENT evObj = WSACreateEvent();
WSAOVERLAPPED overlapped;
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = evObj;
WSABUF wsaBuf;
wsaBuf.buf = buf;
wsaBuf.len = BUF_SIZE;
flags = 0;
szAdr = sizeof(szAdr);
hClntSock = accept(hServSock, (sockaddr*)&clntAdr, &szAdr);
//最后一个参数指定CR函数,在IO完成时调用
if (WSARecv(hClntSock,&wsaBuf,1,&dwRecvBytes,&flags,&overlapped,CmpRoutine)==SOCKET_ERROR)
{
if (WSAGetLastError() == WSA_IO_PENDING)
{
printf_s("bakcground data recv");
}
}
//最后一个参数指定为TRUE,线程进入alertable wait状态
idx = WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, TRUE);
//调用CR函数后,上述函数返回WSA_IO_INCOMPLETE
if (idx == WSA_IO_INCOMPLETE)
printf("IO completed\n");
else
printf("IO error");
WSACloseEvent(evObj);
closesocket(hClntSock);
}
closesocket(hServSock);
WSACleanup();
return 0;
}
调用CR函数后,上述函数返回WSA_IO_INCOMPLETE
if (idx == WSA_IO_INCOMPLETE)
printf("IO completed\n");
else
printf("IO error");
WSACloseEvent(evObj);
closesocket(hClntSock);
}
closesocket(hServSock);
WSACleanup();
return 0;
}