windows网络进阶

线程池

“池”,我把他归结为一大堆的资源,
比如说,内存,线程,网络连接,数据库的连接等一系列的资源,
因而与之相对应的就有,内存池,线程池,网络连接池,数据库连接池等。
线程池也就是一大堆的线程资源,
它们在程序刚开始运行的时候就存在了,
等需要它们的时候呢,就可以使用它们,
而不必向操作系统去申请。
那么我们为何不在需要的时候再向操作系统申请呢?
这是由于动态的申请线程资源的话,要从应用层转到内核层,
这期间花费的时间是非常慢的,
如果我们要经常的使用线程,而采用动态申请就需要非常多的时间,
这会大大降低程序的效率。
因此,针对这种情况,我们就在程序运行开始时,
就申请一大堆线程,需要的时候就从线程池里边拿,不需熬的时候就让它们挂起。
MSDN提供了CreateThreadPool这个API,用于创建一个线程池,
PTP_POOL WINAPI CreateThreadpool(
  _Reserved_ PVOID reserved 		//操作系统保留的,必须为NULL
);
第二个是SetThreadpoolThreadMinimum,用于设置线程池的最小线程数,
BOOL WINAPI SetThreadpoolThreadMinimum(
  PTP_POOL ptpp,		//上一个函数的返回值	
  DWORD    cthrdMic		//线程池中最小的线程数	
);
第三个是SetThreadpoolThreadMaximum,用于设置最大的线程数,
VOID WINAPI SetThreadpoolThreadMaximum(
  PTP_POOL ptpp,		//第一个函数的返回值
  DWORD    cthrdMost	//线程池中最大的线程数
);
当我们设置完最小和最大线程数的时候,就可以初始化一下线程池的回调环境了,
回调环境是一个TP_CALLBACK_ENVIRON结构体,
我们用InitializeThreadpoolEnvironment来初始化该环境,
VOID InitializeThreadpoolEnvironment(
  PTP_CALLBACK_ENVIRON pcbe		//回调环境地址
);
回调环境必须指明我们的工作任务由哪个线程池来处理,
而SetThreadpoolCallbackPool这个API就是来干这个滴,
VOID SetThreadpoolCallbackPool(
  PTP_CALLBACK_ENVIRON pcbe, 	//回调环境地址
  PTP_POOL             ptpp 	//第一个函数的返回值
);
最后呢,将一个工作任务添加到线程池的队列中呢,
需要调用TrySubmitThreadpoolCallback,
BOOL WINAPI TrySubmitThreadpoolCallback(
  PTP_SIMPLE_CALLBACK  pfns,	//该参为我们的工作任务函数名,稍后介绍
  PVOID                pv,		//该参为我们工作任务函数的第二个参数传递过来的
  PTP_CALLBACK_ENVIRON pcbe		//回调环境地址
);
为了让线程池一异步的方式执行我们的工作任务函数呢,
我们需要定义一个具有以下原型的函数,格式不能改,名字可改:
VOID CALLBACK SimpleCallback(
  PTP_CALLBACK_INSTANCE Instance,
  PVOID                 Context
);
测试代码:
#include <windows.h>

void CALLBACK test1(
	PTP_CALLBACK_INSTANCE instance, 
	PVOID context)
{
	printf("Pool thread %d is running test1...\n", GetCurrentThreadId());
	return;
}

void CALLBACK test2(
	PTP_CALLBACK_INSTANCE instance, 
	PVOID context)
{
	printf("Pool thread %d is running test2...\n", GetCurrentThreadId());
	return;
}

int _tmain(int argc, _TCHAR* argv[])
{
	// 创建线程池
	PTP_POOL pool = CreateThreadpool(0); 
	// 设置 线程池内最小有1个线程
	SetThreadpoolThreadMinimum(pool, 1); 
	// 设置 最多有10个线程
	SetThreadpoolThreadMaximum(pool, 10);
 
	TP_CALLBACK_ENVIRON env;
	//初始化线程回调环境
	InitializeThreadpoolEnvironment(&env);
	SetThreadpoolCallbackPool(&env, pool);
	//将test1任务添加到线程池队列中
	TrySubmitThreadpoolCallback(test1, 0, &env);
	//将test2任务添加到线程池队列中
	TrySubmitThreadpoolCallback(test2, 0, &env);
	system("pause");
	return 0;
}

select网络模型

我们在TCP的服务端里边,接收一个客户端的时候,我们调用accept函数,
这个函数会返回一个客户端的socket,我们在主线程里边不停的接收客户的连接,
每当有客户连接时,我们就会在开一个线程,用于对客户的服务。
因此,如果有N个的客户进行连接的话,那么线程数量就会有N+1个(N个服务线程+主线程),
若N比较大,则线程就会非常多,以至于将整个电脑都给拖垮掉。
而我们的select模型呢,就是为了解决这个问题而设计的。

TCP的服务端一样,它需要初始化环境,然后执行绑定,监听等操作。
但是之后,我们会直接开一个线程来对客户进行服务,
然后才是我们原来的一个循环,来接待客户的连接。
typedef struct fd_set 
{
  u_int    fd_count;                 // 有多少个socket
  SOCKET   fd_array[FD_SETSIZE];     // 客户端socket数组,FD_SETSIZE为64
} fd_set;
当我们调用accept来获取客户端的连接之后,会调用FD_SET这个宏,
它实际上是会将我们的客户那个socket保存到fd_array这个数组里边去,
因为这个数组最大为64个,所以最多只能有64个客户端进行连接。
我们把服务客户的那个线程叫做工作者线程,在里边我们会调用一个函数叫做select,
int select(  
  int nfds,                            //已经忽略了
  fd_set FAR *readfds,                 //可读fd_set的地址
  fd_set FAR *writefds,                //可写fd_set地址
  fd_set FAR *exceptfds,               //异常错误fd_set地址
  const struct timeval FAR *timeout    //timeval结构
);
这个函数会检查fd_array这个数组里边所有的socket是否有信号到来,
如果有就成功返回,否则会阻塞在这里,不过我们在最后一个参数那里,传一个等待时间。
调用完select之后,
我们可以在调用FD_ISSET这个宏来判断是fd_array这个数组里边的那个socket有信号了。
之后我们就可以进行数据收发了。

示例代码:
#include <winsock2.h>
#include <stdio.h>
#define PORT 6000
#pragma comment (lib, "Ws2_32.lib")
fd_set  g_fdClientSock;
int clientNum = 0;
 
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;
}

// 工作者线程 
DWORD WINAPI WorkThreadProc(LPARAM lparam)
{
	fd_set fdRead;
	FD_ZERO( &fdRead );
	int nRet = 0;
	char* recvBuffer =(char*)malloc( sizeof(char) * 1024 );
	if ( recvBuffer == NULL )
	{
		return -1;
	}
	    
	memset( recvBuffer, 0, sizeof(char) * 1024 );
	while ( true )
	{
	    fdRead = g_fdClientSock;
	    timeval tv;
		tv.tv_sec = 0;
		tv.tv_usec = 10;
	    //检查fd_arrray数组里边是否有信号到来
	    // 一个线程同时监控多个服务端的已经连接描述符
	    // 这样一个线程完成了之前N个工作线程完成的任务
	    nRet = select( 0, &fdRead, NULL, NULL, &tv );
	    if ( nRet != SOCKET_ERROR )
	    {
	        for ( int i = 0; i < g_fdClientSock.fd_count; i++ )
	        {
	        	// 遍历出来哪些SOCKET有信号
	            if ( FD_ISSET(g_fdClientSock.fd_array[i],&fdRead)  )
	            {
	            	// 下面是数据的收发
	                memset( recvBuffer, 0, sizeof(char) * 1024 );
	                nRet = recv( g_fdClientSock.fd_array[i], recvBuffer, 1024, 0);
	                if ( nRet == SOCKET_ERROR )
	                {
	                    closesocket( g_fdClientSock.fd_array[i]);
	                    clientNum--;
	                    FD_CLR( g_fdClientSock.fd_array[i], &g_fdClientSock );
	                }
	                else if ( nRet == 0 )
	                {
	                    closesocket( g_fdClientSock.fd_array[i]);
	                    clientNum--;
	                    FD_CLR( g_fdClientSock.fd_array[i], &g_fdClientSock );
	                }
	                else
	                {	                    
	                    printf("Recv msg:%s\n",recvBuffer);
	                    send(
	                    	g_fdClientSock.fd_array[i], 
	                    	recvBuffer, 
	                    	strlen(recvBuffer), 
	                    	0);
	                }
	            }
	        }
	    }
	}
 
	if ( recvBuffer != NULL )
	{
		free( recvBuffer );
	}
	    
	return 0;
}

int main()
{
	//初始化环境
	WinSockInit();
 
	SOCKET listenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = htonl(INADDR_ANY);
	server.sin_port = htons(PORT);
	//绑定
	int ret = bind(listenSock, (sockaddr*)&server, sizeof(server));
	//监听
	ret = listen(listenSock, 4);
 
	sockaddr_in clientAddr;
	int nameLen = sizeof( clientAddr );
	// 先把工作者线程创建起来
	CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)WorkThreadProc, NULL, NULL, NULL);
 
 	// 独占一个线程执行accept,不断产生由服务端产生的已经连接套接字
 	// 加入到全局fd_set结构中【全局结构多线程访问应该加互斥锁保护】
 	// 使用select的一个缺陷是,一个select受fd_set结构限制
 	// 可以在一个fd_set里监控的描述符个数的上限较小
 	//FD_SETSIZE==64
	while( clientNum < FD_SETSIZE )
	{
		// 当有一个客户端进行连接时,主线程的accept会进行返回
		SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );
		FD_SET(clientSock, &g_fdClientSock);
		clientNum++;
	}
	
	closesocket(listenSock);
	WSACleanup();
	return 0;
}

EventSelect网络模型

select网络模型,它解决了有N个客户端连接,就有N+1个线程的问题,
现在我们只需要2个线程就能搞定了。
但是,我们的服务端仅仅是这样的话,它的效率还是非常低的。
在select模型中,我们在工作者线程里边调用了select函数来判断是否有数据到来了(有信号了),
如果没有信号,select就会卡在这里,虽然我们可以给它一个超时时间,
但是它在底层还是调用了sleep函数。所以它浪费了CPU的时钟周期,
由于我们不知道客户是什么发送数据过来,可能是1秒,可能是1小时,也有可能是1天,
所有等待数据到来占了非常大的一部分时间,效率是比较低的。
接下来我们来看下EventSelect模型,它要解决的问题就是将这个等待数据到来的时间给节省掉了。

首先,初始化网络环境,创建一个监听的socket,然后进行bind,listen操作。
接下来我们会创建一个网络事件对象,
它和我们讲内核态下线程同步里边事件对象很类似,我们可以调用WSACreateEvent来创建它,
WSAEVENT  WSACreateEvent (void);
然后我们再调用WSAEventSelect,来将监听的socket与该事件进行一个关联,
int WSAEventSelect(  
  SOCKET s,                 //套接字
  WSAEVENT hEventObject,    //网络事件对象
  long lNetworkEvents       //需要关注的事件
);
因为我们这里是将监听的socket与事件对象进行关联,
因此我们只需要关注两个事件,
一个是客户端的连接,一个是socket关闭这两个事件。
然后,我们可以创建一个工作者线程了。
最后将我们监听的socket和我们创建的那个网络事件对象保存到各自的全局数组里边去。
这时候我们的主线程就结束了。
然后我们来看下我们的工作者线程做了哪些工作。
在工作者线程里边,会有一个死循环,
在循环刚开始的时候,会调用WSAWaitForMultipleEvents函数,
来查看我们那个全局事件对象数组里边是否至少有一个有信号到来,
DWORD WSAWaitForMultipleEvents(  
  DWORD cEvents,                  //指定了事件对象数组里边的个数,最大值为64
  const WSAEVENT FAR *lphEvents,  //事件对象数组
  BOOL fWaitAll,                  //等待类型,TRUE表示要数组里全部有信号才返回,FALSE表示至少有一个就返回,这里必须为FALSE
  DWORD dwTimeout,                //等待的超时时间
  BOOL fAlertable                 //当系统的执行队列有I/O例程要执行时,是否返回,TRUE执行例程返回,FALSE不返回不执行,这里为FALSE
);
如果该函数执行成功,就返回一个索引值,这个值代表了那个有信号的事件对象的索引值。
我们在主线程里边关注了两个事件,一个是客户端的连接,一个是关闭事件,
那我们如何知道出现了哪个网络事件呢,我需要调用WSAEnumNetworkEvents,
来检测指定的socket上的网络事件。
int WSAEnumNetworkEvents
(  
  SOCKET s,                             //指定的socket
  WSAEVENT hEventObject,                //事件对象
  LPWSANETWORKEVENTS lpNetworkEvents    //WSANETWORKEVENTS结构地址
);
当我们调用这个函数成功后,
它会将我们指定的socket和事件对象所关联的网络事件的信息保存到
WSANETWORKEVENTS这个结构体里边去,
typedef struct _WSANETWORKEVENTS 
{
  long     lNetworkEvents;				//指定了哪个已经发生的网络事件
  int      iErrorCodes[FD_MAX_EVENTS];	//错误码
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
根据这个结构体我们就可以判断是否是我们所关注的网络事件已经发生了。
如果是我们那个客户端连接的事件发生了,
我们就调用accept函数将客户端和服务端进行连接。
连接完成后,我们再次创建一个网络事件对象,然后继续调用WSAEventSelect函数,
将这个事件对象和客户端的那个socket进行一个关联,
此时我们需要关注的网络事件,数据的读和写,还有关闭这三个事件。
然后我们就把客户端的socket和新建的事件对象保存到各自的全局数组里边去。
如果是我们的读的网络事件发生了,那么我们就调用recv函数进行操作。
如果是写的网络事件发生了,我们就可以做一些日志等操作。
若是关闭的事件发生了,就调用closesocket将socket关掉,在数组里将其置零等操作。
最后,在提一下另一个网络模型---异步选择模型,这个模型呢它和我们的事件选择模型很像,
不过它是基于windows消息的,这就说明了我们只能在窗口程序里边来使用,
而事件选择是以事件对象为基础,它不管是控制台还是窗口程序都可以使用,
因此用的较多的也是我们事件选择模型。
#include <winsock2.h>
#include <stdio.h>
#define PORT 6000
#pragma comment (lib, "Ws2_32.lib")

SOCKET ArrSocket[64] = { 0 };
WSAEVENT ArrEvent[64] = { 0 };
DWORD dwTotal = 0;
DWORD dwIndex = 0;

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;
}
 
DWORD WINAPI WorkThreadProc(LPARAM lparam)
{
	char buf[1024] = { 0 };
	//用于ACCEPT临时使用的SOCKET
	SOCKET sockClient = INVALID_SOCKET;
	WSANETWORKEVENTS NetWorkEvent = { 0 };
	while (TRUE) 
	{		
		//数组内任意一个WSAEVENT有信号了,返回对应的索引值
		dwIndex = WSAWaitForMultipleEvents(
			dwTotal, 
			ArrEvent, 
			FALSE, 
			100, 
			FALSE);
		if (dwIndex == WSA_WAIT_TIMEOUT) 
		{
			continue;
		}
		
		//检测指定的socket的网络事件的发生
		WSAEnumNetworkEvents(
			ArrSocket[dwIndex - WSA_WAIT_EVENT_0], 
			ArrEvent[dwIndex - WSA_WAIT_EVENT_0], 
			&NetWorkEvent);//调用完成后NetWorkEvent保存了网络事件及一些标志位
		
		//如果第3位数据是1,代表有客户端进行连接
		if (NetWorkEvent.lNetworkEvents & FD_ACCEPT)
		{
			//如果出错了,就跳过			
			if (NetWorkEvent.iErrorCode[FD_ACCEPT_BIT] != 0)
			{
				continue;
			}
			
			sockClient = accept(ArrSocket[dwIndex - WSA_WAIT_EVENT_0], NULL, NULL);
			if (sockClient == INVALID_SOCKET)
			{
				continue;
			}
				
			//连接完成后,将客户端的SOCKET保存到数据,同时新建EVENT与SOCKET建立关系
			WSAEVENT newEvent = WSACreateEvent();
			// Socket--Event【特定监控事件集合的Event】
			// 此后可使用WSAWaitForMultipleEvents等待Event集合中某个Event因为事件而返回
			WSAEventSelect(
				sockClient, 
				newEvent, 
				FD_READ | FD_WRITE | FD_CLOSE);
			
			ArrSocket[dwTotal] = sockClient;
			ArrEvent[dwTotal] = newEvent;
			++dwTotal;
		}
 
		if (NetWorkEvent.lNetworkEvents & FD_READ) 
		{
			if (NetWorkEvent.iErrorCode[FD_READ_BIT] != 0)
			{
				continue;
			}
			
			int len = recv(ArrSocket[dwIndex - WSA_WAIT_EVENT_0], buf, sizeof(buf), 0);
			printf("Recv: %s\n", buf);
			send(ArrSocket[dwIndex - WSA_WAIT_EVENT_0], buf, strlen(buf), 0);
		}
 
		if (NetWorkEvent.lNetworkEvents & FD_WRITE) 
		{
			if (NetWorkEvent.iErrorCode[FD_WRITE_BIT] != 0)
			{
				continue;
			}
			
			printf("Send something\n");
		}
 
		if (NetWorkEvent.lNetworkEvents & FD_CLOSE) 
		{
			if (NetWorkEvent.iErrorCode[FD_CLOSE_BIT] != 0)
			{
				continue;
			}
			
			closesocket(ArrSocket[dwIndex - WSA_WAIT_EVENT_0]);
			ArrSocket[dwIndex - WSA_WAIT_EVENT_0] = 0;							
		}
	}
}

int main()
{
	//初始化环境
	WinSockInit();
	SOCKET sockListen = INVALID_SOCKET;
	sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sockListen == INVALID_SOCKET)
	{
		printf("Create socket error");
		return -1;
	}
	
	sockaddr_in service;
	service.sin_family = AF_INET;
	service.sin_addr.S_un.S_addr = INADDR_ANY;
	service.sin_port = htons(PORT);
	//绑定
	if (bind(sockListen, (sockaddr*)&service, sizeof(service)) == SOCKET_ERROR)
	{
		printf("bind failed\n");
		return -1;
	}
	
	//监听
	if (listen(sockListen, SOMAXCONN) == SOCKET_ERROR) 
	{
		printf("listen error\n");
		return -1;
	}
 
	//创建一个网络事件对象
	WSAEVENT ListenEvent = WSACreateEvent();
	// 把WSAEVENT与一个SOCKET进行关联,告诉关联的对象需要关注的事件有哪些
	WSAEventSelect(sockListen, ListenEvent, FD_ACCEPT | FD_CLOSE);
	//创建一个子进程,用子进程来处理所有的SOCKET上面的事件
	CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)WorkThreadProc, NULL, NULL, NULL);
	//保存到全局数组
	ArrSocket[dwTotal] = sockListen;
	ArrEvent[dwTotal] = ListenEvent;
	++dwTotal;
 
	system("pause");
	if (sockListen != INVALID_SOCKET)
	{
		closesocket(sockListen);
	}
	
	WSACleanup();
	return 0;
}

重叠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。 
实例代码:
#include <winsock2.h>
#include <stdio.h>
#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;
}

完成端口模型

重叠IO模型,
它已经把我们的等待数据到来和拷贝数据到我们程序的缓冲区这个时间全部交给了操作系统去完成了,
它已经很完善了。
但是,如果我们想要把服务端的性能做的更好一点的话,它还是有点不足的,
比如说,重叠IO它只有一个工作者线程在工作,要想获得更高的效率,我们应该增加一些线程。
而且,重叠IO它只能同时进行64个客户端的数据收发,这也是一个很大的问题。
为此,就产生了完成端口模型,它就解决了这些问题。
可以说,在windows下做大型服务端的开发,完成端口模型是不二之选。

前面几种模型呢,我们都是在main函数里面直接写的,由于这个完成端口比较重要,
我们就给它稍微封装一下。
首先,新建一个完成端口的类,
在构造函数里面我们对它的一些数据成员进行初始化和对网络环境的初始化。
其次它有几个公有成员函数,第一个是Initialize初始化函数,来看下它的声明:
bool Initialize(
  NOTIFYPROC pNotifyProc, //回调函数地址
  int nPort               //端口
);
第一个参数的的类型是我们自定义的一个函数指针,它的格式如下: 
typedef void (CALLBACK* NOTIFYPROC)(LPVOID pContext,LPBYTE lpBuf, DWORD dwSize);
在Initialize这个函数里面我们做了哪些事情呢,
首先,我们调用WSASocket来建立一个监听的udp的socket,
然后进行绑定,因为是udp所以不用进行监听了。
接下来,我们调用CreateIoCompletionPort创建一个完成端口对象,其声明如下: 
HANDLE CreateIoCompletionPort (
  HANDLE FileHandle,              // 文件的句柄
  HANDLE ExistingCompletionPort,  // 已存在的完成端口对象,为NULL表示新建一个完成端口
  ULONG_PTR CompletionKey,        // 传递给处理函数的参数
  DWORD NumberOfConcurrentThreads // 最大访问该IO操作的线程数
);
我们给这些参数赋值为无效值或空,这样我们就新建了一个完成端口了。 
接下来,我们再次调用CreateIoCompletionPort函数,
这是我们给的参数与上面的不一样了,
第一个参数我们把监听的那个socket传进去,因为socket也是一个特殊的文件句柄。
第二个参数我们把刚创建的完成端口传进去。
第三个参数我们也把监听的socket传过去,
第四个参数我们一般把它赋值为CPU核心数的2倍左右的个数。
调用完该函数之后,我们就已经将一个socket与我们的完成端口进行了一个绑定。
与完成端口绑定好了之后,
我们就创建一些工作者线程,创建线程的数量与上面那个函数的第四个参数的个数就行了。
创建好线程之后我们会调用另外一个成员函数PostRecv函数,来投递一个接收请求。
至此,Initialize成员函数就到此为止了。

接下来看下工作者线程是如何工作的。
在工作者线程函数里,有一个循环,我们调用了GetQueuedCompletionStatus这么一个函数,
它和重叠IO模型l里面的WSAWaitForMultipleEvents函数的功能是相似的,
这个函数呢是用于监控一个完成端口,看这个完成端口上所投递的任务是否完成,
如果完成就返回,否则继续监控。
BOOL GetQueuedCompletionStatus(
  HANDLE CompletionPort,       // 指定的完成端口
  LPDWORD lpNumberOfBytes,     // 传输的字节数
  PULONG_PTR lpCompletionKey,  // 用于接收IO操作完成后与文件句柄相关联的Completion Key
  LPOVERLAPPED *lpOverlapped,  // overlapped结构
  DWORD dwMilliseconds         // 等待时间
);
我们在最后一个参数传了一个INFINITE,表示无限的等待。
第三个参数overlapped结构我们也和重叠IO的单IO结构一样,对OVERLAPPED结构进行了扩展。
该类声明如下: 
class OVERLAPPEDPLUS 
{
public:
	OVERLAPPED m_ol;				//overlapped结构
	WSABUF DataBuf;    				//WSABUF结构,里面保存了大小和指针
	CHAR   Buffer[BUFSIZE];   		//缓冲区
	unsigned long recvBytes;     	//存储接收到的字节数
    SOCKADDR_IN remoteAddr; 		//存储数据来源IP地址
    int remoteAddrLen;              //存储数据来源IP地址长度
	OVERLAPPEDPLUS() 
	{
		ZeroMemory(this, sizeof(OVERLAPPEDPLUS));
	}
};
当GetQueuedCompletionStatus成功返回的时候,
我们可以来调用成员函数OnRecv来处理接收到的数据。
至此,我们的工作者线程函数就完成了。
我们在Initialize函数最后投递了一个接收请求,我们来看下这个成员函数的实现。
在这个函数的开始处,
我们调用AllocateContext成员函数为OVERLAPPEDPLUS申请了一段内存,
然后,我们调用WSARecvFrom来接收数据,由于该函数是异步的,它会立即返回,
其声明如下:
int WSARecvFrom(  
  SOCKET s,                     				//监听的socket                          
  LPWSABUF lpBuffers,                                     //WSABUFFER指针
  DWORD dwBufferCount,                                    //buf的大小
  LPDWORD lpNumberOfBytesRecvd,                           //接收的字节数
  LPDWORD lpFlags,                                        //标志位
  struct sockaddr FAR *lpFrom,                            //接收原端口地址
  LPINT lpFromlen,                                        //接收原端口地址的大小
  LPWSAOVERLAPPED lpOverlapped,                           //overlapped结构
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine  //指向接收操作完成后调用的例程的指针,这里给NULL就行了
);
当这个函数成功返回的时候,
我们的工作者线程函数里面的GetQueuedCompletionStatus就会返回,
我们给GetQueuedCompletionStatus里面传了一个overlapped结构,
返回时它也会返回overlapped结构的地址,找到了地址,我们自然也知道了里面的一些信息了。 

PostRecv完了之后,我们来看下OnRecv成员函数,
在这个函数里面我们定义的一个回调函数,
在里面我们来真正的处理接收到的数据,
然后调用成员函数MoveToFreePool来释放内存,
最后再调用PostRecv来继续投递一个接收请求。
我们的OnRecv就做这么多。

在PostRecv开始处我们为OVERLAPPED结构申请了一段内存,
调用AllocateContext来实现,
这个函数里,我们调用Interlocked系列来做了一个自旋锁,
因为有多个线程访问关键数据,我们必须做线程同步操作。
然后我们又做了一个最简单的内存池。
我们用两个链表来保存OVERLAPPED结构的指针,
一个是保存正在使用的,一个是空闲的,
当我们需要申请内存的时候,我们就查看空闲链表里面是否为空,
不为空我们就直接拿来用,否则,就申请。然后初始化一下OVERLAPPEDPLUS的成员。

在MoveToFreePool里面,我们也会做个自旋锁来进行线程同步,
然后遍历正在使用的链表,找到要释放的指针,将其移除链表,然后加到空闲链表里边去。
我们来看下资源释放的成员函数CloseCompletionPort,
我们在Initialize函数里创建了一些线程,
我们需要调用PostQueuedCompletionStatus这个函数将其释放,其声明如下:
BOOL PostQueuedCompletionStatus(
  HANDLE CompletionPort,            // 完成端口句柄
  DWORD dwNumberOfBytesTransferred, // 传输的字节数,这里给0
  ULONG_PTR dwCompletionKey,        // 这里给0 
  LPOVERLAPPED lpOverlapped         // overlapped结构,给0
);
我们将后三个参数全部给0,
特别是第三个,
我们会在工作者线程函数调用GetQueuedCompletionStatus之后,检查一下第三个参数是否为0,
若是则表示要关闭了,就退出该工作者线程,线程就自然结束了。
至于剩下的操作就是删除两个链表里面的指针了。 
示例代码:
#ifndef AFX_IOCPSERVER_H__75B80E90_FD25_4FFB_B273_0090AA43BBDF__INCLUDED_
#define AFX_IOCPSERVER_H__75B80E90_FD25_4FFB_B273_0090AA43BBDF__INCLUDED_
 
#include <WINSOCK2.H>
#include <windows.h>
#include <algorithm>
#include <list>
 
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#define BUFSIZE     512 
 
class OVERLAPPEDPLUS 
{
public:
	OVERLAPPED m_ol;				//overlapped结构
	WSABUF DataBuf;    				//WSABUF指针
	CHAR Buffer[BUFSIZE];    		//缓冲区
	unsigned long recvBytes;      	//存储接收到的字节数
    SOCKADDR_IN remoteAddr; 		//存储数据来源IP地址
    int remoteAddrLen;              //存储数据来源IP地址长度
 
	OVERLAPPEDPLUS() 
	{
		ZeroMemory(this, sizeof(OVERLAPPEDPLUS));
	}
};
 
//函数指针
typedef void (CALLBACK* NOTIFYPROC)(LPVOID pContext,LPBYTE lpBuf, DWORD dwSize);
typedef list<OVERLAPPEDPLUS* > ContextList;
 
class CIOCPServer
{
public:
	CIOCPServer();
	virtual ~CIOCPServer();
 
	NOTIFYPROC m_pNotifyProc;
	bool Initialize(NOTIFYPROC pNotifyProc, int nPort);
	static unsigned __stdcall WorkerThreadFunc(LPVOID WorkContext);
	bool PostSend(OVERLAPPEDPLUS* pContext, LPBYTE lpData, UINT nSize);
	bool PostRecv();
	volatile LONG g_fResourceInUse ;	//实现自旋锁进行内存池管理
	void Shutdown();
	
	LONG m_nCurrentThreads;				//当前开启的线程数
	LONG m_nBusyThreads;				//正在工作的线程数
 
	ContextList	m_listContexts;			//正在使用的链表
	ContextList	m_listFreePool;			//空闲链表
	
protected:
	void MoveToFreePool(OVERLAPPEDPLUS *pContext);
	OVERLAPPEDPLUS*  AllocateContext();
 
	bool m_bInit;						//是否完成初始化并运行
	void CloseCompletionPort();
	void Stop();
 
	SOCKET m_socListen;    
	HANDLE m_hCompletionPort;
	bool m_bTimeToKill;
 
	string GetHostName(SOCKET socket);
	bool OnRecv(OVERLAPPEDPLUS* pContext, DWORD dwSize = 0);
};
#endif


#include "IOCPServer.h"
CIOCPServer::CIOCPServer()
{
	printf("CIOCPServer=%p\n",this);	
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2,2), &wsaData); 
	m_socListen	= NULL;
	m_bTimeToKill = false;
	m_hCompletionPort = NULL;
	m_bInit = false;
	m_nCurrentThreads = 0;
	m_nBusyThreads = 0;
 
	m_nSendKbps = 0;
	m_nRecvKbps = 0;
	g_fResourceInUse = FALSE;
}
 
CIOCPServer::~CIOCPServer()
{
	printf("IOCPServer[%p] is shuting down\n",this);
	Shutdown();
	WSACleanup();
}
 
bool CIOCPServer::Initialize(NOTIFYPROC pNotifyProc,int nPort)
{
	m_pNotifyProc	= pNotifyProc;
	DWORD Flags = 0;    
	//创建监听的socket
	m_socListen = WSASocket(
		AF_INET, 
		SOCK_DGRAM, 
		IPPROTO_IP, 
		NULL, 
		0, 
		WSA_FLAG_OVERLAPPED);
 
	if (m_socListen == INVALID_SOCKET)
	{
		printf(
			"Could not create listen socket %ld\n",
			WSAGetLastError());
		return false;
	}
	
	//绑定
	SOCKADDR_IN		saServer;		
	saServer.sin_port = htons(nPort);
	saServer.sin_family = AF_INET;
	saServer.sin_addr.s_addr = INADDR_ANY;
 
	nRet = bind(
		m_socListen, 
		(LPSOCKADDR)&saServer, 
		sizeof(struct sockaddr));
	if (nRet == SOCKET_ERROR)
	{
		printf("bind() error %ld\n",WSAGetLastError());
		closesocket(m_socListen);
		return false;
	}
	
	printf("CIOCPServer[%p] is binded on port %d now\n",this,nPort);
	//创建完成端口
	m_hCompletionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0 );
	if ( m_hCompletionPort == NULL ) 
	{
		printf("CIOCPServer[%p] CreateIoCompletionPort failed\n",this);
		closesocket( m_socListen );
		return false;
	}
	
	SYSTEM_INFO systemInfo;
	GetSystemInfo( &systemInfo );
	// 每个CPU核心建立2个工作者线程
	UINT nWorkerCnt = systemInfo.dwNumberOfProcessors * 2;
	//将完成端口与监听的socket进行绑定
	CreateIoCompletionPort (
		(HANDLE)m_socListen,
		m_hCompletionPort,
		(DWORD)m_socListen,
		nWorkerCnt);  	
	
	// 创建一些线程
	HANDLE hWorker;
	for ( UINT i = 0; i < nWorkerCnt; i++ ) 
	{
		UINT tmp=i+1;
		hWorker=CreateThread(
			NULL,
			0,
			(LPTHREAD_START_ROUTINE)WorkerThreadFunc,
			this,
			0,
			(LPDWORD)&tmp);
		if (hWorker == NULL ) 
		{
			CloseHandle( m_hCompletionPort );
			return false;
		}
		
		//投递一个接收请求
		PostRecv();
		m_nCurrentThreads++;
		//关闭句柄,那么线程就没人管的运行了
		CloseHandle(hWorker);
	}
	
	m_bInit = true;
	return true;
}
 
unsigned CIOCPServer::WorkerThreadFunc(LPVOID thisContext)    
{
	CIOCPServer* pThis = reinterpret_cast<CIOCPServer*>(thisContext);
    HANDLE hCompletionPort = pThis->m_hCompletionPort;
    DWORD dwIoSize;
	OVERLAPPEDPLUS*	pOverlapPlus;
	bool bError;
	DWORD nSocket;  
	InterlockedIncrement(&pThis->m_nBusyThreads);
	while(true)
	{
		pOverlapPlus = NULL;
		bError = false;

		InterlockedDecrement(&pThis->m_nBusyThreads);
		//无线等待是否有任务完成了
		BOOL bIORet = GetQueuedCompletionStatus(
			   hCompletionPort,
			   &dwIoSize,
			   (LPDWORD) &nSocket,
			   (LPOVERLAPPED *)&pOverlapPlus, 
			   INFINITE);
		//nSocket=0表示要退出线程了
		if (nSocket == 0)
		{
			break;
		}
		
		int nBusyThreads = InterlockedIncrement(&pThis->m_nBusyThreads);
		DWORD dwIOError = GetLastError();
		//若接收出错了
		if (dwIoSize == 0 || dwIOError == SOCKET_ERROR || bIORet == 0)
		{
			pThis->MoveToFreePool(pOverlapPlus);  
			continue;    
		}
		
		//处理接收的数据
		pThis->OnRecv(pOverlapPlus,dwIoSize);	
	}
 
	InterlockedDecrement(&pThis->m_nCurrentThreads);
	InterlockedDecrement(&pThis->m_nBusyThreads);
   	return 0;
} 
 
string CIOCPServer::GetHostName(SOCKET socket)
{
	sockaddr_in  sockAddr;
	memset(&sockAddr, 0, sizeof(sockAddr));
	int nSockAddrLen = sizeof(sockAddr);
	BOOL bResult = getpeername(socket,(SOCKADDR*)&sockAddr, &nSockAddrLen);
	return bResult != INVALID_SOCKET ? inet_ntoa(sockAddr.sin_addr) : "";
}
 
bool CIOCPServer::PostRecv()
{
	// 分配内存
	OVERLAPPEDPLUS * PerIoData =AllocateContext();
	if (PerIoData == NULL)    
	{    
		printf("GlobalAlloc() failed with error %d\n", GetLastError());    
		return false;    
	}   
 
	DWORD RecvBytes=0;
	DWORD Flags=0;
	//接收数据
	if (WSARecvFrom(
		m_socListen, 
		&(PerIoData->DataBuf), 
		1, 
		&RecvBytes, 
		&Flags,   
		(sockaddr*)&(PerIoData->remoteAddr),
		&PerIoData->remoteAddrLen,    
		&(PerIoData->m_ol), NULL) == SOCKET_ERROR)    
	{    
		if (WSAGetLastError() != ERROR_IO_PENDING)    
		{    
			printf("WSARecvFrom() failed with error %d\n", WSAGetLastError());    
			return false;
		}
	}    
	
	return true;
}
 
bool CIOCPServer::PostSend(
	OVERLAPPEDPLUS* pContext, 
	LPBYTE lpData, 
	UINT nSize)
{
	if (pContext == NULL)
	{
		return false;
	}
		
	//发送数据
	sendto( 
		m_socListen, 
		(char *)lpData,
		nSize, 
		0 , 
		(sockaddr*)&(pContext->remoteAddr), 
		sizeof(pContext->remoteAddr)); 
 
	return true;
}
 
bool CIOCPServer::OnRecv(OVERLAPPEDPLUS* pContext, DWORD dwIoSize)
{
	if (dwIoSize == 0)
	{
		return false;
	}
	
	printf(
		"BusyThread: %d\t- %s\n",
		m_nBusyThreads,
		pContext->Buffer);
	//回调函数,处理数据
	m_pNotifyProc(pContext,(LPBYTE)pContext->Buffer, dwIoSize);
	//释放内存
	MoveToFreePool(pContext); 
	PostRecv();
	return true;
}

void CIOCPServer::CloseCompletionPort()
{
	while (m_nCurrentThreads)
	{
		PostQueuedCompletionStatus(m_hCompletionPort, 0, (DWORD) NULL, NULL);
		Sleep(100);
	}
	
	// 关闭完成端口句柄
	CloseHandle(m_hCompletionPort);
	//清除指针操作
	OVERLAPPEDPLUS* pContext = NULL;
	list<OVERLAPPEDPLUS* >::iterator iter;
	for(iter=m_listContexts.begin();iter!=m_listContexts.end();)
	{
		pContext=*iter++;
		delete pContext;
	}
	
	m_listContexts.clear();
	for(iter=m_listFreePool.begin();iter!=m_listFreePool.end();)
	{
		pContext=*iter++;
		delete pContext;
	}
	
	m_listFreePool.clear();
}
 
void CIOCPServer::Shutdown()
{
	if (m_bInit == false)
	{
		return;
	}
		
	m_bInit = false;
	m_bTimeToKill = true;
 
	CloseCompletionPort();
	closesocket(m_socListen);	
}
 
void CIOCPServer::MoveToFreePool(OVERLAPPEDPLUS *pContext)
{
	//线程同步,自旋锁
	while(InterlockedExchange(&g_fResourceInUse,TRUE) == TRUE)
	{
		Sleep(1);
	}
		
	//将要释放的指针添加到空闲链表中
    ContextList::iterator iter;
	iter=find(m_listContexts.begin(),m_listContexts.end(),pContext);
    if (iter!=m_listContexts.end())
    {
		m_listContexts.remove(pContext);
		m_listFreePool.push_back(pContext);
    }
    
    InterlockedExchange(&g_fResourceInUse,FALSE);
}
 
OVERLAPPEDPLUS*  CIOCPServer::AllocateContext()
{
	OVERLAPPEDPLUS* pContext = NULL;
	//自旋锁,线程同步
	while(InterlockedExchange(&g_fResourceInUse,TRUE) == TRUE)Sleep(1);
	//若空闲链表不为空
	if (!m_listFreePool.empty())
	{
		pContext = m_listFreePool.front();
		m_listFreePool.remove(pContext);
	}
	else
	{
		pContext = new OVERLAPPEDPLUS();
	}
	
	//初始化赋值操作
	ZeroMemory(pContext, sizeof(OVERLAPPEDPLUS));
	pContext->DataBuf.len = BUFSIZE;
	pContext->DataBuf.buf = pContext->Buffer;   
	pContext->recvBytes = 24;
    pContext->remoteAddrLen = sizeof(pContext->remoteAddr);
	m_listContexts.push_back(pContext);
	InterlockedExchange(&g_fResourceInUse,FALSE);
	return pContext;
} 



#include <conio.h>
#include "IOCPServer.h"
 
void CALLBACK cb(LPVOID pContext,LPBYTE lpBuf, DWORD dwSize)
{
	printf("%s\n",lpBuf);
	return;
}

int main(int argc, char * argv[])
{
	CIOCPServer * server=new CIOCPServer();
	server->Initialize(cb,6000);
	_getch();
	return 0;
}

参考

https://blog.csdn.net/Timmiy/article/details/52167022
https://blog.csdn.net/Timmiy/article/details/52201534
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

raindayinrain

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

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

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

打赏作者

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

抵扣说明:

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

余额充值