WSAEventSelect模型的是事件句柄數組和套節字句柄數組的方式去實作事件Select模型的
接下來用的是套接字連結清單對象和線程連結清單對象組合下的事件Select模型
/////////////////////////////////////////////////////
// EventSelect.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所指的對象,如果找到就從中移除
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;
// 釋放資源
::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;
if(pThread->nSocketCount < WSA_MAXIMUM_WAIT_EVENTS - 1)
if(pThread->pSockHeader == NULL)
pThread->pSockHeader = pThread->pSockTail = pSocket;
pThread->pSockTail->pNext = pSocket;
pThread->pSockTail = pSocket;
pThread->nSocketCount ++;
bRet = TRUE;
// 插入成功,說明成功處理了客戶的連接配接請求
if(bRet)
::InterlockedIncrement(&g_nTatolConnections);
::InterlockedIncrement(&g_nCurrentConnections);
return bRet;
// 将一個套節字對象安排給空閑的線程處理
void AssignToFreeThread(PSOCKET_OBJ pSocket)
pSocket->pNext = NULL;
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);
// 訓示線程重建句柄數組
::WSASetEvent(pThread->events[0]);
// 從給定線程的套節字對象清單中移除一個套節字對象
void RemoveSocketObj(PTHREAD_OBJ pThread, PSOCKET_OBJ pSocket)
// 在套節字對象清單中查找指定的套節字對象,找到後将之移除
PSOCKET_OBJ pTest = pThread->pSockHeader;
if(pTest == pSocket)
if(pThread->pSockHeader == pThread->pSockTail)
pThread->pSockTail = pThread->pSockHeader = pTest->pNext;
pThread->pSockHeader = pTest->pNext;
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 --;
// 說明一個連接配接中斷
::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 if(event.lNetworkEvents & FD_CLOSE) // 套節字關閉
else if(event.lNetworkEvents & FD_WRITE) // 套節字可寫
if(event.iErrorCode[FD_WRITE_BIT] == 0)
return TRUE;
while(FALSE);
// 套節字關閉,或者有錯誤發生,程式都會轉到這裡來執行
RemoveSocketObj(pThread, pSocket);
FreeSocketObj(pSocket);
return FALSE;
PSOCKET_OBJ FindSocketObj(PTHREAD_OBJ pThread, int nIndex) // nIndex從1開始
// 在套節字清單中查找
while(--nIndex)
if(pSocket == NULL)
return NULL;
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;
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(!HandleIO(pThread, pSocket))
printf(" Unable to find socket object /n ");
///////////////////////////////////////////
// EventSelectServer.cpp檔案
#include <winsock2.h>
#pragma comment(lib, "WS2_32") // 連結到WS2_32.lib
#include <stdio.h>
#include <windows.h>
#include "EventSelect.h"
class CInitSock
public:
CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
// 初始化WS2_32.dll
WSADATA wsaData;
WORD sockVersion = MAKEWORD(minorVer, majorVer);
if(::WSAStartup(sockVersion, &wsaData) != 0)
exit(0);
~CInitSock()
::WSACleanup();
};
// 初始化Winsock庫
CInitSock theSock;
int main1();
int main2();
void main()
printf("Please select a item:/n");
printf("s:Server/n");
printf("c:Client/n");
char a;
while(1){
scanf("%c",&a);
if(a=='s'){main1();break;}
else if(a=='c'){main2();break;}
else continue;
int main1()
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);
// 處理客戶連接配接請求,列印狀态資訊
int nRet = ::WaitForSingleObject(event, 5*1000);
if(nRet == WAIT_FAILED)
printf(" Failed WaitForSingleObject() /n");
else if(nRet == WSA_WAIT_TIMEOUT) // 定時顯式狀态資訊
printf(" /n");
printf(" TatolConnections: %d /n", g_nTatolConnections);
printf(" CurrentConnections: %d /n", g_nCurrentConnections);
else // 有新的連接配接未決
::ResetEvent(event);
// 循環處理所有未決的連接配接請求
sockaddr_in si;
int nLen = sizeof(si);
SOCKET sNew = ::accept(sListen, (sockaddr*)&si, &nLen);
if(sNew == SOCKET_ERROR)
PSOCKET_OBJ pSocket = GetSocketObj(sNew);
pSocket->addrRemote = si;
::WSAEventSelect(pSocket->s, pSocket->event, FD_READ|FD_CLOSE|FD_WRITE);
AssignToFreeThread(pSocket);
::DeleteCriticalSection(&g_cs);
//Client
int main2()
// 建立套節字
SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(s == INVALID_SOCKET)
printf(" Failed socket() /n");
// 也可以在這裡調用bind函數綁定一個本地位址
// 否則系統将會自動安排
// 填寫遠端位址資訊
sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(4567);
// 注意,這裡要填寫伺服器程式(TCPServer程式)所在機器的IP位址
// 如果你的計算機沒有聯網,直接使用127.0.0.1即可
servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if(::connect(s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1)
printf(" Failed connect() /n");
while(TRUE){
scanf("%s",szText);
//puts(szText);
// 向伺服器端發送資料
::send(s, szText, strlen(szText), 0);
// 關閉套節字
::closesocket(s);