windows Socket编程之重叠IO模型

上一篇文章我们讲了EventSelect网络模型,它已经解决了等待数据到来的这一大部分时间,但是它还有一小部分时间没有节省下来。那就是把数据从网卡的缓冲区拷贝到我们应用程序的缓冲区里边。而这一篇的重叠IO模型就是将这一小部分的时间也给节省了下来。

首先,我们在主线程里边初始化网络环境,然后创建监听的socket,接下来,执行绑定,监听的操作,然后,创建一个工作者线程来对客户进行服务。执行以上操作之后呢,是一个死循环。在这个循环里边,我们首先调用accept函数来对一个客户进行连接操作。然后将该函数返回的客户端的socket保存到我们定义的一个全局socket数组里边进去。然后对我们自定义的结构体单IO操作分配一个空间,其声明如下:

typedef struct
{
   WSAOVERLAPPED overlap;	//OVERLAPPED结构,该结构里边有一个event事件对象
   WSABUF Buffer;		//WSABUF结构,里边有一个buf的大小,还有一个指针指向buf
   char szMessage[MSGSIZE];	//消息数据
   DWORD NumberOfBytesRecvd;	//保存接收到的字节数
   DWORD Flags;			//标志位
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
注意,该结构体是我们自己定义的,其中第一个参数overlapped结构体是最重要的,必须把它放在第一个声明处。因为我们要的是overlapped的地址,而其它的则是我们自己进行扩展的,大家也可以进行自己的扩展。那什么叫做单IO操作呢,比如说你请了一个人来专门打理你的店铺,你每天呢都会发一个信息给这个人,信息的内容就是叫他每天干一些指定的的事情,而这个信息就是我们这个单IO操作这个数据结构所指定的内容。

我们在堆上给我们的单IO结构分配完空间之后呢,我们来对它进行初始化,我们把Buffer里面的指针指向szMessage,把里面的大小指定为szMessage的大小。然后,调用WSACreateEvent创建一个事件对象,将这个事件对象赋予给overlappped里边的事件对象。

最后,在循环的末尾我们调用WSARecv,来对数据进行接收,其声明如下:

int WSARecv(  
  SOCKET s,                                               	//客户连接的socket
  LPWSABUF lpBuffers,                                     	//WSABUFFER指针
  DWORD dwBufferCount,                                    	//Buffer的个数,一般这里给个1
  LPDWORD lpNumberOfBytesRecvd,                           	//接收的字节数
  LPDWORD lpFlags,                                        	//标志位
  LPWSAOVERLAPPED lpOverlapped,                           	//overlaopped结构地址
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine  	//没啥用,为NULL
);
注意,该函数是异步的,也就是说它调用完就直接进行返回了,不用等待。所以整个循环,只有刚开始接收到一个客户的连接之后,我们就畅通无阻的往下执行一遍,然后再回到循环的开始继续等待客户的连接。接下来,看下工作者线程。

工作者线程,也是一个死循环,它和我们的EventSelect一样,刚开始也是调用了WSAWaitForMultipleEvents,来监控我们的socket数组哪一个有信号了,由于这个函数最多只能监控64个socket,所以我们的服务端只能同时进行64个客户的数据收发。调用完该函数之后,它会返回一个索引值,我们将调用WSAResetEvent,将我们全局event数组里边那个有信号的手动重置为无信号状态。因为我们用WSACreateEvent创建的事件对象是以手动重置的方式创建的。如果,不重置成无信号的状态,那么就像上面我们举得那个例子一样,我们请的那个人,他第二天查看信息的时候,还会继续的执行昨天的工作。

接下来,是我们重叠IO模型里边和EventSelect里边最大的不同点,我们会调用WSAGetOverlappedResult,来判断重叠IO调用是否成功,其声明如下:

BOOL WSAGetOverlappedResult(  
  SOCKET s,                      //有信号的那个socket
  LPWSAOVERLAPPED lpOverlapped,  //overlapped结构地址
  LPDWORD lpcbTransfer,          //接收的字节数
  BOOL fWait,                    //TRUE表示操作完成就返回
  LPDWORD lpdwFlags              //标志位
);
这个函数的第三个参数和我们的WSARecv的第四个参数是一样的,操作系统会改写这个值,若该值为0表示客户端断开连接或该数据传输失败了。如果没有失败,我们就将数据保存到我们那个单IO结构里边的szMessage数组里边。然后再次调用WSARecv,告诉操作系统继续帮我们监控这个socket。
以下是重叠IO的实例代码:

#include 
#include 
#define PORT 6000
#define MSGSIZE 1024
#pragma comment (lib, "Ws2_32.lib")
BOOL WinSockInit()
{
	WSADATA data = {0};
	if(WSAStartup(MAKEWORD(2, 2), &data))
		return FALSE;
	if ( LOBYTE(data.wVersion) !=2 || HIBYTE(data.wVersion) != 2 ){
		WSACleanup();
		return FALSE;
	}
	return TRUE;
}

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()
{
	SOCKET sListen, sClient;
	SOCKADDR_IN local, client;
	DWORD dwThreadId;
	int iaddrSize = sizeof(SOCKADDR_IN);
	// 初始化环境
	WinSockInit();
	// 创建监听socket
	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);
	// 创建工作者线程
	CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
	while (TRUE)
	{
	    // 接受连接
	    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;

	    // 分配一个单io操作数据结构
	    g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
	    GetProcessHeap(),
	    HEAP_ZERO_MEMORY,
	    sizeof(PER_IO_OPERATION_DATA));
	    //初始化单io结构
	    g_pPerIODataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
	    g_pPerIODataArr[g_iTotalConn]->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->szMessage;
	    g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();
	    // 开始一个异步操作
	    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++;
	}

	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);
		if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
			continue;
		index = ret - WSA_WAIT_EVENT_0;
		//手动设置为无信号
		WSAResetEvent(g_CliEventArr[index]);
		//判断该重叠调用到底是成功,还是失败
		WSAGetOverlappedResult(
		g_CliSocketArr[index],
		&g_pPerIODataArr[index]->overlap,
		&cbTransferred,
		TRUE,
		&g_pPerIODataArr[g_iTotalConn]->Flags);
		//若调用失败
		if (cbTransferred == 0)
			Cleanup(index);//关闭客户端连接
		else
		{
			//将数据保存到szMessage里边			
			g_pPerIODataArr[index]->szMessage[cbTransferred] = '\0';
			//这里直接就转发回去了
			send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage,cbTransferred, 0);
			// 进行另一个异步操作
			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;
}

你可能感兴趣的:(windows编程)