重叠IO模型
重叠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是否完成。详情参见如下代码:
/*
该程序使用重叠IO模型实现发送数据的客户端
*/#include <stdio.h>
#include <WinSock2.h>
#include <WS2tcpip.h>#define BUF_SIZE 50void 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");//该函数用于验证事件状态是否转换为signaledWSAWaitForMultipleEvents(1, //需要验证的事件数&evObj, //需要验证的事件对象的句柄的数组TRUE, //所有事件对象转换为signaled状态时便返回WSA_INFINITE, //time-outFALSE //传递TRUE时进入可等待可警告状态);//该函数用于获取WSASend函数实际发送数据的大小WSAGetOverlappedResult(hSock, //进行重叠IO的套接字的句柄&overlapped, //进行重叠IO时使用的结构体&LSendBytes, //用于保存实际传输数据的大小FALSE, //如果该函数调用时仍在进行IO,该参数为TRUE时等待IO完成NULL //如果不需要获取附加信息,传递NULL);}elseErrorHandling("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
。
下面是具体代码:
#include <stdio.h>
#include <WinSock2.h>
#include <WS2tcpip.h>#define BUF_SIZE 50DWORD 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_INCOMPLETEif (idx == WSA_IO_INCOMPLETE)printf("IO completed\n");elseprintf("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;
}