socket通信之八:完成端口模型实现的客户/服务器模型

完成端口的详细说明可以参考下面这两篇文章,理论讲的很好。

手把手教你玩转SOCKET模型:完成端口(Completion Port)详解


 完成端口(I/O completion)原理 收藏


但是本文的实现和它们还是有点区别。

这里就只列出完成端口服务器端的基本流程了。


首先,说说主线程:


1.创建完成端口对象


2.创建工作者线程(这里工作者线程的数量是按照CPU的个数来决定的,这样可以达到最佳性能)


3.创建监听套接字,绑定,监听,然后程序进入循环


4.在循环中,我做了以下几件事情:
(1).接受一个客户端连接

(2). 将该客户端套接字与完成端口绑定到一起(还是调用CreateIoCompletionPort,但这次的作用不同),注意,按道理来讲,此时传递给 CreateIoCompletionPort的第三个参数应该是一个完成键,一般来讲,程序都是传递一个单句柄数据结构的地址,该单句柄数据包含了和该 客户端连接有关的信息,由于我们只关心套接字句柄,所以直接将套接字句柄作为完成键传递;

(3).触发一个WSARecv异步调用,这次又用到了“尾随数据”,使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后,此外,还有操作类型等重要信息。


在工作者线程的循环中:


1.调用GetQueuedCompletionStatus取得本次I/O的相关信息(例如套接字句柄、传送的字节数、单I/O数据结构的地址等等)


2.通过单I/O数据结构找到接收数据缓冲区,然后将数据原封不动的发送到客户端


3.再次触发一个WSARecv异步操作


下面是完成端口,socket连接,工作线程之间的关系。



服务端代码如下,好多代码和前面重叠IO都比较类似。客户端代码和前面是一样的。

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <iostream>

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


using namespace std;

#define  PORT 6000
//#define  IP_ADDRESS "10.11.163.113"  //表示服务器端的地址
#define  IP_ADDRESS "127.0.0.1"  //直接使用本机地址

#define MSGSIZE 1024

//与重叠IO结构相关的一些信息,把它们封装在一个结构体中方便管理
class PerSocketData
{
public:
	WSAOVERLAPPED overlap;//每一个socket连接需要关联一个WSAOVERLAPPED对象
	WSABUF buffer;//与WSAOVERLAPPED对象绑定的缓冲区
	char          szMessage[MSGSIZE];//初始化buffer的缓冲区
	DWORD          NumberOfBytesRecvd;//指定接收到的字符的数目
	DWORD          flags;
	
};

//使用这个工作线程来通过重叠IO的方式与客户端通信
DWORD WINAPI workThread(LPVOID lpParam)
{
	HANDLE completionPort=(HANDLE)lpParam;
	DWORD dwBytesTransfered;
	SOCKET clientSocket;
	PerSocketData * lpIOdata=NULL;

	while(true)
	{
		GetQueuedCompletionStatus(
			completionPort,
			&dwBytesTransfered,
			 (LPDWORD)&clientSocket,
			(LPOVERLAPPED*)&lpIOdata,
			INFINITE);

		if (dwBytesTransfered==0xFFFFFFFF)
		{
			return 0;
		}
		
		if (dwBytesTransfered==0)
		{
			cout<<"客户端退出"<<endl;
			closesocket(clientSocket);
			HeapFree(GetProcessHeap(),0,lpIOdata);
		}
		else
		{
			
			cout<<lpIOdata->szMessage<<endl;

			send(clientSocket,lpIOdata->szMessage,dwBytesTransfered+1,0);//多发送一个字符,将字符串结束符也发送过去

			memset(lpIOdata,0,sizeof(PerSocketData));
			lpIOdata->buffer.len=MSGSIZE;
			lpIOdata->buffer.buf=lpIOdata->szMessage;

			WSARecv(clientSocket,
				&lpIOdata->buffer,
				1,
				&lpIOdata->NumberOfBytesRecvd,
				&lpIOdata->flags,
				&lpIOdata->overlap,
				NULL);
		}

	}

		return 0;
}


void main()
{
	
	WSADATA wsaData;
	int err;

	//1.加载套接字库
	err=WSAStartup(MAKEWORD(1,1),&wsaData);
	if (err!=0)
	{
		cout<<"Init Windows Socket Failed::"<<GetLastError()<<endl;
		return ;
	}

	//下面执行一些使用完成端口需要进行的步骤
	//创建一个完成端口
	HANDLE completionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
	PerSocketData * sockData;
	SYSTEM_INFO systeminfo;
	GetSystemInfo(&systeminfo);
	DWORD dwThreadId;
	for (int i=0;i<systeminfo.dwNumberOfProcessors;i++)
	{
		CreateThread(NULL,0,workThread,completionPort,0,&dwThreadId);
	}

	//2.创建socket
	//套接字描述符,SOCKET实际上是unsigned int
	SOCKET serverSocket;
	serverSocket=socket(AF_INET,SOCK_STREAM,0);
	if (serverSocket==INVALID_SOCKET)
	{
		cout<<"Create Socket Failed::"<<GetLastError()<<endl;
		return ;
	}


	//服务器端的地址和端口号
	struct sockaddr_in serverAddr,clientAdd;
	serverAddr.sin_addr.s_addr=inet_addr(IP_ADDRESS);
	serverAddr.sin_family=AF_INET;
	serverAddr.sin_port=htons(PORT);

	//3.绑定Socket,将Socket与某个协议的某个地址绑定
	err=bind(serverSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
	if (err!=0)
	{
		cout<<"Bind Socket Failed::"<<GetLastError()<<endl;
		return ;
	}


	//4.监听,将套接字由默认的主动套接字转换成被动套接字
	err=listen(serverSocket,10);
	if (err!=0)
	{
		cout<<"listen Socket Failed::"<<GetLastError()<<endl;
		return ;
	}

	cout<<"服务器端已启动......"<<endl;

	int addrLen=sizeof(clientAdd);
	SOCKET  sockConn;
	while(true)
	{
		//5.接收请求,当收到请求后,会将客户端的信息存入clientAdd这个结构体中,并返回描述这个TCP连接的Socket
		sockConn=accept(serverSocket,(struct sockaddr*)&clientAdd,&addrLen);
		if (sockConn==INVALID_SOCKET)
		{
			cout<<"Accpet Failed::"<<GetLastError()<<endl;
			return ;
		}
		cout<<"客户端连接:"<<inet_ntoa(clientAdd.sin_addr)<<":"<<clientAdd.sin_port<<endl;
		
		//将之前的第6步替换成了下面的操作
		CreateIoCompletionPort((HANDLE)sockConn,completionPort,(DWORD)sockConn,0);

		sockData=(PerSocketData*)HeapAlloc(
			GetProcessHeap(),
			HEAP_ZERO_MEMORY,
			sizeof(PerSocketData));
		sockData->buffer.len=MSGSIZE;
		sockData->buffer.buf=sockData->szMessage;
		
		WSARecv(
			sockConn,
			&sockData->buffer,
			1,
			&sockData->NumberOfBytesRecvd,
			&sockData->flags,
			&sockData->overlap,
			NULL);
	}

	PostQueuedCompletionStatus(completionPort,0xFFFFFFFF,0,NULL);
	CloseHandle(completionPort);
	closesocket(serverSocket);
	//7.清理Windows Socket库
	WSACleanup();
}



执行测试:

socket通信之八:完成端口模型实现的客户/服务器模型_第1张图片


可执行文件可以在这里下载,工程文件可以在这里下载。


参考:

Windows Socket五种I/O模型

你可能感兴趣的:(socket通信之八:完成端口模型实现的客户/服务器模型)