使用IO完成端口IOCP与线程池创建高性能服务器

使用IO完成端口IOCP与线程池创建高性能服务器

在使用socket网络编程,实现一个服务器时,初学者最容易想到方法是当服务器监听的socket接受(accept)到一个客户端时创建一个线程,然后在线程中调用recv函数实时监控客户端是否有数据发送过来。这种方法是可以实现服务器对客户端数据的监听,但是这种方法效率很低,无法支持大量客户端同时连接。这种方法需要对每个连接的客户端创建一个线程,如果有一千可客户端就要创建一千个线程,线程多了对于服务器会产生极大的压力。首先线程本身也是需要使用资源的,每个线程有内核对象,线程栈,创建和销毁线程也都比较费时。其次CPU中线程的切换也是比较费时的,当CPU唤醒一个睡眠的线程是需要切换线程上下文,即把睡眠线程的运行数据(如:寄存器数据)载入到CPU中,如果线程过多CPU花费在切换线程上的时间会比执行服务器程序代码时间还多。

线程过多会占用太多的系统资源,频繁的切换线程消耗过多的CPU, IO完成端口(IOCP)和线程池就是用来解决上面提到的两个问题。要理解IOCP需要先了解异步IO读取,这里从异步IO开始讲起。

异步IO

使用socket的recv/send函数发送接收数据都是同步的,即调用时需要读取到数据,或者发送完成才会返回。异步IO则是当调用recv/send函数是立即返回,系统会在后台处理发送接收数据,当后台完成数据的时候再通知调用者。这种机制和DMA(Direct Memory Access,直接内存存取)类似,当发送完读取命令后则不需要CUP继续处理,等数据到达后再通知CPU处理。因为文件的读取和socket的recv类似,实际上socket也就相当于文件句柄,这里举一个文件异步IO的例子,文件异步读取的步骤如下:

  1. 使用FILE_FLAG_OVERLAPPED创建异步文件句柄,CreateFile(… , FILE_FLAG_OVERLAPPED, NULL)
  2. 创建读取请求,为每个请求创建一个事件对象,当读取完成后事件会变成触发状态
  3. 使用ReadFile发送读取请求,可以同时发送多个读取请求
  4. 使用WaitForMultipleObjects等待发送请求的事件对象中任意一个变成触发状态。
  5. 使用GetOverlappedResult获取读取结果
  6. 重复第3步继续发送读取请求,直到读取完成。

以下是完整的异步读取文件例子代码,代码下载地址

#include <windows.h>

#include <iostream>
#include <string>
using namespace std;

const int BufferSize = 256;

struct IORequest : public OVERLAPPED
{
	IORequest()
	{
		Internal = InternalHigh = 0;
		Offset = OffsetHigh = 0;
		hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//创建读取事件对象,当读取完成后事件会变成触发状态
	}

	~IORequest()
	{
		if (NULL != hEvent)
		{
			CloseHandle(hEvent);
		}
	}
};

int main()
{
	HANDLE hFile = NULL;
	//使用FILE_FLAG_OVERLAPPED创建异步文件句柄
	hFile = CreateFile(TEXT("Text.txt"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
	if (INVALID_HANDLE_VALUE  == hFile)
	{
		cout << "Invaild File:"<<"Text.txt" << endl;
		return 1;
	}

	LARGE_INTEGER liFileSize = { 0 };
	GetFileSizeEx(hFile, &liFileSize);
	long lNextReadOffset = 0, lNeedProcess = liFileSize.LowPart;
	string text;
	text.resize(liFileSize.LowPart);//分配好缓存
	char *buffer = (char*)text.data();

	IORequest req[4]; //创建四个请求同时在后台读取
	HANDLE hs[4];
	for (int i = 0; i< 4; i++)
	{
		hs[i] = req[i].hEvent;
		ReadFile(hFile, buffer, 0, NULL, &req[i]);//发送空请求,请求可以立即完成,从而进入wile循环中的WaitForMultipleObjects
	}

	//处理已完成IO请求,以下代码可以创建单独的线程处理,这里为了方便没有创建新线程处理
	DWORD WaitResult = 0;
	while (lNeedProcess > 0)
	{
		WaitResult = WaitForMultipleObjects(4, hs, FALSE, INFINITE);//等待4个读取请求中任意一个完成
		if (WaitResult >= WAIT_OBJECT_0 && WaitResult <= WAIT_OBJECT_0+4)//数据已经在后台读取完成,前台处理读取的数据
		{
			int i = WaitResult - WAIT_OBJECT_0;
			DWORD dwNumBytes = 0;
			BOOL Result = GetOverlappedResult(hFile, &req[i], &dwNumBytes, FALSE);//无需等待因为数据已经完成读取
			if (Result == TRUE && dwNumBytes > 0)//读取成功且读取到数据
			{
				lNeedProcess -= BufferSize;
			}

			if (lNeedProcess > 0)
			{
				//读取请求完成,重新发送一个读取请求
				ResetEvent(req[i].hEvent);
				req[i].Offset = lNextReadOffset;
				ReadFile(hFile, buffer+ lNextReadOffset, BufferSize, NULL, &req[i]);//发送异步读取请求
				lNextReadOffset += BufferSize;//增加读取偏移量
			}
		}
	}
	CloseHandle(hFile);
	cout << text << endl<<endl;

	return 0;
}

如果想要更深入了解异步文件读取可以参考书籍《Windows核心编程(第5版》——第10章 同步设备I/O与异步设备I/O。

在使用同步IO时我们需要一个单独线程调用recv等待读取数据,这把整个socket的读取当成了一个大的任务,使用单独的一条线程执行, 由于recv会阻塞线程,这个线程也就同时只能执行一个socket的读取任务。异步IO可以把socket的IO任务分成很多个小任务,在线程中可以使用WaitForMultipleObjects同时等待多个IO请求的完成,这也就可以实现了一个线程同时监测多个客户端socket的recv。

 

IO完成端口(IOCP)

WaitForMultipleObjects可等待的最大事件数为MAXIMUM_WAIT_OBJECTS,这个宏的值为64,也就是说使用WaitForMultipleObjects最多可同时等待64个IO请求的完成。如果使用WaitForMultipleObjects去监测socket异步IO,一条线程监测64个socket客户端,而且这里还会有另一个限制,就是一个异步IO请求发出后,只能有一条线程去等待它的完成,不能多条线程去同时等待一个请求完成。

为了更好的等待异步IO请求的完成,Windows引入了IO完成端口(Input/Output Completion Port,IOCP)。我们可以把一个异步文件句柄与一个IOCP关联,当发出异步IO请求后,我们只需要使用函数GetQueuedCompletionStatus监测IOCP就可以监测IO请求是否完成。使用IOCP监测IO请求时,不需要事件内核对象,这更节省资源。一个IOCP可以同时与多个异步文件句柄关联,可以同时监测IO请求的数量也不受限制,而且可多个线程同时监测一个IOCP,当一个请求完成时,只会分配到其中一个线程处理。

IO完成端口多线程处理

如果电脑只有一个单核CPU多线程并没有意义,反而会因为线程切换让费CPU的运算。但现在的电脑,特别是服务器,不可能只有一个单核CPU,现在电脑的CPU都是多核设计,对于一个四核四线程的CPU来说可以同时四条线程运行,如果只用一条线程则会浪费CPU运算。

对于IOCP,我们可以同时用多条线程监测完成的IO请求,并行处理IO请求,如果是一个四核CPU四条线程同时在处理完成的IO请求最好,四条线程并行运行没有线程的切换。但是有时线程处理IO请求时可能会因为等待互斥量等情况而暂停,这是会有一个CPU空闲,所以只创建四条线程处理是不够的。依据书上所说的经验,大多数情况下处理IOCP创建的线程数量应该为CPU核数的两倍。
    我们可以通过函数GetSystemInfo获取系统中CPU核数。

减少线程切换

IOCP会记录所有调用GetQueuedCompletionStatus阻塞的线程,当有IO请求完成时,IOCP会将请求分配给最后调用GetQueuedCompletionStatus阻塞的线程。如果同时有多个IO请求完成,IOCP会把请求分配给多个线程处理,分配的线程同样是最后几个调用GetQueuedCompletionStatus阻塞的线程。以下是一个IOCP调度线程的例子:

  1. 假如有AB两个线程依次调用GetQueuedCompletionStatus等待IO请求完成。
  2. 现在等待处理IO请求的线程为AB
  3. 当有一个IO请求完成时,IOCPIO请求分配给B线程处理。
  4. 现在等待处理IO请求的线程为A
  5. B处理完IO请求,又调用GetQueuedCompletionStatus等待IO请求完成。
  6. 现在等待处理IO请求的线程为AB
  7. 此时又有一个IO请求完成,IOCP依然将IO请求分配给B线程处理。
  8. 现在等待处理IO请求的线程为A
  9. 此时又有另一个IO请求完成,IOCPIO请求分配给A线程处理。

以上过程中的第7步,第二个IO请求完成时,依然使用的时B线程处理,因为上一次IO请求也是B处理,这样就无需切换线程。如果IO请求完成的速度比较慢,每次都在B处理完后到达,那所有的IO请求都会有B线程完成,只有当B线程“处理不过来”时才会调用A线程处理。

在使用函数CreateIoCompletionPort创建IOCP时,最后一个参数NumberOfConcurrentThreads可以控制同时最多有多少线程处理已完成的IO请求,如果传入0,则使用CPU核数。控制的最大同时运行的线程数量,防止过度的线程同时运行,从而减少线程的切换。

IOCP通过将请求分配给最后调用GetQueuedCompletionStatus阻塞的线程,以及控制同时处理请求的最大数量来减少线程的切换。

以下是IOCP调用线程测试的例子,完整代码下载地址

例子分析,这是一个使用IOCP的socket客户端,该客户端需要配合socket服务器使用,socket服务器代码下载地址。运行客户端前,需要先打开服务器,否则客户端连接不到服务器会自动退出,在测试时我是从服务向客户发送数据。在客户端处理IO请求函数ProcessIOThread中我故意增加了一个Sleep(1000)模拟长时间处理IO请求。代码中有大量注释,具体原理请看代码

 

线程池

通过对IOCP的分析,IOCP已经可以避免服务器创建过多的线程以及过多的线程调度。前面说过一般处理IOCP的IO请求需要创建处理一般创建线程数量为CPU数的两倍,但在很多情况下这并不是效率最高的,毕竟CPU不光要处理IO请求,而且如果同时有大量处理IO请求的对象阻塞(如等待互斥量),可能会出现没有线程处理IO请求。为了更好的处理线程调度,使得CPU运行效率更高,就需要使用线程池。

原理

线程池会同时创建多个线程在后台运行,用来处理各种各样的任务,当处理完任务后又会回到线程池中等待下一个任务的到来。线程池内部的线程全部由线程池自己管理,无需我们创建销毁,线程池内部会对线程进行调度以保证CPU以最高的效率运行,高效的处理各种任务。有了线程池,我们只需要将任务发送给线程池,线程池会自动分配线程完成相应的任务。

任务

有了线程池,我们可以把一个大的任务分成几个小任务发送给线程池,让线程池执行,这样执行速度更快。Windows本身已经提供了线程池函数,我们只需要直接调就行,Windows自带的线程池已经足够高效,可以满足我们大多数的情况。我们可以向Windows线程池中发送以下四种任务:

  1. 调用一个回调函数
  2. 定时调用一个函数
  3. 当内核对象触发时调用一个函数
  4. 处理IOCP完成的IO请求

当我们第一次向线程池中发送任务时,系统会自动创建一个线程,具体使用可以参考书籍《Windows核心编程(第5版》——第10章 同步设备I/O与异步设备I/O。

线程池与异步socket

异步的socket可以把读取和写入分成了各个小的任务,这些任务可以发送给线程池处理。使用步骤如下:

  1. 创建一个线程池IO回调函数PTP_WIN32_IO_CALLBACK,IoCompletionCallback
  2. 使用函数CreateThreadpoolIo创建一个线程池的IO任务,并传入socket和回调函数IoCompletionCallback
  3. 调用StartThreadpoolIo向线程池发送一个IO任务
  4. 调用异步读取写入发送IO任务给socket
  5. 如果第4步发送失败则调用CancelThreadpoolIo取消线程池的IO请求
  6. 重复3、4、5步。

注意每次调用异步读取写入前需要调用一次StartThreadpoolIo

CreateThreadpoolIo内部会自动将sockct与系统创建的一个IOCP进行关联,无需我们自己创建IOCP。

 

对于服务器监听accept客户端的socket,也可以使用AcceptEx将accept变为异步,这使得服务器更加统一,而且AcceptEx的速度比accept更快。

AcceptExwindows对标准socket的一个扩展函数,位于头文件Mswsock.h

以下线程池管理Socket服务器完整例子,代码下载地址
 

#include <winsock2.h>  
#include <Mswsock.h>  //使用异步的AcceptEx
#include <WS2tcpip.h>
#include <Windows.h>  
#include <process.h>
#include <iostream>  
#include <string>  
#include <set>

using namespace std;

#pragma comment(lib, "Ws2_32.lib")      // Socket动态链接库  
//#pragma comment(lib, "Mswsock.lib")      // AcceptEx动态链接库  

enum class IOOption
{
	Invaild,
	Accept,
	Receive,
	Send,
	Start,
	Exit
};

struct SocketClient
{
	SOCKET socket;
	PTP_IO io;
	bool isConnect;
	sockaddr_in ClientAddr;
};

struct IORequest : public OVERLAPPED
{
	static const size_t BufferSize = 2043;
	char buffer[BufferSize + 1];//多一个空间存储截至字符 \0
	WSABUF wsabuf;
	bool isUsing;
	SocketClient *client;
	IOOption option;
	IORequest() :OVERLAPPED{ 0 }, buffer{ 0 }
	{
		option = IOOption::Invaild;
		wsabuf.len = BufferSize;
		wsabuf.buf = buffer;
		isUsing = false;
		client = nullptr;
	}
};

VOID CALLBACK IoCompletionCallback(PTP_CALLBACK_INSTANCE, PVOID, PVOID, ULONG, ULONG_PTR, PTP_IO);
int AddAcceptRequest();

const DWORD AcceptBufferReservedLen = sizeof(SOCKADDR_IN) + 16;//具体参考AcceptEx的dwLocalAddressLength参数,URL https://docs.microsoft.com/zh-cn/windows/desktop/api/mswsock/nf-mswsock-acceptex
LPFN_ACCEPTEX lpfnAcceptEx = nullptr;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs = nullptr;
GUID GuidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
LPFN_DISCONNECTEX lpfnDisconnectEx = nullptr;//DisconnectEx函数可以关闭客户端的socket,关闭后可以传递给AcceptEx重用。
GUID GuidDisconnectEx = WSAID_DISCONNECTEX;


const int MaxRequestCount = 400;
SOCKET server;
IORequest sendRequest[MaxRequestCount];
static LONG CurrentAcceptRequcest = 0;

CRITICAL_SECTION cs;
set<SocketClient*> clients;
PTP_IO acceptIO;

int main()
{

	WORD wVersionRequested = MAKEWORD(2, 2);
	WSADATA wsaData;
	int err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		cout << "Socket Initialize failed!" << endl;
		return 1;
	}

	server = socket(AF_INET, SOCK_STREAM, 0);
	if (server == INVALID_SOCKET) {
		cout << "Create Socket Error: " << WSAGetLastError() << endl;
		WSACleanup();
		return 2;
	}

	SOCKADDR_IN ipAddress;
	ipAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	ipAddress.sin_family = AF_INET;
	ipAddress.sin_port = htons(60001);
	int result = bind(server, (SOCKADDR*)&ipAddress, sizeof(SOCKADDR));
	if (0 != result) {
		cout << "Bind Error: " << GetLastError() << endl;
		return 3;
	}
	result = listen(server, SOMAXCONN);
	if (0 != result) {
		cout << "Listen Error: " << GetLastError() << endl;
		return 4;
	}

	DWORD dwBytes;
	int iResult = WSAIoctl(server, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptEx, sizeof(GuidAcceptEx), 
		                   &lpfnAcceptEx, sizeof(lpfnAcceptEx), &dwBytes, NULL, NULL);
	iResult = WSAIoctl(server, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidGetAcceptExSockaddrs, sizeof(GuidGetAcceptExSockaddrs), 
		                &lpfnGetAcceptExSockaddrs, sizeof(lpfnGetAcceptExSockaddrs), &dwBytes, NULL, NULL);
	iResult = WSAIoctl(server, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidDisconnectEx, sizeof(GuidDisconnectEx), 
		                &lpfnDisconnectEx, sizeof(lpfnDisconnectEx), &dwBytes, NULL, NULL);
	InitializeCriticalSection(&cs);

	acceptIO = CreateThreadpoolIo((HANDLE)server, IoCompletionCallback, NULL, NULL);
	AddAcceptRequest();

	string str;
	cout <<"Server Port:"<< 60001 << ", Input Send: " << endl;
	IORequest* pReq = nullptr;
	do
	{
		cin >> str;
		if ((str == "Exit"))
		{
			break;
		}

		for each (auto c in clients)
		{
			if (!c->isConnect)
			{
				continue;
			}
			pReq = new IORequest();
			pReq->isUsing = true;
			pReq->option = IOOption::Send;
			pReq->client = c;
			memcpy(pReq->buffer, str.data(), str.size());
			pReq->wsabuf.len = str.size();
			StartThreadpoolIo(c->io);//每次发送异步数据前需要调用一次,否则线程池无法收到完成消息并有可能会导致崩溃
			iResult = WSASend(c->socket, &pReq->wsabuf, 1, NULL, 0, pReq, NULL);//异步发送
			if (iResult != 0 && WSAGetLastError() != WSA_IO_PENDING)
			{
				CancelThreadpoolIo(c->io);
			}
		}

	} while (true);
	

	CancelIoEx((HANDLE)server, NULL);
	WaitForThreadpoolIoCallbacks(acceptIO, FALSE);//
	closesocket(server);
	CloseThreadpoolIo(acceptIO);

	while (clients.size() > 0)
	{
		auto c = *clients.begin();
		CancelIoEx((HANDLE)c->socket, NULL);//取消请求
		WaitForThreadpoolIoCallbacks(c->io, FALSE);//等待取消,取消处会删除相应的内存
	}
	return 0;
}

int AddAcceptRequest()
{
	if (nullptr == lpfnAcceptEx)
	{
		DWORD dwBytes = 0;
		int iResult = WSAIoctl(server, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptEx, sizeof(GuidAcceptEx),
			&lpfnAcceptEx, sizeof(lpfnAcceptEx), &dwBytes, NULL, NULL);
	}
	const LONG MaxAcceptRequest = 5;//最大等待Accept的数量,这个数量需要根据实际情况调整,
	const LONG MinAcceptRequest = 2;//最少等待Accept的数量
	static IORequest acceptRequest[MaxAcceptRequest];


	if (CurrentAcceptRequcest >= MinAcceptRequest)
	{
		return 0;
	}

	EnterCriticalSection(&cs);
	int n = 0;
	for (int i = 0; i < MaxAcceptRequest; ++i)
	{
		if (acceptRequest[i].isUsing)
		{
			continue;
		}
		SOCKET sclient = socket(AF_INET, SOCK_STREAM, 0);
		StartThreadpoolIo(acceptIO);
		BOOL Ret = lpfnAcceptEx(server, sclient, acceptRequest[i].buffer,
			0, //设为0表示accept客户端时不接收数据
			AcceptBufferReservedLen, AcceptBufferReservedLen, NULL, &acceptRequest[i]);
		if (Ret == TRUE || (Ret == FALSE && WSAGetLastError() == ERROR_IO_PENDING))
		{
			cout << "Add Pending Accept" << endl;
			acceptRequest[i].isUsing = true;
			acceptRequest[i].option = IOOption::Accept;
		}
		else
		{
			CancelThreadpoolIo(acceptIO);//发送异步请求失败,取消上一次的StartThreadpoolIo(acceptIO),防止内存泄漏。
			cout << "Add Accept Failed" << endl;
			break;;
		}
		SocketClient *client = new SocketClient();
		client->isConnect = false;
		client->socket = sclient;
		client->io = CreateThreadpoolIo((HANDLE)sclient, IoCompletionCallback, NULL, NULL);
		acceptRequest[i].client = client;
		clients.insert(client);
		++n;
	}
	LeaveCriticalSection(&cs);
	InterlockedExchange(&CurrentAcceptRequcest, MaxAcceptRequest);
	return n;
}


VOID CALLBACK IoCompletionCallback(
	_Inout_     PTP_CALLBACK_INSTANCE Instance,
	_Inout_opt_ PVOID                 Context,
	_Inout_opt_ PVOID                 Overlapped,
	_In_        ULONG                 IoResult,
	_In_        ULONG_PTR             NumberOfBytesTransferred,
	_Inout_     PTP_IO                Io)
{
	IORequest *pReq = (IORequest*)Overlapped;
	IOOption option = pReq->option;
	DWORD flag = 0;

	switch (option)
	{
	case IOOption::Invaild:
		break;
	case IOOption::Accept:
	{
		if (NO_ERROR != IoResult)
		{
			bool isFind = false;
			EnterCriticalSection(&cs);
			isFind = 0 < clients.erase(pReq->client);
			LeaveCriticalSection(&cs);
			if (isFind)
			{
				cout << "Port: " << ntohs(pReq->client->ClientAddr.sin_port) << "  Disconnect." << endl;
				closesocket(pReq->client->socket);
				CloseThreadpoolIo(pReq->client->io);
				delete pReq->client;
				pReq->client = nullptr;
			}
		}
		else
		{
			int iResult = 0;
			iResult = setsockopt(pReq->client->socket, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char *)&server, sizeof(server));

			sockaddr_in *local = nullptr, *remote= nullptr;
			int localLen = 0, remoteLen = 0;
			lpfnGetAcceptExSockaddrs(pReq->buffer, 0, AcceptBufferReservedLen, AcceptBufferReservedLen, (sockaddr**)&local, &localLen, (sockaddr**)&remote, &remoteLen);
			if (remoteLen == sizeof(sockaddr_in))
			{
				memcpy(&pReq->client->ClientAddr, remote, remoteLen);
				cout << "Client Connected. Port: " << ntohs(remote->sin_port) << endl;
			}

			IORequest *recvReq = new IORequest();
			recvReq->option = IOOption::Receive;
			recvReq->isUsing = true;
			recvReq->client = pReq->client;
			StartThreadpoolIo(recvReq->client->io);
			iResult = WSARecv(recvReq->client->socket, &recvReq->wsabuf, 1, NULL, &flag, recvReq, nullptr);
			if (iResult != 0 && WSAGetLastError() != WSA_IO_PENDING)
			{
				CancelThreadpoolIo(recvReq->client->io);
			}
			pReq->client->isConnect = true;
		}

		InterlockedAdd(&CurrentAcceptRequcest, -1);
		pReq->isUsing = false;	
		pReq->client = nullptr;
		if (WSA_OPERATION_ABORTED != IoResult) //Request被取消,系统正在退出
		{
			AddAcceptRequest();
		}
	}
		break;
	case IOOption::Receive:
	{
		if (NO_ERROR != IoResult)
		{
			bool isFind = false;
			EnterCriticalSection(&cs);
			isFind = 0 < clients.erase(pReq->client);
			LeaveCriticalSection(&cs);
			if (isFind)
			{
				cout << "Port: " << ntohs(pReq->client->ClientAddr.sin_port) << "  Disconnect." << endl;
				closesocket(pReq->client->socket);
				CloseThreadpoolIo(pReq->client->io);
				delete pReq->client;
			}
			delete pReq;
			break;
		}

		if (NumberOfBytesTransferred > 0)
		{
			pReq->buffer[NumberOfBytesTransferred] = '\0';
			cout << "Port: "<< ntohs(pReq->client->ClientAddr.sin_port)<<",  Receive: " << pReq->buffer << endl;
		}
		int iResult = 0;
		StartThreadpoolIo(pReq->client->io);
		iResult = WSARecv(pReq->client->socket, &pReq->wsabuf, 1, NULL, &flag, pReq, nullptr);
		if (iResult != 0 && WSAGetLastError() != WSA_IO_PENDING)
		{
			CancelThreadpoolIo(pReq->client->io);
		}
	}
		break;
	case IOOption::Send:
	{
		if (NO_ERROR != IoResult)
		{
			bool isFind = false;
			EnterCriticalSection(&cs);
			isFind = 0 < clients.erase(pReq->client);
			LeaveCriticalSection(&cs);
			if (isFind)
			{
				cout << "Port: " << ntohs(pReq->client->ClientAddr.sin_port) << "  Disconnect." << endl;
				closesocket(pReq->client->socket);
				CloseThreadpoolIo(pReq->client->io);
				delete pReq->client;
			}
		}
		else
		{
			cout << "Port: "<< ntohs(pReq->client->ClientAddr.sin_port) << "  Send Successfully." << endl;//异步发送成功
		}
		delete pReq;
	}
		break;
	case IOOption::Start:
	{

	}
		break;
	case IOOption::Exit:
		break;
	default:
		break;
	}
}

 

文中所有提及的代码下载地址

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值