Win-服务器端 IOCP模型

Win-服务器端 IOCP模型_第1张图片
1、IOCP概念
输入输出完成端口(Input/()utputCompletionPort,IOCP),是支持多个同时发生的异步I/O操作的应用程序编程接口,在WindowsNT的3.5版本以后,或AIX5版以后或Solaris第十版以后,开始支持。IOCP特别适合c/s模式网络服务器端模型。因为,让每一个socket有一个线程负责同步(阻塞)数据处理,one-thread-per-client的缺点是:一是如果连入的客户多了,就需要同样多的线程;二是不同的socket的数据处理都要线程切换的代价。

2、IOCP原理
通常的办法是,线程池中的工作线程的数量与CPU内核数量相同,以此来最小化线程切换代价。一个IOCP对象,在操作系统中可关联着多个Socket和(或)文件控制端。IOCP对象内部有一个先进先出(FIFO)队列,用于存放IOCP所关联的输入输出端的服务请求完成消息。请求输入输出服务的进程不接收10服务完成通知,而是检查IOCP的消息队列以确定10请求的状态。(线程池中的)多个线程负责从IOCP消息队列中取走完成通知并执行数据处理;如果队列中没有消息,那么线程阻塞挂起在该队列。这些线程从而实现了负载均衡。

3、内部结构
Windows中利用CreateIoCompletionPort命令创建完成端口对象时,统内部为该对象自动创建了5个数据结构,分别是:
设备列表(DeviceList)
IO完成请求队列(I/OCompletionQueue-FIFO)
等待线程队列(WaitingThreadList-LIFO)
释放线程队列(ReleasedThreadList)
暂停线程队列(PausedThreadList)

4、完成端口实现流程
1、创建一个完成端口CreateIoCompletionPort()
2、根据OS中CPU核心数量建立对应Worker线程
3、创建一个用于监听socket,绑定到完成端口上,然后开始在指定的端口上监听客户端连接请求
4、必须在监听socket上投递AcceptEx请求(mswsock.dIl mswsock.lib等)
5、Worker线程干的事情:使用GetQueuedCompletionStatus()监控完成端口
6、当接收到AcceptEx通知时 _DoAccept0
7、当收到Recv通知时,_DoRecv0
8、关闭完成端口

5、Win-服务器端 IOCP模型_第2张图片

6、IOCPServer.cpp

#include 
#include 
#include 
#include 
using namespace std;

// IOCP需要调用到动态链接库
#pragma comment(lib,"Kernel32.lib")
// Socket编程需要用的动态连接库
#pragma comment(lib,"Ws2_32.lib")

const int g_DataBufferSize = 2048;			// 服务器端口
typedef struct{
     
	OVERLAPPED overLapped;
	WSABUF dataBuffer;
	char buffer[g_DataBufferSize];
	int bufferLength;
	int operationType;
}PER_IO_OPERATEION_DATA,*LPPER_IO_OPERATION_DATA,*LPPER_IO_DATA,PER_IO_DATA;

typedef struct{
     
	SOCKET socket;
	SOCKADDR_STORAGE clientAddr;
	char *pszClientName;
}PER_HANDLE_DATA,*LPPER_HANDLE_DATA;

// 设置自定义全局变量
HANDLE ComletionPort = NULL;
HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);

const int g_DefaultPort = 6688;
SOCKET serverSocket;			// 服务器socket
vector<PER_HANDLE_DATA*> g_clientGroup;		// 专用于记录客户端向量组

// 设置自定义函数声明
BOOL InitNetwork(WORD port);	// 初始化网络相关操作
BOOL InitWorkThread();		// 初始化工作者线程
DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID);		// 工作者线程处理函数
DWORD WINAPI ServerSendThread(LPVOID lpParam);	// 发送消息的函数
DWORD WINAPI AddClient(LPVOID lpParam);		// 向服务器添加客户端
BOOL DeleteClient(vector < PER_HANDLE_DATA*>&g_clientGroup, SOCKET&clientSocket);		// 向服务器删除客户端(退出)
int myStringLen(const char *str);	// 字符串处理函数


int _tmain(int argc, _TCHAR* argv[])
{
     
	if (!InitNetwork(g_DefaultPort))
		return -1;

	if (!InitWorkThread())  // 错误点
		return -1;
	HANDLE sendThread = CreateThread(NULL, 0, ServerSendThread, 0, 0, NULL);
	HANDLE addThread = CreateThread(NULL, 0, AddClient, 0, 0, NULL);
	WaitForSingleObject(sendThread, INFINITE);
	WaitForSingleObject(addThread, INFINITE);

	system("pause");

	return 0;
}

int myStringLen(const char *str)
{
     
	int i = 0;
	while (*str != '\0')
	{
     
		i++;
		str++;
	}
	return i;
}

BOOL InitNetwork(WORD port)	// 初始化网络相关操作
{
     
	WORD wVersionRequested = MAKEWORD(2, 2);
	WSADATA wsaData;
	DWORD Error = WSAStartup(wVersionRequested, &wsaData);
	
	if (Error != 0)
	{
     
		printf("\n初始化请求动态连接库失败.\n\n");
		return FALSE;
	}

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
     
		WSACleanup();
		cerr << "\n请求的版本不是2.2版本.\n\n";
		return FALSE;
	}

	// 建立服务器socket
	serverSocket = socket(AF_INET, SOCK_STREAM, 0);	// TCP协议流式套接字
	SOCKADDR_IN serverAddress;
	serverAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	serverAddress.sin_family = AF_INET;
	serverAddress.sin_port = htons(port);

	int bindResult = bind(serverSocket, (SOCKADDR*)&serverAddress, sizeof(SOCKADDR));
	if (bindResult == SOCKET_ERROR)
	{
     
		cerr << "\n绑定服务器失败:" << GetLastError() << endl;
		return FALSE;
	}

	// 将SOCKET套接字设置为监听模式
	int listenResult = listen(serverSocket, 100);
	if (SOCKET_ERROR == listenResult)
	{
     
		cerr << "\n服务器进入监听模式失败:" << GetLastError() << endl;
		return FALSE;
	}

	cout << "\n\n\tIOCP模型服务器端已准备就绪,正在等待客户端连接..........\n" << endl;

	return TRUE;
}

BOOL InitWorkThread()		// 初始化工作者线程
{
     
	// 首先创建IOCP内核对象
	HANDLE completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if (NULL == completionPort)	// 判断创建IO内核对象是否成功,失败就执行如下语句块
	{
     
		cerr << "创建IO完成端口失败:" << GetLastError() << endl;
		return FALSE;
	}

	// 创建IOCP线程(线程里面创建线程池,并且将完成端口传递到内核该线程) 内核:单核 双核 微内核
	SYSTEM_INFO mySystemInfo;
	GetSystemInfo(&mySystemInfo); // 确定处理器的核心数量
	
	// 根据CPU的核心数量创建对应线程
	for (DWORD i = 0; i < (mySystemInfo.dwNumberOfProcessors * 2 + 2); i++)
	{
     
		HANDLE threadHandle = CreateThread(NULL, 0, ServerWorkThread, completionPort, 0, NULL);
		if (NULL == threadHandle)
		{
     
			cerr << "创建线程失败:" << GetLastError() << endl;
			return FALSE;
		}
		CloseHandle(threadHandle);
	}

	return TRUE;
}

DWORD WINAPI ServerWorkThread(LPVOID lpParam) //工作者线程处理函数
{
     
	ComletionPort = (HANDLE)lpParam;
	DWORD BytesTransferred;
	LPOVERLAPPED IpOverlapped;
	LPPER_HANDLE_DATA PerHanleData = NULL;
	LPPER_IO_DATA PerIoData = NULL;
	DWORD RecvBytes;
	DWORD Flags = 0;
	BOOL bRet = false;

	while (true)
	{
     
		bRet = GetQueuedCompletionStatus(ComletionPort, &BytesTransferred, (PULONG_PTR)&PerHanleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE);
		if (bRet == 0)
		{
     
			cerr << "\n获取完成端口状态发生失败:" << GetLastError() << endl;
			DeleteClient(g_clientGroup, PerHanleData->socket);
			continue;
		}


		PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overLapped);

		// 检查在套接字上是否有错误发生
		if (BytesTransferred == 0)
		{
     
			closesocket(PerHanleData->socket);
			GlobalFree(PerHanleData);
			GlobalFree(PerIoData);
			continue;
		}

		// 开始处理数据信息,接收来自客户端的数据
		WaitForSingleObject(hMutex, INFINITE);
		SOCKET clientsock = NULL;
		cout << "有客户端数据为:" << PerIoData->dataBuffer.buf << endl;
		ReleaseMutex(hMutex);

		// 为下一个重叠调用建立单个IO操作数据
		ZeroMemory(&(PerIoData->overLapped), sizeof(OVERLAPPED));
		PerIoData->dataBuffer.len = 1024;
		PerIoData->dataBuffer.buf = PerIoData->buffer;
		PerIoData->operationType = 0;		// 读操作
		WSARecv(PerHanleData->socket, &(PerIoData->dataBuffer), 1, &RecvBytes, &Flags, &(PerIoData->overLapped), NULL);
	}

	return TRUE;
}

BOOL DeleteClient(vector < PER_HANDLE_DATA*>&g_clientGroup, SOCKET&clientSocket)	// 向服务器删除客户端(退出)
{
     
	for (int i = 0; i < g_clientGroup.size(); i++)
	{
     
		if (g_clientGroup[i]->socket == clientSocket)
		{
     
			g_clientGroup.erase(g_clientGroup.begin() + i);
			cout << "已监测到客户端退出.\n\n";
			return TRUE;
		}
	}

	return FALSE;
}

DWORD WINAPI ServerSendThread(LPVOID lpParam)	// 发送消息的函数
{
     
	while (1)
	{
     
		char data[2048];
		gets_s(data);
		int len;
		for (len = 0; data[len] != '\0'; len++);
		
		data[len] = '\n';
		data[++len] = '\0';
		cout << "数据为:" << data << g_clientGroup.size() << endl;

		WaitForSingleObject(hMutex, INFINITE);
		for (int i = 0; i < g_clientGroup.size(); ++i)
		{
     
			//发送数据
			send(g_clientGroup[i]->socket, data, 2048, 0);
		}
		ReleaseMutex(hMutex);
	}

	return 0;
}

DWORD WINAPI AddClient(LPVOID lpParam)		// 向服务器添加客户端
{
     
	while (true)
	{
     
		PER_HANDLE_DATA *PerHandleData = NULL;
		SOCKADDR_IN saRemote;
		int RemoteLen;
		SOCKET acceptSocket;

		RemoteLen = sizeof(saRemote);
		acceptSocket = accept(serverSocket, (SOCKADDR*)&saRemote, &RemoteLen);
		if (acceptSocket == SOCKET_ERROR)
		{
     
			cerr << "接收客户端失败:" << GetLastError() << endl;
			return -1;
		}

		PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
		PerHandleData->socket = acceptSocket;
		memcpy(&PerHandleData->clientAddr, &saRemote, RemoteLen);
		g_clientGroup.push_back(PerHandleData); // 将单个客户端数据指针放到客户端组当中

		CreateIoCompletionPort((HANDLE)(PerHandleData->socket), ComletionPort, (DWORD)PerHandleData, 0);

		LPPER_IO_OPERATION_DATA PerIoData = NULL;
		PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA));
		PerIoData->dataBuffer.len = 2048;
		PerIoData->dataBuffer.buf = PerIoData->buffer;
		PerIoData->operationType = 0; // 读取操作

		DWORD RecvBytes;
		DWORD Flags = 0;
		WSARecv(PerHandleData->socket, &(PerIoData->dataBuffer), 1, &RecvBytes, &Flags, &(PerIoData->overLapped), NULL);
	}
}

7、IOCPClient.cpp

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

#pragma comment(lib,"Ws2_32.lib")		// socket编程需要使用到动态链接库

SOCKET sockClient;		// 连接成功后套接字
HANDLE bufferMutex;		// 将其互斥成功正常通信的信息量句柄
const int DefaultPort = 6688;	// 端口号

DWORD WINAPI SendMsgThreadFunc(LPVOID lpParameter);	// 发送
DWORD WINAPI RecvMsgThreadFunc(LPVOID lpParameter);	// 接收

int _tmain(int argc, _TCHAR* argv[])
{
     
	// 加载socket动态链接库dll
	WORD wVersionRequested;
	WSADATA wsaData;	// 这个结构体类型用于接收windows socket的结构信息
	wVersionRequested = MAKEWORD(2, 2);
	int iError = WSAStartup(wVersionRequested, &wsaData);
	if (iError != 0)	// 如果返回0,则表示成功申请WSAStartup
		return -1;

	// 检查版本号是否正确
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
     
		WSACleanup();
		return -1;
	}

	sockClient = socket(AF_INET, SOCK_STREAM, 0);
	if (sockClient == INVALID_SOCKET)
	{
     
		printf("Error socket():%ld\n",WSAGetLastError());
		return -1;
	}

	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(DefaultPort);

	while (SOCKET_ERROR == connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
     
		// 如果未连接上服务器,则要求重新连接
		cout << "服务器连接失败,是否重新连接......?(Y/N):";
		char ch;
		while (cin >> ch && (!((ch != 'Y' && ch == 'N') || (ch == 'Y' && ch != 'N'))))
		{
     
			cout << "你输入错误,请重新输入:";
			cin.sync();
			cin.clear();
		}

		if (ch == 'Y')
		{
     
			continue;
		}
		else
		{
     
			cout << "退出系统当中......\n";
			system("pause");
			return 0;
		}
	}

	cin.sync();
	cout << "客户端已准备完毕,可以直接输入数据向服务器发送请求:\n";
	send(sockClient, "\nA Client Has Enter......\,", 200, 0);

	bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);

	HANDLE sendThread = CreateThread(NULL, 0, SendMsgThreadFunc, NULL, 0, NULL);
	HANDLE recvThread = CreateThread(NULL, 0, RecvMsgThreadFunc, NULL, 0, NULL);

	WaitForSingleObject(sendThread, INFINITE);
	closesocket(sockClient);
	CloseHandle(sendThread);
	CloseHandle(recvThread);
	CloseHandle(bufferMutex);
	WSACleanup();

	system("pause");

	return 0;
}

DWORD WINAPI SendMsgThreadFunc(LPVOID lpParameter)	// 发送
{
     
	while (1)
	{
     
		string  strtemp;
		getline(cin, strtemp);
		WaitForSingleObject(bufferMutex, INFINITE);	// p(资源未被占用)
		if ("exit" == strtemp || "EXIT" == strtemp){
     
			strtemp.push_back('\0');
			send(sockClient, strtemp.c_str(), 200, 0);
			break;
		}
		else
		{
     
			strtemp.append("\n");
		}

		printf("\n客户端数据消息(若输入exit 或 EXIT)退出程序:");
		cout<<strtemp;
		send(sockClient, strtemp.c_str(), 200, 0);
		ReleaseSemaphore(bufferMutex, 1, NULL);		// V(资源占用完毕)
	}

	return 0;
}

DWORD WINAPI RecvMsgThreadFunc(LPVOID lpParameter)	// 接收
{
     
	while (1)
	{
     
		char recvBuf[200];
		recv(sockClient, recvBuf, 200, 0);
		WaitForSingleObject(bufferMutex, INFINITE);	// P(资源未被占用)
		printf("%s Datas: %s","Server",recvBuf);	// 接收消息
		ReleaseSemaphore(bufferMutex, 1, NULL);		// V(资源占用完毕)
	}

	return 0;
}

你可能感兴趣的:(C++,Socket网络编程)