這個(gè)問(wèn)題是這樣,如果在接收端(WSARecv)投遞了一個(gè)200字節(jié)的WSABUF,那麼你的工作器線程處理完成通知的時(shí)候,首先收到第一個(gè)200字節(jié),此時(shí)你可以繼續(xù)投遞接收下來(lái)的200個(gè)字節(jié)(注意了,是得到上一個(gè)200字節(jié)通知并保存處理了才投遞下一個(gè))這樣才能保證你的接收和處理1000個(gè)字節(jié)不亂。如果你一開(kāi)始就投遞(而不是投遞一個(gè)之後,在得到通知處理之後依次投遞)了5個(gè)WSARecv,對(duì)應(yīng)5個(gè)單IO操作數(shù)據(jù),會(huì)有5個(gè)通知在隊(duì)列,當(dāng)然在隊(duì)列裏這5個(gè)完成包順序是正確的。問(wèn)題在于,你有多個(gè)工作器線程來(lái)分別處理這些通知,那麼雖然隊(duì)列裏是正確的,但你的工作器線程在處理通知和保存數(shù)據(jù)的時(shí)候,保存這些數(shù)據(jù)有可能出現(xiàn)亂序。根源在于投遞WSARecv的方式。解決方案雖然有,就是對(duì)發(fā)送的包加上編號(hào)header,在工作器線程中保存200字節(jié)的時(shí)候根據(jù)編號(hào)來(lái)處理.
1。IOCP一般只用于“響應(yīng)socket的recv消息”,發(fā)送數(shù)據(jù)時(shí),其線程載體未必是IOCP線程,建議使用其它線程池的線程(IOCP本質(zhì)上就是1個(gè)線程池,速度稍微快一點(diǎn)而已)來(lái)處理業(yè)務(wù)邏輯;
2。IOCP照樣可用于“長(zhǎng)連接socket”,無(wú)論是“大數(shù)量的并發(fā)訪問(wèn)”還是“長(zhǎng)時(shí)間的事務(wù)處理”,只要在收到數(shù)據(jù)之后,把數(shù)據(jù)投遞到另外1個(gè)線程池里處理,而IOCP就立即返回了,不會(huì)造成任何socket層面的延遲和阻塞(即:轉(zhuǎn)換處理事務(wù)的線程載體)。本人在win2k下測(cè)試過(guò)最高4000個(gè)并發(fā)連接(P2-350,28M);
3。我在項(xiàng)目里寫(xiě)的服務(wù)器,用3個(gè)線程池:第1線程池是IOCP,處理recv消息;第2線程池是普通線程池,處理程序事務(wù);第3線程池是普通線程池,用來(lái)send網(wǎng)絡(luò)數(shù)據(jù);
--------------
我一般把你第三個(gè)線程池和第一個(gè)重合起來(lái),這樣效率會(huì)更高。
我來(lái)個(gè)總結(jié):
1,我會(huì)創(chuàng)建cpu*4的線程數(shù)處理,res,and send.
2.我會(huì)用cpu*8的線程數(shù)處理所有事務(wù)。
同時(shí)post cpu*4個(gè)accept,這樣同時(shí)最大的并行accept就會(huì)是cpu*4;
對(duì)于每個(gè)accpte的clinet post 1 read
接收的情況:
當(dāng)clienta收到一個(gè)完成通知的時(shí)候,處理buff,把結(jié)果(list)放到client結(jié)點(diǎn)中,post event(注意給clienta ->addref())給2,2派出一個(gè)idle thread to 處理clienta ,clienta->release().
N個(gè)client的時(shí)候,情況類是。
發(fā)送情況。
在2中業(yè)務(wù)和暴風(fēng)雨一樣復(fù)雜,而對(duì)于每個(gè)結(jié)點(diǎn)來(lái)說(shuō),都是single thread的,如果2沒(méi)有idle的線程,對(duì)于和結(jié)點(diǎn)clienta一樣的其他結(jié)點(diǎn)來(lái)說(shuō),他們的包結(jié)果來(lái)不級(jí)處理的時(shí)候,會(huì)list到自己的結(jié)點(diǎn)上。直到2有idle的線程來(lái)處理。但1中的線程還在快樂(lè)的接收的自己數(shù)據(jù)。
當(dāng)2中某個(gè)thread的接受到一個(gè)命的時(shí)候,比如要發(fā)送一個(gè)數(shù)據(jù),2 post event to 1,1從clienta中的list取出一個(gè)待發(fā)包,post write.這樣可以避免多個(gè)線程對(duì)同一個(gè)socket post wirte,大家都知道如果一個(gè)socket 在pending狀態(tài)時(shí),不能對(duì)同一個(gè)overlay發(fā)起兩個(gè)post write.我們之所以用一個(gè)Overlay而不用多個(gè),是為了程序著想,不需要太復(fù)雜。因?yàn)閷?duì)于同一個(gè)包,有可能需要post 多次。這樣,我們可以通過(guò)結(jié)點(diǎn)的記錄上次發(fā)送了多少byte.從而正確的從上次的offsize post.
業(yè)務(wù)處理上:
我一般不會(huì)把分析包的fuction放在io處理線程中,也就是iocp中,我會(huì)暴路一個(gè)接口類,比如一個(gè)pure class.這樣做,可以很好的把類分離,并且可以應(yīng)用到不同的應(yīng)用中。
從上面的分析可以看出,真正的執(zhí)行者肯定在2中,當(dāng)2有一個(gè)包分析完成的時(shí)候,我會(huì)post一個(gè)event給2,也就是自己發(fā)事件給自己。讓2分配出另一個(gè)線程來(lái)處理業(yè)務(wù),而原來(lái)的線程可以繼續(xù)分析包。2->2的情況,也不是一概而論,看具體的業(yè)務(wù)。但這樣做,就可以比較高效了。
我現(xiàn)在的處理方式上面,與樓上的有點(diǎn)不一樣.線程數(shù)量上面我不會(huì)固定,也就是不受CPU的多少而限制.但是會(huì)給出一個(gè)同步運(yùn)行線程數(shù)參考值.比如CPU*2.但是實(shí)際運(yùn)作過(guò)程當(dāng)中會(huì)由于有多個(gè)網(wǎng)絡(luò)事件到來(lái),而可能這個(gè)值會(huì)被耗光,也就是在某個(gè)時(shí)間里沒(méi)有線程處于GetQueuedCompletionStatue阻賽來(lái)處理下一個(gè)網(wǎng)絡(luò)事件,此時(shí)我就會(huì)考慮再添加一個(gè)例外線程,來(lái)做等待.由于多出了一個(gè)例外線程,所以可能已經(jīng)超過(guò)允許同時(shí)運(yùn)行的線程數(shù)量參考值,則哪一個(gè)線程完成任務(wù)后先做值檢測(cè),若超出則看是處于阻塞狀態(tài)的線程數(shù)是否達(dá)到兩個(gè),若是則自行結(jié)束(這時(shí)候會(huì)帶一個(gè)麻煩事情就是,該線程的退出會(huì)引發(fā)由其發(fā)出的I/O請(qǐng)求被取消,也會(huì)有一個(gè)網(wǎng)絡(luò)事件).
不過(guò)我發(fā)送時(shí)是直接使用WSASend拉交,也就是說(shuō)可能會(huì)有兩個(gè)線程同時(shí)工作于一個(gè)連接之上.只是當(dāng)有執(zhí)行WSASend時(shí),Sending計(jì)數(shù)加1,而下一輪需要發(fā)送時(shí),先檢測(cè)Sending計(jì)數(shù).如果有Sending,則直接把數(shù)據(jù)包附加到Sending隊(duì)列,如此也可以保證數(shù)據(jù)的順序性,同時(shí)不需要PostQueuedCompletionStatus,少一個(gè)切換過(guò)程,并且實(shí)現(xiàn)真正意義上的重疊IO,對(duì)于處理發(fā)送完成事件的線程,則可以一直發(fā)送WSASend到?jīng)]有待發(fā)數(shù)據(jù)為止,每一次完成通知?jiǎng)t扣減一個(gè)完成計(jì)數(shù),每提交一次WSASend則增加一個(gè)計(jì)數(shù).
由于有多個(gè)線程引用一個(gè)連接的上下文信息,如果在刪除時(shí)直接刪除,則會(huì)報(bào)錯(cuò),所以在需要設(shè)置刪除前,先置刪除標(biāo)識(shí),所有線程遇到該標(biāo)識(shí)將不再發(fā)送WSASend/WSARecv,另由一個(gè)檢測(cè)線程來(lái)對(duì)沒(méi)有(發(fā)送和接收)計(jì)數(shù)的數(shù)據(jù)進(jìn)行清理(記得closesocket先,以阻止所有后來(lái)的網(wǎng)絡(luò)事件,在下一次檢測(cè)過(guò)程當(dāng)中清理為其分配的資源,并刪除).
檢測(cè)線程主要有兩項(xiàng)工作,一是清理垃圾,二是針對(duì)不良連接,也就是在一段時(shí)間內(nèi)沒(méi)有任何I/O操作的連接進(jìn)行清理,也就是說(shuō)如果是自己的客戶端則最好是在沒(méi)有數(shù)據(jù)交互的情況下發(fā)送時(shí)脈信息,對(duì)于部分需要驗(yàn)證的服務(wù),也可以在此對(duì)超時(shí)未進(jìn)行身份驗(yàn)證的連接清理掉.
從我個(gè)人做過(guò)的項(xiàng)目中講,一個(gè)連接就應(yīng)該同時(shí)只會(huì)有1個(gè)r + 1個(gè)s 被投遞,否則出現(xiàn)工作線程恰逢時(shí)間片切換(多核CPU上尤其小心),網(wǎng)絡(luò)粘包半包時(shí),邏輯協(xié)議包的解析上極為困難。
所謂廣播消息,往往只是部分廣播(例如區(qū)域同步),這個(gè)由邏輯層決定需要對(duì)哪些session進(jìn)行消息發(fā)送就行了,socket層要做的只是給指定socket發(fā)送消息。
無(wú)論r/s,都應(yīng)該有自己的緩沖隊(duì)列,r的用于處理粘包半包,s的用于流量緩沖控制。
此外,在我自己的應(yīng)用中,網(wǎng)絡(luò)模塊(獨(dú)立進(jìn)程)收到完整數(shù)據(jù)(能成功解析出邏輯包),再使用namedpipe(也用IOCP)轉(zhuǎn)發(fā)給具體工作進(jìn)程,反向流程依然