SOCKET 重叠IO模型一例

内容纲要
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

#define PORT		5150
#define MSGSIZE		1024

#pragma comment(lib, "ws2_32.lib")

typedef enum
{
	RECV_POSTED
}OPERATION_TYPE;       //枚举,表示状态

typedef struct
{
	WSAOVERLAPPED   overlap;
	WSABUF          Buffer;
	char            szMessage[MSGSIZE];
	DWORD           NumberOfBytesRecvd;
	DWORD           Flags;
	OPERATION_TYPE OperationType;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;    //定义一个结构体保存IO数据

DWORD WINAPI WorkerThread(LPVOID);

int main()
{
	WSADATA                  wsaData;
	SOCKET                   sListen, sClient;
	SOCKADDR_IN              local, client;
	DWORD                    i, dwThreadId;
	int                      iaddrSize = sizeof(SOCKADDR_IN);
	HANDLE                   CompletionPort = INVALID_HANDLE_VALUE;
	SYSTEM_INFO              systeminfo;
	LPPER_IO_OPERATION_DATA  lpPerIOData = NULL;

	//初始化Socket
	WSAStartup(0x0202, &wsaData);

	// 初始化完成端口
	CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

	// 有几个CPU就创建几个工作者线程
	GetSystemInfo(&systeminfo);
	for(i = 0; i < systeminfo.dwNumberOfProcessors; i++)
	{
		CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &dwThreadId);
	}

	// 创建套接字
	sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	// 绑定套接字
	local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	local.sin_family = AF_INET;
	local.sin_port = htons(PORT);
	bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));

	// 开始监听!
	listen(sListen, 3);

	//主进程的这个循环中循环等待客户端连接,若有连接,则将该客户套接字于完成端口绑定到一起
	//然后开始异步等待接收客户传来的数据。
	while (TRUE)
	{
		// 如果接到客户请求连接,则继续,否则等待。
		sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
		//client中保存用户信息。
		printf("Accepted client:%s:%dn", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

		//将这个最新到来的客户套接字和完成端口绑定到一起。
		//第三个参数表示传递的参数,这里就传递的客户套接字地址。
		//最后一个参数为0 表示有和CPU一样的进程数。即1个CPU一个线程
		CreateIoCompletionPort((HANDLE)sClient, CompletionPort, ( ULONG_PTR)sClient, 0);

		// 初始化结构体 使用堆内存分配
		lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PER_IO_OPERATION_DATA));
		lpPerIOData->Buffer.len = MSGSIZE; // len=1024
		lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
		lpPerIOData->OperationType = RECV_POSTED; //操作类型

		WSARecv(sClient,      //异步接收消息,立刻返回。
			&lpPerIOData->Buffer,   //获得接收的数据
			1,        //The number of WSABUF structures in the lpBuffers array.
			&lpPerIOData->NumberOfBytesRecvd, //接收到的字节数,如果错误返回0
			&lpPerIOData->Flags,   //参数,先不管
			&lpPerIOData->overlap,   //输入这个结构体咯。
			NULL);
	}	


	//向每个工作者线程都发送―个特殊的完成数据包。该函数会指示每个线程都“立即结束并退出”.
	PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);
	CloseHandle(CompletionPort);
	closesocket(sListen);
	WSACleanup();

	return 0;
}

//工作者线程有一个参数,是指向完成端口的句柄
DWORD WINAPI WorkerThread(LPVOID CompletionPortID)
{
	HANDLE                   CompletionPort = (HANDLE)CompletionPortID;
	DWORD                    dwBytesTransferred;
	SOCKET                   sClient = INVALID_SOCKET;
	LPPER_IO_OPERATION_DATA  lpPerIOData = NULL;

	while (TRUE)
	{
		BOOL bRet = GetQueuedCompletionStatus(    //遇到可以接收数据则返回,否则等待
			CompletionPort,
			&dwBytesTransferred,				//返回的字数
			(PULONG_PTR) &sClient,				//是响应的哪个客户套接字?
			(LPOVERLAPPED *)&lpPerIOData,		//得到该套接字保存的IO信息
			INFINITE);							//无限等待咯。不超时的那种。

		if (!bRet)
		{
			DWORD dwErrCode = GetLastError();		
			continue;
		}

		if (dwBytesTransferred == 0xFFFFFFFF)
		{
			return 0;
		}

		if(lpPerIOData->OperationType == RECV_POSTED) //如果收到数据
		{
			if (dwBytesTransferred == 0)
			{
				//失去客户端连接
				closesocket(sClient);
				HeapFree(GetProcessHeap(), 0, lpPerIOData);        //释放结构体
			}
			else
			{
				lpPerIOData->szMessage[dwBytesTransferred] = '\0';
				send(sClient, lpPerIOData->szMessage, dwBytesTransferred, 0); //将接收到的消息返回

				// Launch another asynchronous operation for sClient
				memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));
				lpPerIOData->Buffer.len = MSGSIZE;
				lpPerIOData->Buffer.buf = lpPerIOData->szMessage;
				lpPerIOData->OperationType = RECV_POSTED;
				WSARecv(sClient,               //循环接收
					&lpPerIOData->Buffer,
					1,
					&lpPerIOData->NumberOfBytesRecvd,
					&lpPerIOData->Flags,
					&lpPerIOData->overlap,
					NULL);
			}
		}
	}

	return 0;
}

发表回复