淘先锋技术网

首页 1 2 3 4 5 6 7

关键函数 int WSARecv(
SOCKET s,// 当然是投递这个操作的套接字
LPWSABUF lpBuffers, // 接收缓冲区,与Recv函数不同
// 这里需要一个由WSABUF结构构成的数组
DWORD dwBufferCount, // 数组中WSABUF结构的数量
LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,这里会返回函数调用所接收到的字节数
LPDWORD lpFlags,// 一个指向标志位的指针
LPWSAOVERLAPPED lpOverlapped,// “绑定”的重叠结构
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine// 完成例程中将会用到的参数
);


返回值:
WSA_IO_PENDING : 最常见的返回值,这是说明我们的WSARecv操作成功了,但是I/O操作还没有完成,所以我们就需要绑定一个事件来通知我们操作何时完成

#include <winsock2.h>
#include <stdio.h>

#define PORT    5150
#define MSGSIZE 1024

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

typedef struct
{
	WSAOVERLAPPED overlap;
	WSABUF        Buffer;
	char          szMessage[MSGSIZE];
	DWORD         NumberOfBytesRecvd;
	DWORD         Flags;					//完成状态的附加标志位
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;	//重叠结构,用于接收通知时获取数据。

int                     g_iTotalConn = 0;
SOCKET                  g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];	//
WSAEVENT                g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];

DWORD WINAPI WorkerThread(LPVOID);
void Cleanup(int);

int main()
{
	WSADATA     wsaData;
	SOCKET      sListen, sClient;
	SOCKADDR_IN local, client;
	DWORD       dwThreadId;
	int         iaddrSize = sizeof(SOCKADDR_IN);

	// Initialize Windows Socket library
	WSAStartup(0x0202, &wsaData);

	// Create listening socket
	sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	// Bind
	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
	listen(sListen, 3);

	// Create worker thread
	CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);

	while (TRUE)
	{
		// Accept a connection
		sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
		printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

		g_CliSocketArr[g_iTotalConn] = sClient;

		// Allocate a PER_IO_OPERATION_DATA structure
		g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
			GetProcessHeap(),
			HEAP_ZERO_MEMORY,
			sizeof(PER_IO_OPERATION_DATA));
		g_pPerIODataArr[g_iTotalConn]->Buffer.len = MSGSIZE;	//接收缓冲长度
		g_pPerIODataArr[g_iTotalConn]->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->szMessage; //接收缓冲区域
		//创建接收数据事件,绑定该socket
		g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();	

		// 异步该socket接收数据,即立刻返回,等待事件信号状态。
		WSARecv(
			g_CliSocketArr[g_iTotalConn],
			&g_pPerIODataArr[g_iTotalConn]->Buffer,
			1,
			&g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd,
			&g_pPerIODataArr[g_iTotalConn]->Flags,
			&g_pPerIODataArr[g_iTotalConn]->overlap,
			NULL);

		g_iTotalConn++;	//连接数加1
	}

	closesocket(sListen);
	WSACleanup();
	return 0;
}

DWORD WINAPI WorkerThread(LPVOID lpParam)
{
	int   ret, index;
	DWORD cbTransferred;

	while (TRUE)
	{
		ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);	//等待事件信号,1s超时
		if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
		{
			continue;
		}

		index = ret - WSA_WAIT_EVENT_0;		//取信号事件的索引号
		WSAResetEvent(g_CliEventArr[index]);	//将事件重置

		WSAGetOverlappedResult(					//返回指定套接口上一个重叠操作的结果。
			g_CliSocketArr[index],				//调用重叠操作时socket
			&g_pPerIODataArr[index]->overlap,	//指向调用重叠操作时指定的WSAOVERLAPPED结构
			&cbTransferred,				//接收实际数据长度
			TRUE,						//指定函数是否等待挂起的重叠操作结束
			&g_pPerIODataArr[g_iTotalConn]->Flags);		//该变量存放完成状态的附加标志位

		if (cbTransferred == 0)			//接收为数据长度为0,表示socket已经被关闭
		{
			// The connection was closed by client
			Cleanup(index);
		}
		else
		{
			// g_pPerIODataArr[index]->szMessage contains the received data
			g_pPerIODataArr[index]->szMessage[cbTransferred] = '\0';	//在数据末尾加结束符
			send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage,\
				cbTransferred, 0);										//原样返回

			// Launch another asynchronous operation
			WSARecv(
				g_CliSocketArr[index],
				&g_pPerIODataArr[index]->Buffer,
				1,
				&g_pPerIODataArr[index]->NumberOfBytesRecvd,
				&g_pPerIODataArr[index]->Flags,
				&g_pPerIODataArr[index]->overlap,
				NULL);
		}
	}

	return 0;
}

void Cleanup(int index)
{
	closesocket(g_CliSocketArr[index]);
	WSACloseEvent(g_CliEventArr[index]);
	HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[index]);

	if (index < g_iTotalConn - 1)
	{
		g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
		g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
		g_pPerIODataArr[index] = g_pPerIODataArr[g_iTotalConn - 1];
	}

	g_pPerIODataArr[--g_iTotalConn] = NULL;
}
这只是一个简单的模型,我们还可以改进,如accept 不能无限接受连接,最大支持64,另外,套接字绑定的事件数组可以分优先级。

这个模型与上述其他模型不同的是它使用Winsock2提供的异步I/O函数WSARecv。

a) 在调用WSARecv时,指定一个WSAOVERLAPPED结构,这个调用不是阻塞的,也就是说,它会立刻返回。一旦有数据到达的时候,被指定的WSAOVERLAPPED结构中的hEvent被Signaled。由于下面这个语句 g_CliEventArr[g_iTotalConn] =g_pPerIODataArr[g_iTotalConn]->overlap.hEvent;使得与该套接字相关联的WSAEVENT对象也被Signaled,所以WSAWaitForMultipleEvents的调用操作成功返回。

b) 我们现在应该做的就是用与调用WSARecv相同的WSAOVERLAPPED结构为参数调用WSAGetOverlappedResult,从而得到本次I/O传送的字节数等相关信息。

c)  在取得接收的数据后,把数据原封不动的发送到客户端,然后重新激活一个WSARecv异步操作;
如果本次收到数据为0,则表示该套接字的客户端主动关闭了,将该套接字从套接字集中清除,把最后一个套接字顶替到关闭的套接字位置。