• windows套接字学习


    学习记录~~~~~~~~~~~~~~~~~~~~~~~


    windows 提供了一些I/O 模型帮助应用程序以异步方式在一个或者多个套接字上管理I/O,分为6种


    1)阻塞blocking 模型,2)非阻塞 /选择select 模型 , 3)WSAAsyncSelect 模型,4)WSAEventSelect 模型,

    5)重叠 overlapped 模型,6)完成端口 completion port 模型


    非阻塞 

    u_long ul =1;

    SOCKET s = socket)AF_INET,SOCK_STREAM,0);

    ioctlsocket(s,FINNBIO,(u_long*)&url);


    int select(
      __in          int nfds,
      __in_out      fd_set* readfds,   //1)检查可读性如果2)listen 被调用,那么accpet函数成功3)连接关闭,重启,中断
      __in_out      fd_set* writefds,  //1)数据能够发送2)如果一个非阻塞连接调用正在被处理,连接已经成功
      __in_out      fd_set* exceptfds, //1)如果一个非阻塞连接调用正在被处理,连接试图失败,2)OOB数据可读
      __in          const struct timeval* timeout
    );
    

    好处是 程序能够在单个线程内同时处理多个套接字连接,避免了阻塞模型下的线程膨胀问题。

    但是添加到 fd_set 结构的数量是由限制的,默认情况下最大值为FD_SETSIZE 64 这个值可以被设置但不能超过1024,太大的haunted会受到影响,

    例如有1000个套接字,那么在调用select之前就不得不设置这1000个套接字,select返回之后,又必须检查这1000个套接字


    FD_ZERO(*set)     //初始化 fd_set 空集合,集合在使用前应该是空的

    FD_CLR(s,*set)    //从set 移除套接字 s

    FD_ISSET(s,*set) //检查s 是不是set 的成员,如果是返回TRUE

    FD_SET(s,*set)     //添加套接字到集合

    转载代码:

    #include "../common/initsock.h"
    #include <stdio.h>
    
    CInitSock theSock;		// 初始化Winsock库
    int main()
    {
    	USHORT nPort = 4567;// 此服务器监听的端口号
    
    	// 创建监听套节字
    	SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	
    	sockaddr_in sin;
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(nPort);
    	sin.sin_addr.S_un.S_addr = INADDR_ANY;
    	// 绑定套节字到本地机器
    	if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
    	{
    		printf(" Failed bind() 
    ");
    		return -1;
    	}
    	// 进入监听模式
    	::listen(sListen, 5);
    
    		// select模型处理过程
    	// 1)初始化一个套节字集合fdSocket,添加监听套节字句柄到这个集合
    	fd_set fdSocket;		// 所有可用套节字集合
    	FD_ZERO(&fdSocket);
    	FD_SET(sListen, &fdSocket);
    // 	typedef struct fd_set {
    //         u_int fd_count;               /* how many are SET? */
    //         SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
    //		} fd_set;
    	while(TRUE)
    	{
    		// 2)将fdSocket集合的一个拷贝fdRead传递给select函数,
    		// 当有事件发生时,select函数移除fdRead集合中没有未决I/O操作的套节字句柄,然后返回。
    		fd_set fdRead = fdSocket;
    		int nRet = ::select(0, &fdRead, NULL, NULL, NULL);
    		if(nRet > 0)
    		{
    			// 3)通过将原来fdSocket集合与select处理过的fdRead集合比较,
    			// 确定都有哪些套节字有未决I/O,并进一步处理这些I/O。
    			for(int i=0; i<(int)fdSocket.fd_count; i++)
    			{
    				if(FD_ISSET(fdSocket.fd_array[i], &fdRead))
    				{
    					if(fdSocket.fd_array[i] == sListen)		// (1)监听套节字接收到新连接
    					{
    						if(fdSocket.fd_count < FD_SETSIZE)
    						{
    							sockaddr_in addrRemote;
    							int nAddrLen = sizeof(addrRemote);
    							SOCKET sNew = ::accept(sListen, (SOCKADDR*)&addrRemote, &nAddrLen);
    							FD_SET(sNew, &fdSocket);
    							printf("接收到连接(%s)
    ", ::inet_ntoa(addrRemote.sin_addr));
    						}
    						else
    						{
    							printf(" Too much connections! 
    ");
    							continue;
    						}
    					}
    					else
    					{
    						char szText[256];
    						int nRecv = ::recv(fdSocket.fd_array[i], szText, strlen(szText), 0);
    						if(nRecv > 0)						// (2)可读
    						{
    							szText[nRecv] = '';
    							printf("接收到数据:%s 
    ", szText);
    						}
    						else								// (3)连接关闭、重启或者中断
    						{
    							::closesocket(fdSocket.fd_array[i]);
    							FD_CLR(fdSocket.fd_array[i], &fdSocket);
    						}
    					}
    				}
    			}
    		}
    		else
    		{
    			printf(" Failed select() 
    ");
    			break;
    		}
    	}
    	return 0;
    }
    

    WSAAsyncSelect  模型

    允许应用程序以windows 消息的形式接受网络事件通知,这个模型是为了适应windows的消息驱动环境而设置的,限制许多对性能要求不高的网络应用程序都采用WSAAsyncSelect 模型,MFC中的CSocket也是使用它

    WSAAsyncSelect  函数自动把套接字设为 非阻塞模式,并且为套接字绑定一个窗口句柄,当有网络事件发生时,便向这个窗口发送消息

    int WSAAsyncSelect(
      __in          SOCKET s,          //需要设置的套接字句柄
      __in          HWND hWnd,         //指定一个窗口句柄,套接字的通知消息将发送到与其对应的窗口中
      __in          unsigned int wMsg, //网络事件到来时接收到的消息ID,可以选择 WM_USER以上的数值
      __in          long lEvent        //指定那些通知码需要发送
    );
    
    FD_READ Set to receive notification of readiness for reading.
    FD_WRITE Wants to receive notification of readiness for writing.
    FD_OOB Wants to receive notification of the arrival of OOB data.
    FD_ACCEPT Wants to receive notification of incoming connections.
    FD_CONNECT Wants to receive notification of completed connection or multipoint join operation.
    FD_CLOSE Wants to receive notification of socket closure.

    转载代码:

    #include "../common/initsock.h"
    #include <stdio.h>
    
    #define WM_SOCKET WM_USER + 101		// 自定义消息
    CInitSock theSock;
    
    
    LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
    
    int main()
    {
    	char szClassName[] = "MainWClass";	
    	WNDCLASSEX wndclass;
    	// 用描述主窗口的参数填充WNDCLASSEX结构
    	wndclass.cbSize = sizeof(wndclass);	
    	wndclass.style = CS_HREDRAW|CS_VREDRAW;	
    	wndclass.lpfnWndProc = WindowProc;	
    	wndclass.cbClsExtra = 0;		
    	wndclass.cbWndExtra = 0;		
    	wndclass.hInstance = NULL;		
    	wndclass.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);	
    	wndclass.hCursor = ::LoadCursor(NULL, IDC_ARROW);		
    	wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);	
    	wndclass.lpszMenuName = NULL;		
    	wndclass.lpszClassName = szClassName ;
    	wndclass.hIconSm = NULL;	
    	::RegisterClassEx(&wndclass); 
    	// 创建主窗口
    	HWND hWnd = ::CreateWindowEx( 
    		0,
    //IN DWORD dwExStyle, 
    //IN LPCSTR lpClassName, 
    //IN LPCSTR lpWindowName, 
    //IN DWORD dwStyle, 
    //IN int X, IN int Y, 
    //IN int nWidth, IN int nHeight, 
    //IN HWND hWndParent, IN HMENU hMenu, 
    //IN HINSTANCE hInstance, IN LPVOID lpParam						
    		szClassName,			
    		"",	
    		WS_OVERLAPPEDWINDOW,	
    		CW_USEDEFAULT,	
    		CW_USEDEFAULT,		
    		CW_USEDEFAULT,	
    		CW_USEDEFAULT,			
    		NULL,				
    		NULL,		
    		NULL,	
    		NULL);		
    	if(hWnd == NULL)
    	{
    		::MessageBox(NULL, "创建窗口出错!", "error", MB_OK);
    		return -1;
    	}
    
    	USHORT nPort = 4567;	// 此服务器监听的端口号
    
    	// 创建监听套节字
    	SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	
    	sockaddr_in sin;
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(nPort);
    	sin.sin_addr.S_un.S_addr = INADDR_ANY;
    	// 绑定套节字到本地机器
    	if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
    	{
    		printf(" Failed bind() 
    ");
    		return -1;
    	}
    
    	// 将套接字设为窗口通知消息类型。
    	::WSAAsyncSelect(sListen, hWnd, WM_SOCKET, FD_ACCEPT|FD_CLOSE);//Wants to receive notification of incoming connections.
    																   //Wants to receive notification of socket closure.
    	// 进入监听模式
    	::listen(sListen, 5);
    
    	// 从消息队列中取出消息
    	MSG msg;	
    	while(::GetMessage(&msg, NULL, 0, 0))
    	{
    		// 转化键盘消息
    		::TranslateMessage(&msg);
    		// 将消息发送到相应的窗口函数
    		::DispatchMessage(&msg);
    	}
    	// 当GetMessage返回0时程序结束
    	return msg.wParam;
    }
    
    
    LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    	switch (uMsg)
    	{	
    	case WM_SOCKET:
    		{
    			// 取得有事件发生的套节字句柄
    			SOCKET s = wParam;
    			// 查看是否出错
    			if(WSAGETSELECTERROR(lParam))
    			{
    				::closesocket(s);
    				return 0;
    			}
    			// 处理发生的事件
    			switch(WSAGETSELECTEVENT(lParam))
    			{
    			case FD_ACCEPT:		// 监听中的套接字检测到有连接进入
    				{
    					SOCKET client = ::accept(s, NULL, NULL);
    					::WSAAsyncSelect(client, hWnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);
    				}
    				break;
    			case FD_WRITE:
    				{
    				}
    				break;
    			case FD_READ:
    				{
    					char szText[1024] = { 0 };
    					if(::recv(s, szText, 1024, 0) == -1)
    						::closesocket(s);
    					else
    						printf("接收数据:%s", szText);
    				}
    				break;
    			case FD_CLOSE:
    				{ 
    					::closesocket(s);
    				}
    				break;
    			}
    		}
    		return 0;
    	case WM_DESTROY:
    		::PostQuitMessage(0) ;
    		return 0 ;
    	}
    
    	// 将我们不处理的消息交给系统做默认处理
    	return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
    }


    WSAEventSelect 模型

    与 WSAAsyncSelect 类似,允许应用程序在一个或者多个套接字上接收基于事件的网络通知。

    类似是以为它也接收 FD_XXX 类型的网络事件,不过并不依靠windows 的消息驱动机制,而是经由事件对象句柄通知

    使用这个模型的基本思路是为了感兴趣的一组网络事件创建一个事件对象,再调用WSAEventSelct函数将网络事件和事件对象关联在一起

    当网络事件发生时,winsock使相应的事件对象守信,在事件对象上的等待函数就会返回。之后调用WSAEnumNetworkEvents函数便可获得到底发生了什么网络事件

    WSAEVENT WSACreateEvent(void); //创建事件对象,返回一个手工重置的事件对象句柄

    int WSAEventSelect(            //将指定的一组网络事件与它关联在一起
      __in          SOCKET s,
      __in          WSAEVENT hEventObject,  //事件对象句柄
      __in          long lNetworkEvents     //感兴趣的FD_XXX网络事件的组合
    );
    
    关联之后应用程序就可以在事件对象上等待了,winsock提供了WSAWaitForMultipleEvents函数在一个或多个事件对象上等待,当所等待的事件收信/指定的时间过去,函数返回

    DWORD WSAWaitForMultipleEvents(
      __in          DWORD cEvents,            //lphEvents 所指的数组中事件对象句柄的个数
      __in          const WSAEVENT* lphEvents,//指向一个事件对象句柄数组
      __in          BOOL fWaitAll,            //是否等待所有事件对象,如果设为0,那么返回值只能指明一个,就是句柄数组中最前面一个。解决办法就是调用几次函数确定其状态
      __in          DWORD dwTimeout,          //指定等待时间,WSA_INFINITE为无穷大
      __in          BOOL fAlertable           //WSAEventSelct 模型可以忽略,FALSE
    );
    
    最多支持 64 个套接字,如果需要更多套接字,就需要创建额外的工作线程了

    一旦事件对象收信了,那么找到与之对应的套接字,然后调用 下面 金额以查看发生了什网络事件

    int WSAEnumNetworkEvents(
      __in          SOCKET s,
      __in          WSAEVENT hEventObject,             //对应的事件对象句柄,指定了参数就会重置这个事件对象的状态
      __out         LPWSANETWORKEVENTS lpNetworkEvents //指向一个结构体,取得在套接字上发生的网络事件和相关的出错代码
    );
    
    typedef struct _WSANETWORKEVENTS {
      long lNetworkEvents;             //指向已经发生的网络事件 ,FD_ACCEPT,FD_READ
      int iErrorCode[FD_MAX_EVENTS];   //网络事件的出错代码数组
    } WSANETWORKEVENTS, 
     *LPWSANETWORKEVENTS;
    if(event.hEventObject & FD_READ) //处理FD_READ 通知消息

    {

         if(event.iErrorCode[FD_READ_BIT] != 0)

    {

    ````````//FD_READ 出错

    }

    }

    WSAEventSelect 模型简单易用,也不需要窗口环境,缺点就是最多等待 64 个事件对象的限制,当套接字连接数量增加时就只能创建多个线程来处理I/O,也就是使用所谓的线程池

    转载代码:

    #include "initsock.h"
    #include <stdio.h>
    #include <iostream.h>
    #include <windows.h>
    
    // 初始化Winsock库
    CInitSock theSock;
    
    int main()
    {
    	// 事件句柄和套节字句柄表
    	WSAEVENT	eventArray[WSA_MAXIMUM_WAIT_EVENTS];
    	SOCKET		sockArray[WSA_MAXIMUM_WAIT_EVENTS];
    	int nEventTotal = 0;
    
    	USHORT nPort = 4567;	// 此服务器监听的端口号
    
    	// 创建监听套节字
    	SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	
    	sockaddr_in sin;
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(nPort);
    	sin.sin_addr.S_un.S_addr = INADDR_ANY;
    	if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
    	{
    		printf(" Failed bind() 
    ");
    		return -1;
    	}
    	::listen(sListen, 5);
    
    	// 创建事件对象,并关联到新的套节字
    	WSAEVENT event = ::WSACreateEvent();
    	::WSAEventSelect(sListen, event, FD_ACCEPT|FD_CLOSE);
    	// 添加到表中
    	eventArray[nEventTotal] = event;
    	sockArray[nEventTotal] = sListen;	
    	nEventTotal++;
    
    	// 处理网络事件
    	while(TRUE)
    	{
    		// 在所有事件对象上等待
    		int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);
    		// 对每个事件调用WSAWaitForMultipleEvents函数,以便确定它的状态
    		nIndex = nIndex - WSA_WAIT_EVENT_0;
    		for(int i=nIndex; i<nEventTotal; i++)
    		{
    			nIndex = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 1000, FALSE);
    			if(nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
    			{
    				continue;
    			}
    			else
    			{
    				// 获取到来的通知消息,WSAEnumNetworkEvents函数会自动重置受信事件
    				WSANETWORKEVENTS event;
    				::WSAEnumNetworkEvents(sockArray[i], eventArray[i], &event);
    				if(event.lNetworkEvents & FD_ACCEPT)				// 处理FD_ACCEPT通知消息
    				{
    					if(event.iErrorCode[FD_ACCEPT_BIT] == 0)//接收成功
    					{
    						if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
    						{
    							printf(" Too many connections! 
    ");
    							continue;
    						}
    						SOCKET sNew = ::accept(sockArray[i], NULL, NULL);
    						WSAEVENT event = ::WSACreateEvent();
    						::WSAEventSelect(sNew, event, FD_READ|FD_CLOSE|FD_WRITE);
    						// 添加到表中
    						eventArray[nEventTotal] = event;
    						sockArray[nEventTotal] = sNew;	
    						nEventTotal++;
    					}
    				}
    				else if(event.lNetworkEvents & FD_READ)			// 处理FD_READ通知消息
    				{
    					if(event.iErrorCode[FD_READ_BIT] == 0)
    					{
    						char szText[256];
    						int nRecv = ::recv(sockArray[i], szText, strlen(szText), 0);
    						if(nRecv > 0)				
    						{
    							szText[nRecv] = '';
    							printf("接收到数据:%s 
    ", szText);
    						}
    					}
    				}
    				else if(event.lNetworkEvents & FD_CLOSE)		// 处理FD_CLOSE通知消息
    				{
    					if(event.iErrorCode[FD_CLOSE_BIT] == 0)
    					{
    						::closesocket(sockArray[i]);
    						for(int j=i; j<nEventTotal-1; j++)
    						{
    							sockArray[j] = sockArray[j+1];
    							sockArray[j] = sockArray[j+1];	
    						}
    						nEventTotal--;
    					}
    				}
    				else if(event.lNetworkEvents & FD_WRITE)		// 处理FD_WRITE通知消息
    				{
    				}
    			}
    		}
    	}
    	return 0;
    }


    WSAEventSelect 多IO应用~_~

    创建 多个线程来处理 就好很多了~~~~~~~~~~~~~~~~~~~~~

    转载代码:

    H:

    DWORD WINAPI ServerThread(LPVOID lpParam);
    
    // 套节字对象
    typedef struct _SOCKET_OBJ
    {
    	SOCKET s;					// 套节字句柄
    	HANDLE event;				// 与此套节字相关联的事件对象句柄
    	sockaddr_in addrRemote;		// 客户端地址信息
    
    	_SOCKET_OBJ *pNext;			// 指向下一个SOCKET_OBJ对象,为的是连成一个表
    } SOCKET_OBJ, *PSOCKET_OBJ;
    
    // 线程对象
    typedef struct _THREAD_OBJ
    {
    	HANDLE events[WSA_MAXIMUM_WAIT_EVENTS];	// 记录当前线程要等待的事件对象的句柄
    	int nSocketCount;						// 记录当前线程处理的套节字的数量 <=  WSA_MAXIMUM_WAIT_EVENTS
    
    	PSOCKET_OBJ pSockHeader;				// 当前线程处理的套节字对象列表,pSockHeader指向表头
    	PSOCKET_OBJ pSockTail;					// pSockTail指向表尾
    
    	CRITICAL_SECTION cs;					// 关键代码段变量,为的是同步对本结构的访问
    	_THREAD_OBJ *pNext;						// 指向下一个THREAD_OBJ对象,为的是连成一个表
    
    } THREAD_OBJ, *PTHREAD_OBJ;
    
    // 线程列表
    PTHREAD_OBJ g_pThreadList;		// 指向线程对象列表表头
    CRITICAL_SECTION g_cs;			// 同步对此全局变量的访问
    
    // 状态信息
    LONG g_nTatolConnections;		// 总共连接数量
    LONG g_nCurrentConnections;		// 当前连接数量
    
    // 申请一个套节字对象,初始化它的成员
    PSOCKET_OBJ GetSocketObj(SOCKET s)	
    {
    	PSOCKET_OBJ pSocket = (PSOCKET_OBJ)::GlobalAlloc(GPTR, sizeof(SOCKET_OBJ));
    	if(pSocket != NULL)
    	{
    		pSocket->s = s;
    		pSocket->event = ::WSACreateEvent();
    	}
    	return pSocket;
    }
    // 释放一个套节字对象
    void FreeSocketObj(PSOCKET_OBJ pSocket)
    {
    	::CloseHandle(pSocket->event);
    	if(pSocket->s != INVALID_SOCKET)
    	{
    		::closesocket(pSocket->s);
    	}
    	::GlobalFree(pSocket);
    }
    
    // 申请一个线程对象,初始化它的成员,并将它添加到线程对象列表中
    PTHREAD_OBJ GetThreadObj()
    {
    	PTHREAD_OBJ pThread = (PTHREAD_OBJ)::GlobalAlloc(GPTR, sizeof(THREAD_OBJ));
    	if(pThread != NULL)
    	{	
    		::InitializeCriticalSection(&pThread->cs);
    		// 创建一个事件对象,用于指示该线程的句柄数组需要重组
    		pThread->events[0] = ::WSACreateEvent();
    
    		// 将新申请的线程对象添加到列表中
    		::EnterCriticalSection(&g_cs);
    		pThread->pNext = g_pThreadList;
    		g_pThreadList = pThread;
    		::LeaveCriticalSection(&g_cs);
    	}
    	return pThread;
    }
    
    // 释放一个线程对象,并将它从线程对象列表中移除
    void FreeThreadObj(PTHREAD_OBJ pThread)
    {
    	// 在线程对象列表中查找pThread所指的对象,如果找到就从中移除
    	::EnterCriticalSection(&g_cs);
    	PTHREAD_OBJ p = g_pThreadList;
    	if(p == pThread)		// 是第一个?
    	{
    		g_pThreadList = p->pNext;
    	}
    	else
    	{
    		while(p != NULL && p->pNext != pThread)
    		{
    			p = p->pNext;
    		}
    		if(p != NULL)
    		{
    			// 此时,p是pThread的前一个,即“p->pNext == pThread”
    			p->pNext = pThread->pNext;
    		}
    	}
    	::LeaveCriticalSection(&g_cs);
    
    	// 释放资源
    	::CloseHandle(pThread->events[0]);
    	::DeleteCriticalSection(&pThread->cs);
    	::GlobalFree(pThread);
    }
    // 重新建立线程对象的events数组
    void RebuildArray(PTHREAD_OBJ pThread)	
    {
    	::EnterCriticalSection(&pThread->cs);
    	PSOCKET_OBJ pSocket = pThread->pSockHeader;
    	int n = 1;	// 从第1个开始写,第0个用于指示需要重建了
    	while(pSocket != NULL)
    	{
    		pThread->events[n++] = pSocket->event;
    		pSocket = pSocket->pNext;
    	}
    	::LeaveCriticalSection(&pThread->cs);
    }
    /////////////////////////////////////////////////////////////////////
    // 向一个线程的套节字列表中插入一个套节字
    BOOL InsertSocketObj(PTHREAD_OBJ pThread, PSOCKET_OBJ pSocket)
    {
    	BOOL bRet = FALSE;
    	::EnterCriticalSection(&pThread->cs);
    	if(pThread->nSocketCount < WSA_MAXIMUM_WAIT_EVENTS - 1)
    	{
    		if(pThread->pSockHeader == NULL)
    		{
    			pThread->pSockHeader = pThread->pSockTail = pSocket;
    		}
    		else
    		{
    			pThread->pSockTail->pNext = pSocket;
    			pThread->pSockTail = pSocket;
    		}
    		pThread->nSocketCount ++;
    		bRet = TRUE;
    	}
    	::LeaveCriticalSection(&pThread->cs);
    
    	// 插入成功,说明成功处理了客户的连接请求
    	if(bRet)
    	{
    		::InterlockedIncrement(&g_nTatolConnections);//总共连接数量
    		::InterlockedIncrement(&g_nCurrentConnections);
    	}	
    	return bRet;
    }
    // 将一个套节字对象安排给空闲的线程处理
    void AssignToFreeThread(PSOCKET_OBJ pSocket)
    {	
    	pSocket->pNext = NULL;
    
    	::EnterCriticalSection(&g_cs);
    	PTHREAD_OBJ pThread = g_pThreadList;
    	// 试图插入到现存线程
    	while(pThread != NULL)
    	{
    		if(InsertSocketObj(pThread, pSocket))
    			break;
    		pThread = pThread->pNext;
    	}
    
    	// 没有空闲线程,为这个套节字创建新的线程
    	if(pThread == NULL)
    	{
    		pThread = GetThreadObj();
    		// 申请一个线程对象,初始化它的成员,并将它添加到线程对象列表中
    		InsertSocketObj(pThread, pSocket);	
    		::CreateThread(NULL, 0, ServerThread, pThread, 0, NULL);
    	}
    	::LeaveCriticalSection(&g_cs);
    
    	// 指示线程重建句柄数组
    	::WSASetEvent(pThread->events[0]);
    }
    // 从给定线程的套节字对象列表中移除一个套节字对象
    void RemoveSocketObj(PTHREAD_OBJ pThread, PSOCKET_OBJ pSocket)
    {
    	::EnterCriticalSection(&pThread->cs);
    
    	// 在套节字对象列表中查找指定的套节字对象,找到后将之移除
    	PSOCKET_OBJ pTest = pThread->pSockHeader;
    	if(pTest == pSocket)
    	{
    		if(pThread->pSockHeader == pThread->pSockTail)
    			pThread->pSockTail = pThread->pSockHeader = pTest->pNext;
    		else
    			pThread->pSockHeader = pTest->pNext;
    	}
    	else
    	{
    		while(pTest != NULL && pTest->pNext != pSocket)
    			pTest = pTest->pNext;
    		if(pTest != NULL)
    		{
    			if(pThread->pSockTail == pSocket)
    				pThread->pSockTail = pTest;
    			pTest->pNext = pSocket->pNext;
    		}
    	}
    	pThread->nSocketCount --;
    
    	::LeaveCriticalSection(&pThread->cs);
    
    	// 指示线程重建句柄数组
    	::WSASetEvent(pThread->events[0]);
    
    	// 说明一个连接中断
    	::InterlockedDecrement(&g_nCurrentConnections);
    }
    BOOL HandleIO(PTHREAD_OBJ pThread, PSOCKET_OBJ pSocket)
    {
    	// 获取具体发生的网络事件
    	WSANETWORKEVENTS event;
    	::WSAEnumNetworkEvents(pSocket->s, pSocket->event, &event);
    	do
    	{
    		if(event.lNetworkEvents & FD_READ)			// 套节字可读
    		{
    			if(event.iErrorCode[FD_READ_BIT] == 0)
    			{
    				char szText[256];
    				int nRecv = ::recv(pSocket->s, szText, strlen(szText), 0);
    				if(nRecv > 0)				
    				{
    					szText[nRecv] = '';
    					printf("接收到数据:%s 
    ", szText);
    				}
    			}
    			else
    				break;
    		}
    		else if(event.lNetworkEvents & FD_CLOSE)	// 套节字关闭
    		{
    			break;
    		}
    		else if(event.lNetworkEvents & FD_WRITE)	// 套节字可写
    		{
    			if(event.iErrorCode[FD_WRITE_BIT] == 0)
    			{	
    			}
    			else
    				break;
    		}
    		return TRUE;
    	}
    	while(FALSE);
    
    	// 套节字关闭,或者有错误发生,程序都会转到这里来执行
    	RemoveSocketObj(pThread, pSocket);
    	FreeSocketObj(pSocket);
    	return FALSE;
    }
    PSOCKET_OBJ FindSocketObj(PTHREAD_OBJ pThread, int nIndex) // nIndex从1开始
    {
    	// 在套节字列表中查找
    	PSOCKET_OBJ pSocket = pThread->pSockHeader;
    	while(--nIndex)
    	{
    		if(pSocket == NULL)
    			return NULL;
    		pSocket = pSocket->pNext;
    	}
    	return pSocket;
    }
    DWORD WINAPI ServerThread(LPVOID lpParam)
    {
    	// 取得本线程对象的指针
    	PTHREAD_OBJ pThread = (PTHREAD_OBJ)lpParam;
    	while(TRUE)
    	{
    		//	等待网络事件
    		int nIndex = ::WSAWaitForMultipleEvents(
    pThread->nSocketCount + 1, pThread->events, FALSE, WSA_INFINITE, FALSE);
    		nIndex = nIndex - WSA_WAIT_EVENT_0;
    		// 查看受信的事件对象
    		for(int i=nIndex; i<pThread->nSocketCount + 1; i++)
    		{
    			nIndex = ::WSAWaitForMultipleEvents(1, &pThread->events[i], TRUE, 1000, FALSE);
    			if(nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
    			{
    				continue;
    			}
    			else
    			{
    				if(i == 0)				// events[0]受信,重建数组
    				{
    					RebuildArray(pThread);
    					// 如果没有客户I/O要处理了,则本线程退出
    					if(pThread->nSocketCount == 0)
    					{
    						FreeThreadObj(pThread);
    						return 0;
    					}
    					::WSAResetEvent(pThread->events[0]);
    				}
    				else					// 处理网络事件
    				{
    					// 查找对应的套节字对象指针,调用HandleIO处理网络事件
    					PSOCKET_OBJ pSocket = (PSOCKET_OBJ)FindSocketObj(pThread, i);
    					if(pSocket != NULL)
    					{
    						if(!HandleIO(pThread, pSocket))
    							RebuildArray(pThread);
    					}
    					else
    						printf(" Unable to find socket object 
     ");
    				}
    			}
    		}
    	}
    	return 0;
    }


    CPP:

    #include "../common/initsock.h"
    
    #include <stdio.h>
    #include <windows.h>
    
    #include "EventSelectServer.h"
    
    // 初始化Winsock库
    CInitSock theSock;
    
    int main()
    {
    	USHORT nPort = 4567;	// 此服务器监听的端口号
    
    	// 创建监听套节字
    	SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	
    	sockaddr_in sin;
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(nPort);
    	sin.sin_addr.S_un.S_addr = INADDR_ANY;
    	if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
    	{
    		printf(" Failed bind() 
    ");
    		return -1;
    	}
    	::listen(sListen, 200);
    
    	// 创建事件对象,并关联到监听的套节字
    	WSAEVENT event = ::WSACreateEvent();
    	::WSAEventSelect(sListen, event, FD_ACCEPT|FD_CLOSE);
    
    	::InitializeCriticalSection(&g_cs);
    
    	// 处理客户连接请求,打印状态信息
    	while(TRUE)
    	{
    		int nRet = ::WaitForSingleObject(event, 5*1000);
    		if(nRet == WAIT_FAILED)
    		{
    			printf(" Failed WaitForSingleObject() 
    ");
    			break;
    		}
    		else if(nRet == WSA_WAIT_TIMEOUT)	// 定时显式状态信息
    		{
    			printf(" 
    ");
    			printf("   TatolConnections: %d 
    ", g_nTatolConnections);
    			printf(" CurrentConnections: %d 
    ", g_nCurrentConnections);
    			continue;
    		}
    		else								// 有新的连接未决
    		{
    			::ResetEvent(event);//设置为无信号状态
    			// 循环处理所有未决的连接请求
    			while(TRUE)
    			{
    				sockaddr_in si;
    				int nLen = sizeof(si);
    				SOCKET sNew = ::accept(sListen, (sockaddr*)&si, &nLen);
    				if(sNew == SOCKET_ERROR)
    					break;
    				PSOCKET_OBJ pSocket = GetSocketObj(sNew);// 申请一个套节字对象,初始化它的成员
    				pSocket->addrRemote = si;
    				::WSAEventSelect(pSocket->s, pSocket->event, FD_READ|FD_CLOSE|FD_WRITE); 
    		//将指定的一组网络事件与它关联在一起
    				AssignToFreeThread(pSocket);// 将一个套节字对象安排给空闲的线程处理
    			}
    		}
    	}
    	::DeleteCriticalSection(&g_cs);
    	return 0;
    }


    重叠 I/O 模型 模型

    提供更好的系统性能,思想是允许应用程序使用重叠数据结构一次投递一个或者多个异步 I/O 请求(即所谓的重叠I/O)

    提交I/O请求完成后,与之关联的重叠数据结构中的事件对象受信,应用程序便可以使用 WSAGetOverlappedResuly函数获取重叠操作结果。


    为了使用重叠 I/O 模型,必须调用特定的重叠I/O函数创建套接字,在套接字上传输数据。

    SOCKET WSASocket(
      __in  int af,
      __in  int type,
      __in  int protocol,                     //前3个参数与socket一样
      __in  LPWSAPROTOCOL_INFO lpProtocolInfo,//指定下层服务提供者,可以是NULL
      __in  GROUP g,                          //保留
      __in  DWORD dwFlags                     //指定套接字属性,要使用重叠I/O模型,必须指定 WSA_FLAG_OVERLAPPED
    );
    int WSASend(
      __in   SOCKET s,
      __in   LPWSABUF lpBuffers,
      __in   DWORD dwBufferCount,          //上面WSABUF的大小
      __out  LPDWORD lpNumberOfBytesSent,  //I/O操作立即完成的话,此参数取得实际传输数据的字节数
      __in   DWORD dwFlags,                //标志
      __in   LPWSAOVERLAPPED lpOverlapped, //指向 XXX结构,I/O完成后,此事件对象受信
      __in   LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //指向一个完成例程,I/O完成后,WINsock边去调用它,可以设置为NULL
    );
    BOOL AcceptEx(                      //接受一个新的连接,返回本地和远程地址,取得客户程序发送的第一块数据
      __in   SOCKET sListenSocket,      //监听套接字
      __in   SOCKET sAcceptSocket,      //指定一个未被使用的套接字,在这个套接字上接受新的连接
      __in   PVOID lpOutputBuffer,      //指定一个缓冲区,用来取得在新连接上接收到的第一块数据,服务器的本地地址+客户端地址
      __in   DWORD dwReceiveDataLength, //上面 buffer所指的大小
      __in   DWORD dwLocalAddressLength,//为本地地址预留的长度,必须比最大地址长度多16
      __in   DWORD dwRemoteAddressLength,//为远程地址预留的长度,必须必最大地址长度多16
      __out  LPDWORD lpdwBytesReceived,  //取得接搜数据的长度 
      __in   LPOVERLAPPED lpOverlapped   //指定用来处理请求的 XXX结构,不能为NULL
    );
    函数将几个套接字函数的功能集合在了一起,如果它投递的请求成功完成,那么:

    1>接受了新的连接

    2>新连接的本地地址+远程地址都会返回

    3>接受到了远程主机发来的第一块数据

    AccptEx 和大家熟悉的 accept 不同就是 它需要连个套接字,一个指定在那个套接字上监听, 另一个指定在哪个套接字上接受连接,也就是说他不会像accept函数一样为新连接创建套接字

    它是Microsofft 扩展函数,从Mswsock.lib 库中导出的,为了能够直接调用它,而不用链接到Mswsock.lib库,需要使用WSAIoctl函数(ioctlsocket函数的扩展)将AcceptEx函数加载到内存。

    int WSAIoctl(
      __in   SOCKET s,
      __in   DWORD dwIoControlCode,
      __in   LPVOID lpvInBuffer,
      __in   DWORD cbInBuffer,
      __out  LPVOID lpvOutBuffer,
      __in   DWORD cbOutBuffer,
      __out  LPDWORD lpcbBytesReturned,
      __in   LPWSAOVERLAPPED lpOverlapped,
      __in   LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
    );

    	// 加载扩展函数AcceptEx
    	GUID GuidAcceptEx = WSAID_ACCEPTEX;
    	DWORD dwBytes;
    	WSAIoctl(pListen->s, 
    		SIO_GET_EXTENSION_FUNCTION_POINTER, 
    		&GuidAcceptEx, 
    		sizeof(GuidAcceptEx),
    		&pListen->lpfnAcceptEx, 
    		sizeof(pListen->lpfnAcceptEx), 
    		&dwBytes, 
    		NULL, 
    		NULL);

    为了使用重叠I/O  每个 I/O都要接收一个 WSAOVERLAPPED 结构类型的参数

    当重叠I/O请求最终完成以后,与之关联的事件对象受信,等待 函数返回,可以使用   XX 函数取得重叠操作的结构

    BOOL WSAAPI WSAGetOverlappedResult(
      __in   SOCKET s,
      __in   LPWSAOVERLAPPED lpOverlapped,  //重叠启动时指定的WSAOVERLAPPED的结构
      __out  LPDWORD lpcbTransfer,          //取得实际传输字节的数量
      __in   BOOL fWait,                    //释放等待未决的重叠操作
      __out  LPDWORD lpdwFlags              //用于取得完成状态
    );

    调用函数成功时的返回值是TRUE,这说明 重叠操作成功完成了,lpcbTransfer 参数将返回I/O操作实际传输字节的数量,传输的参数无误,返回值是FALSE,则说明套接字s 上有错误发生

    转载代码:

    #include "../common/initsock.h"
    
    #include <Mswsock.h>
    #include <stdio.h>
    #include <windows.h>
    
    CInitSock theSock;
    
    #define BUFFER_SIZE 1024
    
    typedef struct _SOCKET_OBJ
    {
    	SOCKET s;						// 套节字句柄
    	int nOutstandingOps;			// 记录此套节字上的重叠I/O数量
    	
    	LPFN_ACCEPTEX lpfnAcceptEx;		// 扩展函数AcceptEx的指针(仅对监听套节字而言)
    } SOCKET_OBJ, *PSOCKET_OBJ;
    
    typedef struct _BUFFER_OBJ
    {	
    	OVERLAPPED ol;			// 重叠结构
    	char *buff;				// send/recv/AcceptEx所使用的缓冲区
    	int nLen;				// buff的长度
    	PSOCKET_OBJ pSocket;	// 此I/O所属的套节字对象
    
    	int nOperation;			// 提交的操作类型
    #define OP_ACCEPT	1
    #define OP_READ		2
    #define OP_WRITE	3
    
    	SOCKET sAccept;			// 用来保存AcceptEx接受的客户套节字(仅对监听套节字而言)
    	_BUFFER_OBJ *pNext;
    } BUFFER_OBJ, *PBUFFER_OBJ;
    
    HANDLE g_events[WSA_MAXIMUM_WAIT_EVENTS];	// I/O事件句柄数组
    int g_nBufferCount;							// 上数组中有效句柄数量
    PBUFFER_OBJ g_pBufferHead, g_pBufferTail;	// 记录缓冲区对象组成的表的地址
    
    // 申请套节字对象和释放套节字对象的函数
    PSOCKET_OBJ GetSocketObj(SOCKET s)
    {
    	PSOCKET_OBJ pSocket = (PSOCKET_OBJ)::GlobalAlloc(GPTR, sizeof(SOCKET_OBJ));
    	if(pSocket != NULL)
    	{
    		pSocket->s = s;
    	}
    	return pSocket;
    }
    void FreeSocketObj(PSOCKET_OBJ pSocket)
    {
    	if(pSocket->s != INVALID_SOCKET)
    		::closesocket(pSocket->s);
    	::GlobalFree(pSocket);
    }
    
    PBUFFER_OBJ GetBufferObj(PSOCKET_OBJ pSocket, ULONG nLen)
    {
    	if(g_nBufferCount > WSA_MAXIMUM_WAIT_EVENTS - 1)
    		return NULL;
    
    	PBUFFER_OBJ pBuffer = (PBUFFER_OBJ)::GlobalAlloc(GPTR, sizeof(BUFFER_OBJ));
    	if(pBuffer != NULL)
    	{
    		pBuffer->buff = (char*)::GlobalAlloc(GPTR, nLen);
    		pBuffer->ol.hEvent = ::WSACreateEvent();
    		pBuffer->pSocket = pSocket;
    		pBuffer->sAccept = INVALID_SOCKET;
    
    		// 将新的BUFFER_OBJ添加到列表中
    		if(g_pBufferHead == NULL)
    		{
    			g_pBufferHead = g_pBufferTail = pBuffer;
    		}
    		else
    		{
    			g_pBufferTail->pNext = pBuffer;
    			g_pBufferTail = pBuffer;
    		}
    		g_events[++ g_nBufferCount] = pBuffer->ol.hEvent;
    	}
    	return pBuffer;
    }
    
    void FreeBufferObj(PBUFFER_OBJ pBuffer)
    {
    	// 从列表中移除BUFFER_OBJ对象
    	PBUFFER_OBJ pTest = g_pBufferHead;
    	BOOL bFind = FALSE;
    	if(pTest == pBuffer)
    	{
    		g_pBufferHead = g_pBufferTail = NULL;
    		bFind = TRUE;
    	}
    	else
    	{
    		while(pTest != NULL && pTest->pNext != pBuffer)
    			pTest = pTest->pNext;
    		if(pTest != NULL)
    		{
    			pTest->pNext = pBuffer->pNext;
    			if(pTest->pNext == NULL)
    				g_pBufferTail = pTest;
    			bFind = TRUE;
    		}
    	}
    	// 释放它占用的内存空间
    	if(bFind)
    	{
    		g_nBufferCount --;
    		::CloseHandle(pBuffer->ol.hEvent);
    		::GlobalFree(pBuffer->buff);
    		::GlobalFree(pBuffer);	
    	}
    }
    
    PBUFFER_OBJ FindBufferObj(HANDLE hEvent)
    {
    	PBUFFER_OBJ pBuffer = g_pBufferHead;
    	while(pBuffer != NULL)
    	{
    		if(pBuffer->ol.hEvent == hEvent)
    			break;
    		pBuffer = pBuffer->pNext;
    	}
    	return pBuffer;
    }
    
    void RebuildArray()
    {
    	PBUFFER_OBJ pBuffer = g_pBufferHead;
    	int i =  1;
    	while(pBuffer != NULL)
    	{
    		g_events[i++] = pBuffer->ol.hEvent;
    		pBuffer = pBuffer->pNext;
    	}
    }
    
    BOOL PostAccept(PBUFFER_OBJ pBuffer)
    {
    	PSOCKET_OBJ pSocket = pBuffer->pSocket;
    	if(pSocket->lpfnAcceptEx != NULL)
    	{
    		// 设置I/O类型,增加套节字上的重叠I/O计数
    		pBuffer->nOperation = OP_ACCEPT;
    		pSocket->nOutstandingOps ++;
    
    		// 投递此重叠I/O  
    		DWORD dwBytes;
    		pBuffer->sAccept = 
    			::WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    		BOOL b = pSocket->lpfnAcceptEx(pSocket->s, 
    			pBuffer->sAccept,
    			pBuffer->buff, 
    			BUFFER_SIZE - ((sizeof(sockaddr_in) + 16) * 2),
    			sizeof(sockaddr_in) + 16, 
    			sizeof(sockaddr_in) + 16, 
    			&dwBytes, 
    			&pBuffer->ol);
    		if(!b)
    		{
    			if(::WSAGetLastError() != WSA_IO_PENDING)
    				return FALSE;
    		}
    		return TRUE;
    	}
    	return FALSE;
    };
    
    BOOL PostRecv(PBUFFER_OBJ pBuffer)
    {	
    	// 设置I/O类型,增加套节字上的重叠I/O计数
    	pBuffer->nOperation = OP_READ;
    	pBuffer->pSocket->nOutstandingOps ++;
    
    	// 投递此重叠I/O
    	DWORD dwBytes;
    	DWORD dwFlags = 0;
    	WSABUF buf;
    	buf.buf = pBuffer->buff;
    	buf.len = pBuffer->nLen;
    	if(::WSARecv(pBuffer->pSocket->s, &buf, 1, &dwBytes, &dwFlags, &pBuffer->ol, NULL) != NO_ERROR)
    	{
    		printf("%s",buf.buf);
    		if(::WSAGetLastError() != WSA_IO_PENDING)
    			return FALSE;
    	}
    	printf("%s",buf);
    	return TRUE;
    }
    
    BOOL PostSend(PBUFFER_OBJ pBuffer)
    {
    	// 设置I/O类型,增加套节字上的重叠I/O计数
    	pBuffer->nOperation = OP_WRITE;
    	pBuffer->pSocket->nOutstandingOps ++;
    
    	// 投递此重叠I/O
    	DWORD dwBytes;
    	DWORD dwFlags = 0;
    	WSABUF buf;
    	buf.buf = pBuffer->buff;
    	buf.len = pBuffer->nLen;
    	if(::WSASend(pBuffer->pSocket->s, 
    			&buf, 1, &dwBytes, dwFlags, &pBuffer->ol, NULL) != NO_ERROR)
    	{
    		if(::WSAGetLastError() != WSA_IO_PENDING)
    			return FALSE;
    	}
    	return TRUE;
    }
    
    BOOL HandleIO(PBUFFER_OBJ pBuffer)
    {
    	PSOCKET_OBJ pSocket = pBuffer->pSocket; // 从BUFFER_OBJ对象中提取SOCKET_OBJ对象指针,为的是方便引用
    	pSocket->nOutstandingOps --;
    
    	// 获取重叠操作结果
    	DWORD dwTrans;
    	DWORD dwFlags;
    	BOOL bRet = ::WSAGetOverlappedResult(pSocket->s, &pBuffer->ol, &dwTrans, FALSE, &dwFlags);
    	if(!bRet)
    	{
    		// 在此套节字上有错误发生,因此,关闭套节字,移除此缓冲区对象。
    		// 如果没有其它抛出的I/O请求了,释放此缓冲区对象,否则,等待此套节字上的其它I/O也完成
    		if(pSocket->s != INVALID_SOCKET)
    		{
    			::closesocket(pSocket->s);
    			pSocket->s = INVALID_SOCKET;
    		}
    
    		if(pSocket->nOutstandingOps == 0)
    			FreeSocketObj(pSocket);	
    		
    		FreeBufferObj(pBuffer);
    		return FALSE;
    	}
    
    	// 没有错误发生,处理已完成的I/O
    	switch(pBuffer->nOperation)
    	{
    	case OP_ACCEPT:	// 接收到一个新的连接,并接收到了对方发来的第一个封包
    		{
    			// 为新客户创建一个SOCKET_OBJ对象
    			PSOCKET_OBJ pClient = GetSocketObj(pBuffer->sAccept);
    
    			// 为发送数据创建一个BUFFER_OBJ对象,这个对象会在套节字出错或者关闭时释放
    			PBUFFER_OBJ pSend = GetBufferObj(pClient, BUFFER_SIZE);	
    			if(pSend == NULL)
    			{
    				printf(" Too much connections! 
    ");
    				FreeSocketObj(pClient);
    				return FALSE;
    			}
    			RebuildArray();
    			
    			// 将数据复制到发送缓冲区
    			pSend->nLen = dwTrans;
    			memcpy(pSend->buff, pBuffer->buff, dwTrans);
    
    			// 投递此发送I/O(将数据回显给客户)
    			if(!PostSend(pSend))
    			{
    				// 万一出错的话,释放上面刚申请的两个对象
    				FreeSocketObj(pSocket);	
    				FreeBufferObj(pSend);
    				return FALSE;
    			}
    			// 继续投递接受I/O
    			PostAccept(pBuffer);
    		}
    		break;
    	case OP_READ:	// 接收数据完成
    		{
    			if(dwTrans > 0)
    			{
    				// 创建一个缓冲区,以发送数据。这里就使用原来的缓冲区
    				PBUFFER_OBJ pSend = pBuffer;
    				pSend->nLen = dwTrans;
    				
    				// 投递发送I/O(将数据回显给客户)
    				PostSend(pSend);
    			}
    			else	// 套节字关闭
    			{
    	
    				// 必须先关闭套节字,以便在此套节字上投递的其它I/O也返回
    				if(pSocket->s != INVALID_SOCKET)
    				{
    					::closesocket(pSocket->s);
    					pSocket->s = INVALID_SOCKET;
    				}
    
    				if(pSocket->nOutstandingOps == 0)
    					FreeSocketObj(pSocket);		
    				
    				FreeBufferObj(pBuffer);
    				return FALSE;
    			}
    		}
    		break;
    	case OP_WRITE:		// 发送数据完成
    		{
    			if(dwTrans > 0)
    			{
    				// 继续使用这个缓冲区投递接收数据的请求
    				pBuffer->nLen = BUFFER_SIZE;
    				PostRecv(pBuffer);
    			}
    			else	// 套节字关闭
    			{
    				// 同样,要先关闭套节字
    				if(pSocket->s != INVALID_SOCKET)
    				{
    					::closesocket(pSocket->s);
    					pSocket->s = INVALID_SOCKET;
    				}
    
    				if(pSocket->nOutstandingOps == 0)
    					FreeSocketObj(pSocket);	
    
    				FreeBufferObj(pBuffer);
    				return FALSE;
    			}
    		}
    		break;
    	}
    	return TRUE;
    }
    
    
    void main()
    {
    	// 创建监听套节字,绑定到本地端口,进入监听模式
    	int nPort = 4567;
    	SOCKET sListen = 
    		::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    	SOCKADDR_IN si;
    	si.sin_family = AF_INET;
    	si.sin_port = ::ntohs(nPort);
    	si.sin_addr.S_un.S_addr = INADDR_ANY;
    	::bind(sListen, (sockaddr*)&si, sizeof(si));
    	::listen(sListen, 200);
    
    	// 为监听套节字创建一个SOCKET_OBJ对象
    	PSOCKET_OBJ pListen = GetSocketObj(sListen);
    
    	// 加载扩展函数AcceptEx
    	GUID GuidAcceptEx = WSAID_ACCEPTEX;
    	DWORD dwBytes;
    	WSAIoctl(pListen->s, 
    		SIO_GET_EXTENSION_FUNCTION_POINTER, 
    		&GuidAcceptEx, 
    		sizeof(GuidAcceptEx),
    		&pListen->lpfnAcceptEx, 
    		sizeof(pListen->lpfnAcceptEx), 
    		&dwBytes, 
    		NULL, 
    		NULL);
    
    	// 创建用来重新建立g_events数组的事件对象
    	g_events[0] = ::WSACreateEvent();
    
    	// 在此可以投递多个接受I/O请求
    	for(int i=0; i<5; i++)
    	{
    		PostAccept(GetBufferObj(pListen, BUFFER_SIZE));
    	}
    	::WSASetEvent(g_events[0]);
    	
    	while(TRUE)
    	{
    		int nIndex = 
    			::WSAWaitForMultipleEvents(g_nBufferCount + 1, g_events, FALSE, WSA_INFINITE, FALSE);
    		if(nIndex == WSA_WAIT_FAILED)
    		{
    			printf("WSAWaitForMultipleEvents() failed 
    ");
    			break;
    		}
    		nIndex = nIndex - WSA_WAIT_EVENT_0;
    		for(int i=0; i<=nIndex; i++)
    		{
    			int nRet = ::WSAWaitForMultipleEvents(1, &g_events[i], TRUE, 0, FALSE);
    			if(nRet == WSA_WAIT_TIMEOUT)
    				continue;
    			else
    			{
    				::WSAResetEvent(g_events[i]);
    				// 重新建立g_events数组
    				if(i == 0)
    				{
    					RebuildArray();
    					continue;
    				}
    
    				// 处理这个I/O
    				PBUFFER_OBJ pBuffer = FindBufferObj(g_events[i]);
    				if(pBuffer != NULL)
    				{
    					if(!HandleIO(pBuffer))
    						RebuildArray();
    				}
    			}
    		}
    	}
    }


    完成端口 I/O模型。

    当应用程序必须一次管理多个套接字时,完成端口模型提供了更好的系统性能,更好的伸缩性,适合用来处理上百,上千个套接字

    IO完成端口是应用程序使用线程池处理异步IO请求的一种机制,处理多个并发异步IO请求时,使用IO完成端口比在IO请求时创建线程更快更有效

    完成端口实际上是一个WINDOWS IO结构,可以接受多种对象的句柄,如文件对象,套接字对象等。

    HANDLE WINAPI CreateIoCompletionPort(       //创建一个完成端口对象,将一个或者多个文件句柄(套接字句柄)关联到IO完成端口对象
      __in      HANDLE FileHandle,
      __in_opt  HANDLE ExistingCompletionPort,
      __in      ULONG_PTR CompletionKey,
      __in      DWORD NumberOfConcurrentThreads //允许在完成端口上同时执行的线程的数量 为0表示线程数量=处理器数量
    );
    	// 创建完成端口对象,创建工作线程处理完成端口对象中事件
    	HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);

    完成IO 到现在为止在性能和可伸缩性方面表示最好的IO模型,关联到完成端口对象的套接字的数量没有限制,仅需要少量的线程来处理完成IO。

    转载代码:

    #include "../common/initsock.h"
    #include <stdio.h>
    #include <windows.h>
    
    // 初始化Winsock库
    CInitSock theSock;
    
    #define BUFFER_SIZE 1024
    
    typedef struct _PER_HANDLE_DATA		// per-handle数据
    {
    	SOCKET s;			// 对应的套节字句柄
    	sockaddr_in addr;	// 客户方地址
    } PER_HANDLE_DATA, *PPER_HANDLE_DATA;
    
    
    typedef struct _PER_IO_DATA			// per-I/O数据
    {
    	OVERLAPPED ol;			// 重叠结构
    	char buf[BUFFER_SIZE];	// 数据缓冲区
    	int nOperationType;		// 操作类型
    #define OP_READ   1
    #define OP_WRITE  2
    #define OP_ACCEPT 3
    } PER_IO_DATA, *PPER_IO_DATA;
    
    
    DWORD WINAPI ServerThread(LPVOID lpParam)
    {
    	// 得到完成端口对象句柄
    	HANDLE hCompletion = (HANDLE)lpParam;
    
    	DWORD dwTrans;
    	PPER_HANDLE_DATA pPerHandle;
    	PPER_IO_DATA pPerIO;
    	while(TRUE)
    	{
    		// 在关联到此完成端口的所有套节字上等待I/O完成,io系统会向完成端口对象发送一个完成通知封包,先进先出的方式为这些封包排队
    		BOOL bOK = ::GetQueuedCompletionStatus(hCompletion, 
    			&dwTrans, 
    			(LPDWORD)&pPerHandle, 
    			(LPOVERLAPPED*)&pPerIO, 
    			WSA_INFINITE);
    		if(!bOK)						// 在此套节字上有错误发生
    		{
    			::closesocket(pPerHandle->s);
    			::GlobalFree(pPerHandle);
    			::GlobalFree(pPerIO);
    			continue;
    		}
    		
    		if(dwTrans == 0 &&				// 套节字被对方关闭
    			(pPerIO->nOperationType == OP_READ || pPerIO->nOperationType == OP_WRITE))	
    			
    		{
    			::closesocket(pPerHandle->s);
    			::GlobalFree(pPerHandle);
    			::GlobalFree(pPerIO);
    			continue;
    		}
    
    		switch(pPerIO->nOperationType)	// 通过per-I/O数据中的nOperationType域查看什么I/O请求完成了
    		{
    		case OP_READ:	// 完成一个接收请求
    			{
    				pPerIO->buf[dwTrans] = '';
    				printf(pPerIO -> buf);
    				
    				// 继续投递接收I/O请求
    				WSABUF buf;
    				buf.buf = pPerIO->buf ;
    				buf.len = BUFFER_SIZE;
    				pPerIO->nOperationType = OP_READ;
    
    				DWORD nFlags = 0;
    				::WSARecv(pPerHandle->s, &buf, 1, &dwTrans, &nFlags, &pPerIO->ol, NULL);
    			}
    			break;
    		case OP_WRITE: // 本例中没有投递这些类型的I/O请求
    		case OP_ACCEPT:
    			break;
    		}
    	}
    	return 0;
    }
    
    
    void main()
    {
    	int nPort = 4567;
    	// 创建完成端口对象,创建工作线程处理完成端口对象中事件
    	HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
    	::CreateThread(NULL, 0, ServerThread, (LPVOID)hCompletion, 0, 0);
    
    	// 创建监听套节字,绑定到本地地址,开始监听
    	SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, 0);
    	SOCKADDR_IN si;
    	si.sin_family = AF_INET;
    	si.sin_port = ::ntohs(nPort);
    	si.sin_addr.S_un.S_addr = INADDR_ANY;
    	::bind(sListen, (sockaddr*)&si, sizeof(si));
    	::listen(sListen, 5);
    
    	// 循环处理到来的连接
    	while(TRUE)
    	{
    		// 等待接受未决的连接请求
    		SOCKADDR_IN saRemote;
    		int nRemoteLen = sizeof(saRemote);
    		SOCKET sNew = ::accept(sListen, (sockaddr*)&saRemote, &nRemoteLen);
    
    		// 接受到新连接之后,为它创建一个per-handle数据,并将它们关联到完成端口对象。
    		PPER_HANDLE_DATA pPerHandle = 
    							(PPER_HANDLE_DATA)::GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
    		pPerHandle->s = sNew;
    		memcpy(&pPerHandle->addr, &saRemote, nRemoteLen);
    		::CreateIoCompletionPort((HANDLE)pPerHandle->s, hCompletion, (DWORD)pPerHandle, 0);
    	
    		// 投递一个接收请求
    		PPER_IO_DATA pPerIO = (PPER_IO_DATA)::GlobalAlloc(GPTR, sizeof(PER_IO_DATA));
    		pPerIO->nOperationType = OP_READ;
    		WSABUF buf;
    		buf.buf = pPerIO->buf;
    		buf.len = BUFFER_SIZE;	
    		DWORD dwRecv;
    		DWORD dwFlags = 0;
    		::WSARecv(pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, &pPerIO->ol, NULL);
    	}
    }

    扩展函数学习:

    WINDOWS Socket2规范定义了一种扩展机制,允许WINDOWS 套接字服务提供者向应用程序设计者导出先进的数据传输功能,MS通过使用这个扩展机制提供了一些扩展函数。

    有些扩展函数式从WINSOCK 1.1 开始就出现了,从MSWSOCK.DLL导出,然而不建议直接连接到这个DLL,这会将程序绑定在MICROSOFT WONSOCK提供者上。应该用WSAIoctl 函数动态记载它们


    void GetAcceptExSockaddrs(           //将本地和远程地址传递到sockaddr结构
      __in   PVOID lpOutputBuffer,       //传递给AcceptEx函数接受客户第一块数据的缓冲区
      __in   DWORD dwReceiveDataLength,  //上面缓冲区的大小,和AcceptEx函数一致
      __in   DWORD dwLocalAddressLength, //本地地址预留空间大小,和AcceptEx函数一致
      __in   DWORD dwRemoteAddressLength,//远程地址预留空间大小,和AcceptEx函数一致
      __out  LPSOCKADDR *LocalSockaddr,  //返回连接的本地地址
      __out  LPINT LocalSockaddrLength,  //返回本地地址的长度
      __out  LPSOCKADDR *RemoteSockaddr, //返回远程地址
      __out  LPINT RemoteSockaddrLength  //返回远程地址的长度
    );

    	// 加载扩展函数GetAcceptExSockaddrs
    	GUID GuidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
    	::WSAIoctl(m_sListen,
    		SIO_GET_EXTENSION_FUNCTION_POINTER,
    		&GuidGetAcceptExSockaddrs,
    		sizeof(GuidGetAcceptExSockaddrs),
    		&m_lpfnGetAcceptExSockaddrs,
    		sizeof(m_lpfnGetAcceptExSockaddrs),
    		&dwBytes,
    		NULL,
    		NULL
    		);

    
    

    用法:

    					// 取得客户地址
    					int nLocalLen, nRmoteLen;
    					LPSOCKADDR pLocalAddr, pRemoteAddr;
    					m_lpfnGetAcceptExSockaddrs(
    						pBuffer->buff,
    						pBuffer->nLen - ((sizeof(sockaddr_in) + 16) * 2),
    						sizeof(sockaddr_in) + 16,
    						sizeof(sockaddr_in) + 16,
    						(SOCKADDR **)&pLocalAddr,
    						&nLocalLen,
    						(SOCKADDR **)&pRemoteAddr,
    						&nRmoteLen);
    					memcpy(&pClient->addrLocal, pLocalAddr, nLocalLen);
    					memcpy(&pClient->addrRemote, pRemoteAddr, nRmoteLen);


    BOOL TransmitFile(                //在一个已连接的套接字句柄上传输文件数据
        SOCKET hSocket,
        HANDLE hFile,                 //打开的文件句柄 传输这个文件,0就传输 lpTransmitBuffers 
        DWORD nNumberOfBytesToWrite,  //0就传输整个文件
        DWORD nNumberOfBytesPerSend,
        LPOVERLAPPED lpOverlapped,
        LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,
        DWORD dwFlags
    );






























  • 相关阅读:
    nginx 转发配置
    Rancher中httpd证书的管理和使用
    JDK-docker
    软路由
    rancher相关
    rancher部署
    电商 好文 知识积累
    SpringBlade 接口文档 请求token接口报错
    SpringBlade 接口文档 无法访问
    电商 好文
  • 原文地址:https://www.cnblogs.com/zcc1414/p/3982337.html
Copyright © 2020-2023  润新知