日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

WinSock2编程之打造完整的SOCKET池

發布時間:2024/4/11 编程问答 54 豆豆
生活随笔 收集整理的這篇文章主要介紹了 WinSock2编程之打造完整的SOCKET池 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在Winodows平臺上,網絡編程的主要接口就是WinSock,目前大多數的Windows平臺上的WinSock平臺已經升級到2.0版,簡稱為WinSock2。在WinSock2中擴展了很多很有用的Windows味很濃的SOCKET專用API,為Windows平臺用戶提供高性能的網絡編程支持。這些函數中的大多數已經不再是標準的“Berkeley”套接字模型的API了。使用這些函數的代價就是你不能再將你的網絡程序輕松的移植到“尤里平臺”(我給Unix +Linux平臺的簡稱)下,反過來因為Windows平臺支持標準的“Berkeley”套接字模型,所以你可以將大多數尤里平臺下的網絡應用移植到Windows平臺下。

如果不考慮可移植性(或者所謂的跨平臺性),而是著重于應用的性能時,尤其是注重服務器性能時,對于Windows的程序,都鼓勵使用WinSock2擴展的一些API,更鼓勵使用IOCP模型,因為這個模型是目前Windows平臺上比較完美的一個高性能IO編程模型,它不但適用于SOCKET編程,還適用于讀寫硬盤文件,讀寫和管理命名管道、郵槽等等。如果再結合Windows線程池,IOCP幾乎可以利用當今硬件所有可能的新特性(比如多核,DMA,高速總線等等),本身具有先天的擴展性和可用性。

今天討論的重點就是SOCKET池。很多VC程序員也許對SOCKET池很陌生,也有些可能很熟悉,那么這里就先討論下這個概念。

在Windows平臺上SOCKET實際上被視作一個內核對象的句柄,很多Windows API在支持傳統的HANDLE參數的同時也支持SOCKET,比如有名的CreateIoCompletionPort就支持將SOCKET句柄代替HANDLE參數傳入并調用。熟悉Windows內核原理的讀者,立刻就會發現,這樣的話,我們創建和銷毀一個SOCKET句柄,實際就是在系統內部創建了一個內核對象,對于Windows來說這牽扯到從Ring3層到Ring0層的耗時操作,再加上復雜的安全審核機制,實際創建和銷毀一個SOCKET內核對象的成本還是蠻高的。尤其對于一些面向連接的SOCKET應用,服務端往往要管理n多個代表客戶端通信的SOCKET對象,而且因為客戶的變動性,主要面臨的大量操作除了一般的收發數據,剩下的就是不斷創建和銷毀SOCKET句柄,對于一個頻繁接入和斷開的服務器應用來說,創建和銷毀SOCKET的性能代價立刻就會體現出來,典型的例如WEB服務器程序,就是一個需要頻繁創建和銷毀SOCKET句柄的SOCKET應用。這種情況下我們通常都希望對于斷開的SOCKET對象,不是簡單的“銷毀”了之(很多時候“斷開”的含義不一定就等價于“銷毀”,可以仔細思考一下),更多時候希望能夠重用這個SOCKET對象,這樣我們甚至可以事先創建一批SOCKET對象組成一個“池”,在需要的時候“重用”其中的SOCKET對象,不需要的時候將SOCKET對象重新丟入池中即可,這樣就省去了頻繁創建銷毀SOCKET對象的性能損失。在原始的“Berkeley”套接字模型中,想做到這點是沒有什么辦法的。而幸運的是在Windows平臺上,尤其是支持WinSock2的平臺上,已經提供了一套完整的API接口用于支持SOCKET池。

對于符合以上要求的SOCKET池,首先需要做到的就是對SOCKET句柄的“回收”,因為創建函數無論在那個平臺上都是現成的,而最早能夠實現這個功能的WinSock函數就是TransmitFile,如果代替closesocket函數像下面這樣調用就可以“回收”一個SOCKET句柄,而不是銷毀:(注意“回收”這個功能對于TransmitFile函數來說只是個“副業”。)

TransmitFile(hSocket,NULL,0,0,NULL,NULL,TF_DISCONNECT | TF_REUSE_SOCKET );

注意上面函數的最后一個參數,使用了標志TF_DISCONNECT和TF_REUSE_SOCKET,第一個值表示斷開,第二個值則明確的表示“重用”實際上也就是回收這個SOCKET,經過這個處理的SOCKET句柄,就可以直接再用于connect等操作,但是此時我們會發現,這個回收來的SOCKET似乎沒什么用,因為其他套接字函數沒法直接利用這個回收來的SOCKET句柄。

這時就要WinSock2的一組專用API上場了。我將它們按傳統意義上的服務端和客戶端分為兩組:

一、???????? 服務端:

SOCKET WSASocket(

? __in????????? int af,

? __in????????? int type,

? __in????????? int protocol,

? __in????????? LPWSAPROTOCOL_INFO lpProtocolInfo,

? __in???????? ?GROUP g,

? __in????????? DWORD dwFlags

);

BOOL AcceptEx(

? __in????????? SOCKET sListenSocket,

? __in????????? SOCKET sAcceptSocket,

? __in????????? PVOID lpOutputBuffer,

? __in????????? DWORD dwReceiveDataLength,

? __in????????? DWORD dwLocalAddressLength,

? __in????????? DWORD dwRemoteAddressLength,

? __out???????? LPDWORD lpdwBytesReceived,

? __in????????? LPOVERLAPPED lpOverlapped

);

BOOL DisconnectEx(

? __in????????? SOCKET hSocket,

? __in????????? LPOVERLAPPED lpOverlapped,

? __in????????? DWORD dwFlags,

? __in????????? DWORD reserved

);

二、???????? 客戶端:

SOCKET WSASocket(

? __in????????? int af,

? __in????????? int type,

? __in????????? int protocol,

? __in????????? LPWSAPROTOCOL_INFO lpProtocolInfo,

? __in????????? GROUP g,

? __in????????? DWORD dwFlags

);

BOOL PASCAL ConnectEx(

? __in????????? SOCKET s,

? __in????????? const struct sockaddr* name,

? __in????????? int namelen,

? __in_opt????? PVOID lpSendBuffer,

? __in????????? DWORD dwSendDataLength,

? __out???????? LPDWORD lpdwBytesSent,

? __in????????? LPOVERLAPPED lpOverlapped

);

BOOL DisconnectEx(

? __in????????? SOCKET hSocket,

? __in????????? LPOVERLAPPED lpOverlapped,

? __in????????? DWORD dwFlags,

? __in????????? DWORD reserved

);

注意觀察這些函數,似乎和傳統的“Berkeley”套接字模型中的一些函數“大同小異”,其實仔細觀察他們的參數,就已經可以發現一些調用他們的“玄機”了。

首先我們來看AcceptEx函數,與accept函數不同,它需要兩個SOCKET句柄作為參數,頭一個參數的含義與accept函數的相同,而第二個參數的意思就是accept函數返回的那個代表與客戶端通信的SOCKET句柄,在傳統的accept內部,實際在返回那個代表客戶端的SOCKET時,是在內部調用了一個SOCKET的創建動作,先創建這個SOCKET然后再“accept”讓它變成代表客戶端連接的SOCKET,而AcceptEx函數就在這里“擴展”(實際上是“閹割”才對)accept函數,省去了內部那個明顯的創建SOCKET的動作,而將這個創建動作交給最終的調用者自己來實現。AcceptEx要求調用者創建好那個sAcceptSocket句柄然后傳進去,這時我們立刻發現,我們回收的那個SOCKET是不是也可以傳入呢?答案是肯定的,我們就是可以利用這個函數傳入那個“回收”來的SOCKET句柄,最終實現服務端的SOCKET重用。

這里需要注意的就是,AcceptEx函數必須工作在非阻塞的IOCP模型下,同時即使AcceptEx函數返回了,也不代表客戶端連接進來或者連接成功了,我們必須依靠它的“完成通知”才能知道這個事實,這也是AcceptEx函數區別于accept這個阻塞方式函數的最大之處。通常可以利用AcceptEx的非阻塞特性和IOCP模型的優點,一次可以“預先”發出成千上萬個AcceptEx調用,“等待”客戶端的連接。對于習慣了accept阻塞方式的程序員來說,理解AcceptEx的工作方式還是需要費一些周折的。下面的例子就演示了如何一次調用多個AcceptEx:

//批量創建SOCKET,并調用對應的AcceptEx

for(UINT i = 0; i < 1000; i++)

{//調用1000次

//創建與客戶端通訊的SOCKET,注意SOCKET的創建方式

skAccept = ::WSASocket(AF_INET,

?????????????????? SOCK_STREAM,

?????????????????? IPPROTO_TCP,

?????????????????? NULL,

?????????????????? 0,

?????????????????? WSA_FLAG_OVERLAPPED);

if (INVALID_SOCKET == skAccept)

{

? ? throw CGRSException((DWORD)WSAGetLastError());

}

//創建一個自定義的OVERLAPPED擴展結構,使用IOCP方式調用

pAcceptOL = new CGRSOverlappedData(GRS_OP_ACCEPT

,this,skAccept,NULL);

pAddrBuf = pAcceptOL->GetAddrBuf();

//4、發出AcceptEx調用

//注意將AcceptEx函數接收連接數據緩沖的大小設定成了0,這將導致此函數立即返回,雖然與

//不設定成0的方式而言,這導致了一個較低下的效率,但是這樣提高了安全性,所以這種效率

//犧牲是必須的

if(!AcceptEx(m_skServer,

?????????????????? skAccept,

?????????????????? pAddrBuf->m_pBuf,

?????????????????? 0,//將接收緩沖置為0,令AcceptEx直接返回,防止拒絕服務攻擊

?????????????????? GRS_ADDRBUF_SIZE,

?????????????????? GRS_ADDRBUF_SIZE,

?????????????????? NULL,

?????????????????? (LPOVERLAPPED)pAcceptOL))

{

int iError = WSAGetLastError();

if( ERROR_IO_PENDING != iError

???? && WSAECONNRESET != iError )

{

???? if(INVALID_SOCKET != skAccept)

???? {

??????? ?::closesocket(skAccept);

??????? ?skAccept = INVALID_SOCKET;

???? }

? ?? if( NULL != pAcceptOL)

???? {

???????????? GRS_ISVALID(pAcceptOL,sizeof(CGRSOverlappedData));

delete pAcceptOL;

???? pAcceptOL = NULL;

???? }

? }

}

}

以上的例子只是簡單的演示了AcceptEx的調用,還沒有涉及到真正的“回收重用”這個主題,那么下面的例子就演示了如何重用一個SOCKET句柄:

if(INVALID_SOCKET == skClient)

{

throw CGRSException(_T("SOCKET句柄是無效的!"));

}

OnPreDisconnected(skClient,pUseData,0);

CGRSOverlappedData*pData

= new GRSOverlappedData(GRS_OP_DISCONNECTEX

,this,skClient,pUseData);

//回收而不是關閉后再創建大大提高了服務器的性能

DisconnectEx(skClient,&pData->m_ol,TF_REUSE_SOCKET,0);?

......

????? //在接收到DisconnectEx函數的完成通知之后,我們就可以重用這個SOCKET了

CGRSAddrbuf*pBuf = NULL;

pNewOL = new CGRSOverlappedData(GRS_OP_ACCEPT

,this,skClient,pUseData);

pBuf = pNewOL->GetAddrBuf();

//把這個回收的SOCKET重新丟進連接池

if(!AcceptEx(m_skServer,skClient,pBuf->m_pBuf,

???????????????? 0,//將接收緩沖置為0,令AcceptEx直接返回,防止拒絕服務攻擊

???????????????? GRS_ADDRBUF_SIZE, GRS_ADDRBUF_SIZE,

???????????????? NULL,(LPOVERLAPPED)pNewOL))

{

int iError = WSAGetLastError();

?? ?if( ERROR_IO_PENDING != iError

????? ? && WSAECONNRESET != iError )

?? ?{

?? ???? throw CGRSException((DWORD)iError);

?? ? }

}

//注意在這個SOCKET被重新利用后,重新與IOCP綁定一下,該操作會返回一個已設置的錯誤,這個錯誤直接被忽略即可

::BindIoCompletionCallback((HANDLE)skClient

,Server_IOCPThread, 0);

?

至此回收重用SOCKET的工作也就結束了,以上的過程實際理解了IOCP之后就比較好理解了,例子的最后我們使用了BindIoCompletionCallback函數重新將SOCKET丟進了IOCP線程池中,實際還可以再次使用CreateIoCompletionPort函數達到同樣的效果,這里列出這一步就是告訴大家,不要忘了再次綁定一下完成端口和SOCKET。

?? ?對于客戶端來說,可以使用ConnectEx函數來代替connect函數,與AcceptEx函數相同,ConnectEx函數也是以非阻塞的IOCP方式工作的,唯一要注意的就是在WSASocket調用之后,在ConnectEx之前要調用一下bind函數,將SOCKET提前綁定到一個本地地址端口上,當然回收重用之后,就無需再次綁定了,這也是ConnectEx較之connect函數高效的地方之一。

?? 與AcceptEx函數類似,也可以一次發出成千上萬個ConnectEx函數的調用,可以連接到不同的服務器,也可以連接到相同的服務器,連接到不同的服務器時,只需提供不同的sockaddr即可。

?? ?通過上面的例子和講解,大家應該對SOCKET池概念以及實際的應用有個大概的了解了,當然核心仍然是理解了IOCP模型,否則還是寸步難行。

在上面的例子中,回收SOCKET句柄主要使用了DisconnectEx函數,而不是之前介紹的TransmitFile函數,為什么呢?因為TransmitFile函數在一些情況下會造成死鎖,無法正常回收SOCKET,畢竟不是專業的回收重用SOCKET函數,我就遇到過好幾次死鎖,最后偶然的發現了DisconnectEx函數這個專用的回收函數,調用之后發現比TransmitFile專業多了,而且不管怎樣都不會死鎖。

最后需要補充的就是這幾個函數的調用方式,不能像傳統的SOCKET API那樣直接調用它們,而需要使用一種間接的方式來調用,尤其是AcceptEx和DisconnectEx函數,下面給出了一個例子類,用于演示如何動態載入這些函數并調用之:

class CGRSMsSockFun

{

public:

CGRSMsSockFun(SOCKET skTemp = INVALID_SOCKET)

{

???? if( INVALID_SOCKET != skTemp )

???? {

?????? LoadAllFun(skTemp);

???? ?}

}

public:

virtual ~CGRSMsSockFun(void)

{

}

protected:

BOOL LoadWSAFun(SOCKET& skTemp,GUID&funGuid,void*&pFun)

{

???? DWORD dwBytes = 0;

???? BOOL bRet = TRUE;

???? pFun = NULL;

???? BOOL bCreateSocket = FALSE;

???? try

???? {

?????? if(INVALID_SOCKET == skTemp)

?????? {

????????? skTemp = ::WSASocket(AF_INET,SOCK_STREAM,

???????????? IPPROTO_TCP,NULL,0,WSA_FLAG_OVERLAPPED);

bCreateSocket = (skTemp != INVALID_SOCKET);

?????? }

if(INVALID_SOCKET == skTemp)

?????? {

????????? throw CGRSException((DWORD)WSAGetLastError());

?????? }

?????? if(SOCKET_ERROR == ::WSAIoctl(skTemp,

??????????????? SIO_GET_EXTENSION_FUNCTION_POINTER,

??????????????? &funGuid,sizeof(funGuid),

??????????????? &pFun,sizeof(pFun),&dwBytes,NULL,

??????????????? NULL))

?????? {

???????????? pFun = NULL;

???????????? throw CGRSException((DWORD)WSAGetLastError());

?????? }

? }

? catch(CGRSException& e)

? {

???? ?if(bCreateSocket)

???? ?{

?????? ?::closesocket(skTemp);

???? ?}

? }

? return NULL != pFun;

}

protected:

LPFN_ACCEPTEX m_pfnAcceptEx;

LPFN_CONNECTEX m_pfnConnectEx;

LPFN_DISCONNECTEX m_pfnDisconnectEx;

LPFN_GETACCEPTEXSOCKADDRS m_pfnGetAcceptExSockaddrs;

LPFN_TRANSMITFILE m_pfnTransmitfile;

LPFN_TRANSMITPACKETS m_pfnTransmitPackets;

LPFN_WSARECVMSG m_pfnWSARecvMsg;

protected:

BOOL LoadAcceptExFun(SOCKET &skTemp)

{

???? GUID GuidAcceptEx = WSAID_ACCEPTEX;

???? return LoadWSAFun(skTemp,GuidAcceptEx

,(void*&)m_pfnAcceptEx);

}

BOOL LoadConnectExFun(SOCKET &skTemp)

{

???? GUID GuidAcceptEx = WSAID_CONNECTEX;

???? return LoadWSAFun(skTemp,GuidAcceptEx

,(void*&)m_pfnConnectEx);

}

BOOL LoadDisconnectExFun(SOCKET&skTemp)

{

???? GUID GuidDisconnectEx = WSAID_DISCONNECTEX;

???? return LoadWSAFun(skTemp,GuidDisconnectEx

,(void*&)m_pfnDisconnectEx);

}

BOOL LoadGetAcceptExSockaddrsFun(SOCKET &skTemp)

{

???? GUID GuidGetAcceptExSockaddrs

= WSAID_GETACCEPTEXSOCKADDRS;

???? return LoadWSAFun(skTemp,GuidGetAcceptExSockaddrs

,(void*&)m_pfnGetAcceptExSockaddrs);

}

BOOL LoadTransmitFileFun(SOCKET&skTemp)

{

???? GUID GuidTransmitFile = WSAID_TRANSMITFILE;

???? return LoadWSAFun(skTemp,GuidTransmitFile

,(void*&)m_pfnTransmitfile);

}

BOOL LoadTransmitPacketsFun(SOCKET&skTemp)

{

???? GUID GuidTransmitPackets = WSAID_TRANSMITPACKETS;

???? return LoadWSAFun(skTemp,GuidTransmitPackets

,(void*&)m_pfnTransmitPackets);

}

BOOL LoadWSARecvMsgFun(SOCKET&skTemp)

{

???? GUID GuidTransmitPackets = WSAID_TRANSMITPACKETS;

???? return LoadWSAFun(skTemp,GuidTransmitPackets

,(void*&)m_pfnWSARecvMsg);

}

public:

BOOL LoadAllFun(SOCKET skTemp)

{//注意這個地方的調用順序,是根據服務器的需要,并結合了表達式副作用

? //而特意安排的調用順序

? return (LoadAcceptExFun(skTemp) &&

???????????? LoadGetAcceptExSockaddrsFun(skTemp) &&

???????????? LoadTransmitFileFun(skTemp) &&

???????????? LoadTransmitPacketsFun(skTemp) &&

???????????? LoadDisconnectExFun(skTemp) &&

???????????? LoadConnectExFun(skTemp) &&

???????????? LoadWSARecvMsgFun(skTemp));

}

?

public:

GRS_FORCEINLINE BOOL AcceptEx (

????????? SOCKET sListenSocket,

????????? SOCKET sAcceptSocket,

????????? PVOID lpOutputBuffer,

????????? DWORD dwReceiveDataLength,

????????? DWORD dwLocalAddressLength,

????????? DWORD dwRemoteAddressLength,

????????? LPDWORD lpdwBytesReceived,

????????? LPOVERLAPPED lpOverlapped

????????? )

{

???? GRS_ASSERT(NULL != m_pfnAcceptEx);

???? return m_pfnAcceptEx(sListenSocket,

???????????? sAcceptSocket,lpOutputBuffer,

???????????? dwReceiveDataLength,dwLocalAddressLength,

???? ??????? dwRemoteAddressLength,lpdwBytesReceived,

???????????? lpOverlapped);

}

GRS_FORCEINLINE BOOL ConnectEx(

????????? SOCKET s,const struct sockaddr FAR *name,

????????? int namelen,PVOID lpSendBuffer,

????????? DWORD dwSendDataLength,LPDWORD lpdwBytesSent,

????????? LPOVERLAPPED lpOverlapped

????????? )

{

???? GRS_ASSERT(NULL != m_pfnConnectEx);

???? return m_pfnConnectEx(

???????????? s,name,namelen,lpSendBuffer,

???????????? dwSendDataLength,lpdwBytesSent,

???????????? lpOverlapped

???????????? );

}

GRS_FORCEINLINE BOOL DisconnectEx(

????????? SOCKET s,LPOVERLAPPED lpOverlapped,

????????? DWORD? dwFlags,DWORD? dwReserved

????????? )

{

???? GRS_ASSERT(NULL != m_pfnDisconnectEx);

???? return m_pfnDisconnectEx(s,

???????????? lpOverlapped,dwFlags,dwReserved);

}

GRS_FORCEINLINE VOID GetAcceptExSockaddrs (

????????? PVOID lpOutputBuffer,

????????? DWORD dwReceiveDataLength,

????????? DWORD dwLocalAddressLength,

????????? DWORD dwRemoteAddressLength,

????????? sockaddr **LocalSockaddr,

????????? LPINT LocalSockaddrLength,

????????? sockaddr **RemoteSockaddr,

????????? LPINT RemoteSockaddrLength

????????? )

{

???? GRS_ASSERT(NULL != m_pfnGetAcceptExSockaddrs);

???? return m_pfnGetAcceptExSockaddrs(

????????? lpOutputBuffer,dwReceiveDataLength,

????????? dwLocalAddressLength,dwRemoteAddressLength,

????????? LocalSockaddr,LocalSockaddrLength,

????????? RemoteSockaddr,RemoteSockaddrLength

????????? );

}

GRS_FORCEINLINE BOOL TransmitFile(

???? SOCKET hSocket,HANDLE hFile,

???? DWORD nNumberOfBytesToWrite,

???? DWORD nNumberOfBytesPerSend,

???? LPOVERLAPPED lpOverlapped,

???? LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,

???? DWORD dwReserved

???? )

{

???? GRS_ASSERT(NULL != m_pfnTransmitfile);

???? return m_pfnTransmitfile(

???????????? hSocket,hFile,nNumberOfBytesToWrite,

???????????? nNumberOfBytesPerSend,lpOverlapped,

???????????? lpTransmitBuffers,dwReserved

???????????? );

}

GRS_FORCEINLINE BOOL TransmitPackets(

???? SOCKET hSocket,????????????????????????????

???? LPTRANSMIT_PACKETS_ELEMENT lpPacketArray,???????????????????? ??????????

???? DWORD nElementCount,DWORD nSendSize,???????????????

???? LPOVERLAPPED lpOverlapped,DWORD dwFlags??????????????????????????????

???? )

{

???? GRS_ASSERT(NULL != m_pfnTransmitPackets);

???? return m_pfnTransmitPackets(

???????????? hSocket,lpPacketArray,nElementCount,

nSendSize,lpOverlapped,dwFlags

???????????? );

}

GRS_FORCEINLINE INT WSARecvMsg(

????????? SOCKET s,LPWSAMSG lpMsg,

????????? LPDWORD lpdwNumberOfBytesRecvd,

????????? LPWSAOVERLAPPED lpOverlapped,

????????? LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

????????? )

{

???? GRS_ASSERT(NULL != m_pfnWSARecvMsg);

???? return m_pfnWSARecvMsg(

???????????? s,lpMsg,lpdwNumberOfBytesRecvd,

???????????? lpOverlapped,lpCompletionRoutine

???????????? );

}

/*WSAID_ACCEPTEX

? WSAID_CONNECTEX

? WSAID_DISCONNECTEX

? WSAID_GETACCEPTEXSOCKADDRS

? WSAID_TRANSMITFILE

? WSAID_TRANSMITPACKETS

? WSAID_WSARECVMSG

? WSAID_WSASENDMSG */

};

這個類的使用非常簡單,只需要聲明一個類的對象,然后調用其成員AcceptEx、DisconnectEx函數等即可,參數與這些函數的MSDN聲明方式完全相同,除了本文中介紹的這些函數外,這個類還包含了很多其他的Winsock2函數,那么都應該按照這個類中演示的這樣來動態載入后再行調用,如果無法載入通常說明你的環境中沒有Winsock2函數庫,或者是你初始化的不是2.0版的Winsock環境。

這個類是本人完整類庫的一部分,如要使用需要自行修改一些地方,如果不知如何修改或遇到什么問題,可以直接跟帖說明,我會不定期回答大家的問題,這個類可以免費使用、分發、修改,可以用于任何商業目的,但是對于使用后引起的任何問題,本人概不負責,有問題請跟帖。關于AcceptEx以及其他一些函數,包括本文中沒有介紹到得函數,我會在后續的一些專題文章中進行詳細深入的介紹,敬請期待。如果你有什么疑問,或者想要了解什么也請跟帖說明,我會在后面的文章中盡量說明。

總結

以上是生活随笔為你收集整理的WinSock2编程之打造完整的SOCKET池的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。