IO模型之完成端口completion port(windows上性能最好)

1. 完成端口IOCP模型

一个完成端口实际上就是一个通知队列,操作系统把已经完成的重叠I/0请求的通知放到队列中。完成端口会充分利用Windows最复杂的内核对象来进行I/O的调度,属于异步IO,是用于C/S通信模式中性能最好的网络通信模型。

2. 完成端口IOCP模型的原理

完成端口创建几个线程(系统CPU的数目、避免线程上下文切换),等到用户请求的时候,就把这些请求都加入到一个公共消息队列中去,然后这几个线程就排队从消息队列中取出消息并加以处理,这种方式就很优雅的实现了异步通信和负载均衡的问题,因为它提供了一种机制来使用几个线程“公平的“处理来自于多个客户端的输入/输出,并且线程如果没事干的时候也会被系统挂起,不会占用CPU周期,这个关键的作为交换的消息队列,就是完成端口。

3. 使用完成端口的基本流程

1.调用CreateloCompletionPort()函数创建一个完成端口,第四个参数设为0,让完成端口上每个处理器一次只允许执行一个线程

2.根据系统中CPU核心的数量建立对应的Worker线程

3.一个用于监听的Socket,在指定的端口上监听连接请求

4.将接受的套接字绑定到完成端口

5.使用重叠I0,在套接字上投递一个或者多个WSARecv或者WSASend请求

6.在Worker线程使用GetQueuedCompletionStatus函数,它让Worker线程进入不占用CPU的睡眠状态,直到完成端口上出现了需要处理的网络操作或者超出了等待的时间限制为止。

7.重复5-6步骤

4. CreateloCompletionPort函数

HANDLE createloCompletionPort(
	HANDLE FileHandle,//有效的文件句柄或INVALID_HANDLE_VALUE
    HANDLE ExistingCompletionPort,//已经存在的完成端口,为NULL则新建一个IOCP
    ULONG_PTR CompletionKey,//传送给处理函数的参数
    DWORD NumberOfConcurrentThreads //有多少个线程在访问这个消息队列。当参数ExistingCompletionPort不为0的时候,系统忽略该参数,当该参数为0表示允许同时相等数目于处理器个数的线程访问该消息队列。
);

返回值:

  • 成功返回一个IOCP的句柄
  • 失败返回为NULL

5. GetQueuedCompletionStatus函数

BOOL GetQueuedCompletionStatus(
    HANDLE CompletionPort,
    LPDWORD lpNumberOfBytes,
    PULONG_PTR lpCompletionKey,
    LPOVERLAPPED *lpOverlapped,
    DWORD dwMilliseconds
);

调用参数:

CompletionPort:指定的IOCP,该值由CreateIoCompletionPort函数创建。

lpnumberofbytes:一次完成后的I/O操作所传送数据的字节数。

lpcompletionkey:当文件I/O操作完成后,用于存放与之关联的CK。

lpoverlapped:为调用IOCP机制所引用的OVERLAPPED结构。

dwmilliseconds:用于指定调用者等待CP的时间。

返回值:

调用成功,则返回非零数值,相关数据存于lpNumberOfBytes、lpCompletionKey、lpoverlapped变量中。失败则返回零值。

WSAOVERLAPPED结构

typedef struct _OVERLAPPEDPLUS
{
    OVERLAPPED ol;
    SOCKET s, sclient;
    int OpCode;
    WSABUF wbuf;
    DWORD dwBytes, dwFlags;
}OVERLAPPEDPLUS;

调用函数,GetQueuedCompletionStatus(hIocp, &dwBytesXfered,(PULONG_PTR)&PerHandleKey, &Overlap, INFINITE);最后一个参数为等待时间,为INFINITE时直至有信号返回。

6. WSARecv函数

在重叠模型中,接收数据就要靠它了,它的参数也比recv要多,因为要用到重叠结构嘛,它是这样定义的:

int WSARecv(
    SOCKET s, // 当然是投递这个操作的套接字
    ,与Recv函数不同
    // 这里需要一个由WSABUF结构构成的数组
    DWORD dwBufferCount, // 数组中WSABUF结构的数量
    LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,这里会返回函数调用所接收到的字节数
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 完成例程中将会用到的参数,我们这里设置为 NULL
);

返回值:

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

7. 服务端编程

#include <WinSock2.h>  
#include <iostream>  
using namespace std;

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

//工作者线程有一个参数,是指向完成端口的句柄
DWORD WINAPI WorkerThread(LPVOID CompletionPortId);

//自定义结构体方便WSARecv操作所需的参数
typedef struct  _MY_WSAOVERLAPPED
{
	WSAOVERLAPPED overlap;
	WSABUF Buffer;
	DWORD NumberOfBytesRecvd;
	DWORD Flags;

	_MY_WSAOVERLAPPED()
	{
		Buffer.buf = new char[64]{ 0 };
		Buffer.len = 64;
		Flags = 0;//设置为0
		NumberOfBytesRecvd = 0;
		overlap.hEvent = NULL;//完成端口中不需要事件,置空
	}
	~_MY_WSAOVERLAPPED()
	{
		delete[]Buffer.buf;
		Buffer.buf = NULL;
		Buffer.len = 0;
	}
} MY_WSAOVERLAPPED, *PMY_WSAOVERLAPPED;


int main()
{

	// 初始化Winsock 2.2
	WSAData wsaData;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
	{
		cout << "WSAStartup  error:" << WSAGetLastError() << endl;
		return -1;
	}


	//创建完成端口
	HANDLE   completionPort= CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if (completionPort == NULL)
	{
		cout << "CreateIoCompletionPort  error:" << WSAGetLastError() << endl;
		return -1;
	}

	//获取CPU数量,为每个处理器创建一个线程
	SYSTEM_INFO sysInfo;
	::GetSystemInfo(&sysInfo);
	for (int i = 0; i < (int)sysInfo.dwNumberOfProcessors; i++)
	{
		//完成端口作为线程参数传入线程处理函数
		HANDLE   h = CreateThread(NULL, 0, WorkerThread, completionPort, 0, NULL);
		CloseHandle(h);
	}
	cout << "创建了" << sysInfo.dwNumberOfProcessors << "个工作线程!" << endl;


	//创建监听套接字,注意WSA_FLAG_OVERLAPPED参数,  如果使用socket(),默认就是WSA_FLAG_OVERLAPPED
	SOCKET  sListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (sListen == INVALID_SOCKET)
	{
		cout << "socket() Error: " << WSAGetLastError() << endl; 
		return  -1;
	}

	sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(8000);
	sin.sin_addr.S_un.S_addr = htonl(ADDR_ANY);
	//绑定到本机任意地址
	if (SOCKET_ERROR == bind(sListen, (sockaddr*)&sin, sizeof(sin)))
	{
		cout << "bind  error:" << WSAGetLastError() << endl;
		return -1;
	}

	//监听套接字,  Maximum queue length specifiable by listen
	if (listen(sListen, SOMAXCONN) == SOCKET_ERROR)
	{
		cout << " listen() Error:" << WSAGetLastError() << endl;
		return  -1;
	}

	while (true)
	{
		// 接受连接 ,和accept函数类似
		SOCKET  sClient = WSAAccept(sListen, NULL, NULL, NULL, NULL);

		if (sClient == INVALID_SOCKET)
		{
			cout << " accept() Error:" << WSAGetLastError() << endl;
			break;
		}

		cout << sClient << "	进入聊天室!" << endl;
		char tmp[64];
		sprintf(tmp, "欢迎%d进入聊天室!", sClient);
		send(sClient, tmp, strlen(tmp), 0);

		//将接受的套接字sClient绑定到完成端口,一样的函数,这次就不是创建了,而是绑定
		CreateIoCompletionPort((HANDLE)sClient, completionPort, (ULONG_PTR)sClient, 0);


		PMY_WSAOVERLAPPED pOver = new MY_WSAOVERLAPPED;

		//投递WSARecv请求
		int  ret = WSARecv(sClient, &pOver->Buffer, 1,
			&pOver->NumberOfBytesRecvd, &pOver->Flags, &pOver->overlap, NULL);
		if (ret == SOCKET_ERROR)
		{
			int  err = GetLastError();
			if (err == WSA_IO_PENDING)//请求投递成功
			{
			}
			else
			{
				closesocket(sClient);
				delete pOver;
				cout << "WSARecv error" << err << endl; 
			}
		}

	}

	//为了让WorkerThread退出,我们需要约定一个规则,即completionKey为空时退出
	PostQueuedCompletionStatus(completionPort, 0, NULL, NULL);


	//关闭完成端口
	CloseHandle(completionPort);

	//关闭套接字
	closesocket(sListen);

	// 最后应该做一些清除工作
	if (WSACleanup() == SOCKET_ERROR)
	{
		cout << "WSACleanup 出错!" << endl;
	}

}



DWORD WINAPI WorkerThread(LPVOID CompletionPortId)
{
	HANDLE  completionPort = (HANDLE)CompletionPortId;

	DWORD dwByteTransferred;
	SOCKET sClient=NULL;//用于获取参数指针
	PMY_WSAOVERLAPPED  pOver = NULL;

	while (true)
	{
		bool b=GetQueuedCompletionStatus(completionPort, &dwByteTransferred,
			(PULONG_PTR)&sClient, (LPOVERLAPPED*)&pOver, INFINITE);

		//检测,一般completionKey为sClient,不会为NULL,否则退出线程
		if (sClient == NULL)
		{
			return  0;
		}

		//IO操作完成,数据可直接使用
		if (b &&  dwByteTransferred > 0)
		{
			//直接在WSABUF中获取数据
			cout << sClient << "	说: " << pOver->Buffer.buf << endl;

			//清空缓存数据
			ZeroMemory(pOver->Buffer.buf, 64);

			//继续投递WSARecv请求
			//投递WSARecv请求
			int  ret = WSARecv(sClient, &pOver->Buffer, 1,
				&pOver->NumberOfBytesRecvd, &pOver->Flags, &pOver->overlap, NULL);
			if (ret == SOCKET_ERROR)
			{
				int  err = GetLastError();
				if (err == WSA_IO_PENDING)//请求投递成功
				{
				}
				else
				{
					closesocket(sClient);
					delete pOver;
					cout << "WSARecv error" << err << endl;
				}
			}

		}
		else
		{
			cout << sClient << "	离开了!" << endl;
			::closesocket(sClient);
			delete  pOver;//释放结构体 
		    //cout << "GetQueuedCompletionStatus  error: " << WSAGetLastError() << endl;
		}

	}


	return 0;
}

8. 客户端编程

#include<winsock2.h>//winsock2的头文件
#include<iostream>
using  namespace std;

//勿忘,链接dll的lib
#pragma comment(lib, "ws2_32.lib")

int  main()
{

	//加载winsock2的环境
	WSADATA  wd;
	if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
	{
		cout << "WSAStartup  error:" << GetLastError() << endl;
		return 0;
	}

	//1.创建流式套接字
	SOCKET  s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s == INVALID_SOCKET)
	{
		cout << "socket  error:" << GetLastError() << endl;
		return 0;
	}

	//2.链接服务器
	sockaddr_in   addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(8000);
	addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	int len = sizeof(sockaddr_in);
	if (connect(s, (SOCKADDR*)&addr, len) == SOCKET_ERROR)
	{
		cout << "connect  error:" << GetLastError() << endl;
		return 0;
	}

	//3接收服务端的消息
	char buf[100] = { 0 };
	recv(s, buf, 100, 0);
	cout << buf << endl;

	//3随时给服务端发消息
	int  ret = 0;
	do
	{
		char buf[64] = { 0 };
		cout << "请输入聊天内容:";
		cin >> buf;
		ret = send(s, buf, 64, 0);
	} while (ret != SOCKET_ERROR&& ret != 0);


	//4.关闭监听套接字
	closesocket(s);

	//清理winsock2的环境
	WSACleanup();



	return 0;
}


  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超级D洋葱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值