重叠IO模型

重叠IO模型

同一个线程内部向多个目标传输(或接收)数据引起的IO重叠现象称为重叠IO。调用IO函数应该立即返回,IO函数以非阻塞的模式工作。

除了IO本身,如何确定IO完成时的状态也是十分重要的。

创建重叠IO套接字

使用WSASocket函数创建重叠IO套接字。第四个参数指定``

执行重叠IO的函数

WSASendWSARecv

通过WSAGetOverlappedResult函数获得实际传输数据的大小

重叠IO的完成确认

有两种方法确认重叠IO是否完成:

  • 利用WSASendWSARecv的第六个参数,基于事件对象
  • 利用WSASendWSARecv的第七个参数,基于completion routine

第一种

使用WSAWaitForMultipleEventsWSAGetOverlappedResult函数确认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;

}

复制代码