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() \n");
		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)\n", ::inet_ntoa(addrRemote.sin_addr));
						}
						else
						{
							printf(" Too much connections! \n");
							continue;
						}
					}
					else
					{
						char szText[256];
						int nRecv = ::recv(fdSocket.fd_array[i], szText, strlen(szText), 0);
						if(nRecv > 0)						// (2)可讀
						{
							szText[nRecv] = '\0';
							printf("接收到數據:%s \n", szText);
						}
						else								// (3)連接關閉、重啓或者中斷
						{
							::closesocket(fdSocket.fd_array[i]);
							FD_CLR(fdSocket.fd_array[i], &fdSocket);
						}
					}
				}
			}
		}
		else
		{
			printf(" Failed select() \n");
			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() \n");
		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() \n");
		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! \n");
							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] = '\0';
							printf("接收到數據:%s \n", 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] = '\0';
					printf("接收到數據:%s \n", 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 \n ");
				}
			}
		}
	}
	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() \n");
		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() \n");
			break;
		}
		else if(nRet == WSA_WAIT_TIMEOUT)	// 定時顯式狀態信息
		{
			printf(" \n");
			printf("   TatolConnections: %d \n", g_nTatolConnections);
			printf(" CurrentConnections: %d \n", 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! \n");
				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 \n");
			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] = '\0';
				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
);






























發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章