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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

网狐棋牌(五) TCPSocketEnging分析

發(fā)布時間:2024/4/11 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 网狐棋牌(五) TCPSocketEnging分析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
相關(guān)UML:
網(wǎng)絡(luò)引擎整體結(jié)構(gòu):


SocketItem細(xì)節(jié):



先來看幾個底層結(jié)構(gòu):
//重疊結(jié)構(gòu)類
class?COverLapped
{
????//變量定義
public:
????WSABUF????????????????????????????m_WSABuffer;????????????????????????//數(shù)據(jù)指針
????OVERLAPPED????????????????????????m_OverLapped;????????????????????????//重疊結(jié)構(gòu)
????const?enOperationType????????????m_OperationType;????????????????????//操作類型

????
//函數(shù)定義
public:
????//構(gòu)造函數(shù)
????COverLapped(enOperationType?OperationType);
????//析構(gòu)函數(shù)
????virtual?~COverLapped();

????//信息函數(shù)
public:
????//獲取類型
????enOperationType?GetOperationType()?{?return?m_OperationType;?}
};

//接收重疊結(jié)構(gòu)
class?COverLappedSend?:?public?COverLapped
{
????//數(shù)據(jù)變量
public:
????BYTE????????????????????????????m_cbBuffer[SOCKET_BUFFER];????????????//數(shù)據(jù)緩沖

????
//函數(shù)定義
public:
????//構(gòu)造函數(shù)
????COverLappedSend();
????//析構(gòu)函數(shù)
????virtual?~COverLappedSend();
};

//重疊結(jié)構(gòu)模板
template?<enOperationType?OperationType>?class?CATLOverLapped?:?public?COverLapped
{
????//函數(shù)定義
public:
????//構(gòu)造函數(shù)
????CATLOverLapped()?:?COverLapped(OperationType)?{}
????//析構(gòu)函數(shù)
????virtual?~CATLOverLapped()?{}
};

先復(fù)習(xí)下基礎(chǔ),Windows下的網(wǎng)絡(luò)模型有很多種,這里只拿出三種來說:
EventSelect:基于信號機(jī)制,以socket為單位綁定信號量,當(dāng)socket上有指定的事件發(fā)生時激發(fā)信號,然后查詢事件處理事件重設(shè)事件,繼續(xù)在信號量上等待。其實(shí)也是在伯克利select模型上的換不換藥的加強(qiáng)。
OverLapped:分兩種工作模式完成回調(diào),和完成事件。重疊IO監(jiān)視每次操作,每次IO都綁定一個重疊對象,當(dāng)操作完成以后激發(fā)信號或者調(diào)用回調(diào)。
IOCP:和overlapped類似,不過結(jié)果經(jīng)過了Windows的預(yù)處理以隊(duì)列的形式掛在完成端口上

根據(jù)上面的復(fù)習(xí),可以得出一個結(jié)論,IOCP環(huán)境中每一次IO操作都需要一個重疊結(jié)構(gòu),那么一個CServerSocketItem至少需要如些這些東東:
他要接受數(shù)據(jù),所以必須有一個接受數(shù)據(jù)的 OverLapped
它要發(fā)送數(shù)據(jù),說以必須有一個發(fā)送數(shù)據(jù)的 OverLapped
netFox對OverLapped做了使用了類似池的的管理手段,他的Send都是不等待上一次完成就直接投遞下一個請求了,,,這是很操蛋的做法,,,

然后繼續(xù)復(fù)習(xí)下基礎(chǔ):
在EventSelect模型中獲處理件類型流程是這樣:
event受信,使用::WSAEnumNetworkEvents查詢和這個event關(guān)聯(lián)的socket發(fā)生的事件,根據(jù)查詢到的事件類型去處理事件
在以每一次IO為查詢對象重疊IO、IOCP模型中是這樣:
使用GetOverlappedResult 或者 GetQueuedCompletionStatus然后根據(jù)重疊結(jié)構(gòu)去查詢投遞的是什么類型的操作,然后找到關(guān)聯(lián)的socket去操作,,,

這樣必然要給OverLapped做個擴(kuò)展,提供一種通過OverLapped查詢操作類型和socket的能力。
通過分析代碼,netFox關(guān)聯(lián)socket是通過在創(chuàng)建完成端口的時候綁定SocketItem對象指針完成的,操作類型是通過對OverLapped結(jié)構(gòu)加強(qiáng)完成的。
通過GetQueuedCompletionStatus獲取到完成OverLapped以后使用一個宏:
(這是COverLapped類型)? pSocketLapped=CONTAINING_RECORD(pOverLapped,COverLapped,m_OverLapped);
來獲取包裝后的OverLapped,然后獲取操作類型,然后執(zhí)行具體操作。
其實(shí)宏的展開如下:
(COverLapped*)((BYTE*)pOverLapped - (COverLapped*)(0)->m_OverLapped);
pOverLapped是獲取到的某個COverLapped中的成員變量,(COverLapped*)(0)->m_OverLapped是到在COverLapped中的偏移,((BYTE*)pOverLapped - (COverLapped*)(0)->m_OverLapped) 就是根據(jù)pOverLapped推算出來的包含地址為pOverLapped作為成員變量m_OverLapped的COverLapped對象的地址。
然后就分別調(diào)用:

//發(fā)送完成函數(shù)
bool CServerSocketItem::OnSendCompleted(COverLappedSend * pOverLappedSend, DWORD dwThancferred);

//接收完成函數(shù)
bool CServerSocketItem::OnRecvCompleted(COverLappedRecv * pOverLappedRecv, DWORD dwThancferred);

為毛要區(qū)分Send OverLapped 和 Recv OverLapped呢,,,
應(yīng)為投遞一次Send不一定是瞬間完成的,在處理的過程中存儲數(shù)據(jù)的內(nèi)存應(yīng)該是鎖定的,也就是不允許修改的,,,所以O(shè)verLapped應(yīng)該自己管理內(nèi)存。
而recv應(yīng)該也是需要有一片內(nèi)存直接接受數(shù)據(jù)的,很奇怪netFox沒有提供,,,

recv居然是在投遞接受請求的時候給了一個空的buffer,然后在完成回調(diào)中自己再次調(diào)用recv方法接受數(shù)據(jù)。
接受有關(guān)的成員變量如下:
//狀態(tài)變量
protected:
????bool????????????????????????????m_bNotify;????????????????????????????//通知標(biāo)志
????bool????????????????????????????m_bRecvIng;????????????????????????????//接收標(biāo)志
????bool????????????????????????????m_bCloseIng;????????????????????????//關(guān)閉標(biāo)志
????bool????????????????????????????m_bAllowBatch;????????????????????????//接受群發(fā)
????WORD????????????????????????????m_wRecvSize;????????????????????????//接收長度
????BYTE????????????????????????????m_cbRecvBuf[SOCKET_BUFFER*5];????????//接收緩沖 int iRetCode=recv(m_hSocket,(char *)m_cbRecvBuf+m_wRecvSize,sizeof(m_cbRecvBuf)-m_wRecvSize,0);
難道這么蠢的做法只是為了躲開分包算法?
具體的看看接受代碼:
//接收完成函數(shù)
bool?CServerSocketItem::OnRecvCompleted(COverLappedRecv?*?pOverLappedRecv,?DWORD?dwThancferred)
{
????//效驗(yàn)數(shù)據(jù)
????ASSERT(m_bRecvIng==true);

????//設(shè)置變量
????m_bRecvIng=false;
????m_dwRecvTickCount=GetTickCount();

????//判斷關(guān)閉
????if?(m_hSocket==INVALID_SOCKET)
????{
????????CloseSocket(m_wRountID);
????????return?true;
????}

????//接收數(shù)據(jù)
????int?iRetCode=recv(m_hSocket,(char?*)m_cbRecvBuf+m_wRecvSize,sizeof(m_cbRecvBuf)-m_wRecvSize,0);
????if?(iRetCode<=0)
????{
????????CloseSocket(m_wRountID);
????????return?true;
????}

????//接收完成
????m_wRecvSize+=iRetCode;
????BYTE?cbBuffer[SOCKET_BUFFER];
????CMD_Head?*?pHead=(CMD_Head?*)m_cbRecvBuf;

????//處理數(shù)據(jù)
????try
????{
????????while?(m_wRecvSize>=sizeof(CMD_Head))
????????{
????????????//效驗(yàn)數(shù)據(jù)
????????????WORD?wPacketSize=pHead->CmdInfo.wDataSize;
????????????if?(wPacketSize>SOCKET_BUFFER)?throw?TEXT("數(shù)據(jù)包超長");
????????????if?(wPacketSize<sizeof(CMD_Head))?throw?TEXT("數(shù)據(jù)包非法");
????????????if?(pHead->CmdInfo.cbMessageVer!=SOCKET_VER)?throw?TEXT("數(shù)據(jù)包版本錯誤");
????????????if?(m_wRecvSize<wPacketSize)?break;

????????????//提取數(shù)據(jù)
????????????CopyMemory(cbBuffer,m_cbRecvBuf,wPacketSize);
????????????WORD?wRealySize=CrevasseBuffer(cbBuffer,wPacketSize);
????????????ASSERT(wRealySize>=sizeof(CMD_Head));
????????????m_dwRecvPacketCount++;

????????????//解釋數(shù)據(jù)
????????????WORD?wDataSize=wRealySize-sizeof(CMD_Head);
????????????void?*?pDataBuffer=cbBuffer+sizeof(CMD_Head);
????????????CMD_Command?Command=((CMD_Head?*)cbBuffer)->CommandInfo;

????????????//內(nèi)核命令
????????????if?(Command.wMainCmdID==MDM_KN_COMMAND)
????????????{
????????????????switch?(Command.wSubCmdID)
????????????????{
????????????????case?SUB_KN_DETECT_SOCKET:????//網(wǎng)絡(luò)檢測
????????????????????{
????????????????????????break;
????????????????????}
????????????????default:?throw?TEXT("非法命令碼");
????????????????}
????????????}
????????????else?
????????????{
????????????????//消息處理
????????????????m_pIServerSocketItemSink->OnSocketReadEvent(Command,pDataBuffer,wDataSize,this);????????????
????????????}

????????????//刪除緩存數(shù)據(jù)
????????????m_wRecvSize-=wPacketSize;
????????????MoveMemory(m_cbRecvBuf,m_cbRecvBuf+wPacketSize,m_wRecvSize);
????????}
????}
????catch?()
????{?
????????CloseSocket(m_wRountID);
????????return?false;
????}

????return?RecvData();
}
這是還是有分包算法的,總的來說接受流程如下:
直接使用recv把數(shù)據(jù)接受到SocketItem的緩沖區(qū)中,當(dāng)長度大于CMD_HEAD之后,進(jìn)入處理階段,處理head數(shù)據(jù)各種判斷,然后將數(shù)據(jù)扔出去,再調(diào)整緩沖區(qū),,,

簡單的說:
Send完全不考慮同步問題,不管一個勁的網(wǎng)隊(duì)列投遞Send請求,,,這邊處理隊(duì)列也是直接Send完事,完全不考慮上一次是否send成功,,,
Recv更是莫名其妙的使用完成端口繞一圈還回到recv直接接受了,,,

很狗血的做法,,,

更正下我自己狗血的不理解:
如果一個服務(wù)器提交了非常多的重疊的receive在每一個連接上,那么限制會隨著連接數(shù)的增長而變化。如果一個服務(wù)器能夠預(yù)先估計可能會產(chǎn)生的最大并發(fā)連接數(shù),服務(wù)器可以投遞一個使用零緩沖區(qū)的receive在每一個連接上。因?yàn)楫?dāng)你提交操作沒有緩沖區(qū)時,那么也不會存在內(nèi)存被鎖定了。使用這種辦法后,當(dāng)你的receive操作事件完成返回時,該socket底層緩沖區(qū)的數(shù)據(jù)會原封不動的還在其中而沒有被讀取到receive操作的緩沖區(qū)來。此時,服務(wù)器可以簡單的調(diào)用非阻塞式的recv將存在socket緩沖區(qū)中的數(shù)據(jù)全部讀出來,一直到recv返回 WSAEWOULDBLOCK 為止。 這種設(shè)計非常適合那些可以犧牲數(shù)據(jù)吞吐量而換取巨大 并發(fā)連接數(shù)的服務(wù)器。當(dāng)然,你也需要意識到如何讓客戶端的行為盡量避免對服務(wù)器造成影響。在上一個例子中,當(dāng)一個零緩沖區(qū)的receive操作被返回后使 用一個非阻塞的recv去讀取socket緩沖區(qū)中的數(shù)據(jù),如果服務(wù)器此時可預(yù)計到將會有爆發(fā)的數(shù)據(jù)流,那么可以考慮此時投遞一個或者多個receive 來取代非阻塞的recv來進(jìn)行數(shù)據(jù)接收。(這比你使用1個缺省的8K緩沖區(qū)來接收要好的多。)

源碼中提供了一個簡單實(shí)用的解決WSAENOBUF錯誤的辦法。我們執(zhí)行了一個零字節(jié)緩沖的異步WSARead(...)(參見 OnZeroByteRead(..))。當(dāng)這個請求完成,我們知道在TCP/IP棧中有數(shù)據(jù),然后我們通過執(zhí)行幾個有MAXIMUMPACKAGESIZE緩沖的異步WSARead(...)去讀,解決了WSAENOBUFS問題。但是這種解決方法降低了服務(wù)器的吞吐量。

總結(jié):?

解決方法一:?

投遞使用空緩沖區(qū)的 receive操作,當(dāng)操作返回后,使用非阻塞的recv來進(jìn)行真實(shí)數(shù)據(jù)的讀取。因此在完成端口的每一個連接中需要使用一個循環(huán)的操作來不斷的來提交空緩沖區(qū)的receive操作。?

解決方法二:?

在投遞幾個普通含有緩沖區(qū)的receive操作后,進(jìn)接著開始循環(huán)投遞一個空緩沖區(qū)的receive操作。這樣保證它們按照投遞順序依次返回,這樣我們就總能對被鎖定的內(nèi)存進(jìn)行解鎖。?



///
如果一個服務(wù)器同時連接了許多客戶端, 對每個客戶端又調(diào)用了許多 WSARecv, 那么大量的內(nèi)存將會被鎖定到非分頁內(nèi)存池. 鎖定這些內(nèi)存時是按照頁面邊界來鎖定的, 也就是說即使你 WSARecv 的緩存大小是 1 字節(jié), 被鎖定的內(nèi)存也將會是 4k. 非分頁內(nèi)存池是由整個系統(tǒng)共用的, 如果用完的話最壞的情況就是系統(tǒng)崩潰. 一個解決辦法是, 使用大小為 0 的緩沖區(qū)調(diào)用 WSARecv. 等到調(diào)用成功時再換用非阻塞的 recv 接收到來的數(shù)據(jù), 直到它返回 WSAEWOULDBLOCK 表明數(shù)據(jù)已經(jīng)全部讀完. 在這個過程中沒有任何內(nèi)存需要被鎖定, 但壞處是效率稍低. 轉(zhuǎn)自:http://www.cppblog.com/Error/articles/148237.html

總結(jié)

以上是生活随笔為你收集整理的网狐棋牌(五) TCPSocketEnging分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。