linux socket高性能服务器处理框架
這個(gè)博客很多東西
http://blog.csdn.net/luozhonghua2014/article/details/37041765
?
思考一種高性能的服務(wù)器處理框架
1、首先需要一個(gè)內(nèi)存池,目的在于:
·減少頻繁的分配和釋放,提高性能的同時(shí),還能避免內(nèi)存碎片的問(wèn)題;
·能夠存儲(chǔ)變長(zhǎng)的數(shù)據(jù),不要很傻瓜地只能預(yù)分配一個(gè)最大長(zhǎng)度;
·基于SLAB算法實(shí)現(xiàn)內(nèi)存池是一個(gè)好的思路:分配不同大小的多個(gè)塊,請(qǐng)求時(shí)返回大于請(qǐng)求長(zhǎng)度的最小塊即可,對(duì)于容器而言,處理固定塊的分配和回收,相當(dāng) 容易實(shí)現(xiàn)。當(dāng)然,還要記得需要設(shè)計(jì)成線程安全的,自旋鎖比較好,使用讀寫(xiě)自旋鎖就更好了。
·分配內(nèi)容的增長(zhǎng)管理是一個(gè)問(wèn)題,比如第一次需要1KB空間,隨著數(shù)據(jù)源源不斷的寫(xiě)入,第二次就需要4KB空間了。擴(kuò)充空間容易實(shí)現(xiàn),可是擴(kuò)充的時(shí)候必然 涉及數(shù)據(jù)拷貝。甚至,擴(kuò)充的需求很大,上百兆的數(shù)據(jù),這樣就不好辦了。暫時(shí)沒(méi)更好的想法,可以像STL一樣,指數(shù)級(jí)增長(zhǎng)的分配策略,拷貝數(shù)據(jù)雖不可避免, 但是起碼重分配的幾率越來(lái)越小了。
·上面提到的,如果是上百兆的數(shù)據(jù)擴(kuò)展需要,采用內(nèi)存映射文件來(lái)管理是一個(gè)好的辦法:映射文件后,雖然占了很大的虛擬內(nèi)存,但是物理內(nèi)存僅在寫(xiě)入的時(shí)候才 會(huì)被分配,加上madvice()來(lái)加上順序?qū)懙膬?yōu)化建議后,物理內(nèi)存的消耗也會(huì)變小。
·用string或者vector去管理內(nèi)存并不明智,雖然很簡(jiǎn)單,但服務(wù)器軟件開(kāi)發(fā)中不適合使用STL,特別是對(duì)穩(wěn)定性和性能要求很高的情況下。
2、第二個(gè)需要考慮的是對(duì)象池,與內(nèi)存池類似:
·減少對(duì)象的分配和釋放。其實(shí)C++對(duì)象也就是struct,把構(gòu)造和析構(gòu)脫離出來(lái)手動(dòng)初始化和清理,保持對(duì)同一個(gè)緩沖區(qū)的循環(huán)利用,也就不難了。
·可以設(shè)計(jì)為一個(gè)對(duì)象池只能存放一種對(duì)象,則對(duì)象池的實(shí)現(xiàn)實(shí)際就是固定內(nèi)存塊的池化管理,非常簡(jiǎn)單。畢竟,對(duì)象的數(shù)量非常有限。
3、第三個(gè)需要的是隊(duì)列:
·如果可以預(yù)料到極限的處理能力,采用固定大小的環(huán)形隊(duì)列來(lái)作為緩沖區(qū)是比較不錯(cuò)的。一個(gè)生產(chǎn)者一個(gè)消費(fèi)者是常見(jiàn)的應(yīng)用場(chǎng)景,環(huán)形隊(duì)列有其經(jīng)典的“鎖無(wú) 關(guān)”算法,在一個(gè)線程讀一個(gè)線程寫(xiě)的場(chǎng)景下,實(shí)現(xiàn)簡(jiǎn)單,性能還高,還不涉及資源的分配和釋放。好啊,實(shí)在是好!
·涉及多個(gè)生產(chǎn)者消費(fèi)者的時(shí)候,tbb::concurent_queue是不錯(cuò)的選擇,線程安全,并發(fā)性也好,就是不知道資源的分配釋放是否也管理得足 夠好。
4、第四個(gè)需要的是映射表,或者說(shuō)hash表:
·因?yàn)閑poll是事件觸發(fā)的,而一系列的流程可能是分散在多個(gè)事件中的,因此,必須保留下中間狀態(tài),使得下一個(gè)事件觸發(fā)的時(shí)候,能夠接著上次處理的位置 繼續(xù)處理。要簡(jiǎn)單的話,STL的hash_map還行,不過(guò)得自己處理鎖的問(wèn)題,多線程環(huán)境下使用起來(lái)很麻煩。
·多線程環(huán)境下的hash表,最好的還是tbb::concurent_hash_map。
5、核心的線程是事件線程:
·事件線程是調(diào)用epoll_wait()等待事件的線程。例子代碼里面,一個(gè)線程干了所有的事情,而需要開(kāi)發(fā)一個(gè)高性能的服務(wù)器的時(shí)候,事件線程應(yīng)該專 注于事件本身的處理,將觸發(fā)事件的socket句柄放到對(duì)應(yīng)的處理隊(duì)列中去,由具體的處理線程負(fù)責(zé)具體的工作。
6、accept()單獨(dú)一個(gè)線程:
·服務(wù)端的socket句柄(就是調(diào)用bind()和listen()的這個(gè))最好在單獨(dú)的一個(gè)線程里面做accept(),阻塞還是非阻塞都無(wú)所謂,相 比整個(gè)服務(wù)器的通訊,用戶接入的動(dòng)作只是很小一部分。而且,accept()不放在事件線程的循環(huán)里面,減少了判斷。
7、接收線程單獨(dú)一個(gè):
·接收線程從發(fā)生EPOLLIN事件的隊(duì)列中取出socket句柄,然后在這個(gè)句柄上調(diào)用recv接收數(shù)據(jù),直到緩沖區(qū)沒(méi)有數(shù)據(jù)為止。接收到的數(shù)據(jù)寫(xiě)入以 socket為鍵的hash表中,hash表中有一個(gè)自增長(zhǎng)的緩沖區(qū),保存了客戶端發(fā)過(guò)來(lái)的數(shù)據(jù)。
·這樣的處理方式適合于客戶端發(fā)來(lái)的數(shù)據(jù)很小的應(yīng)用,比如HTTP服務(wù)器之類;假設(shè)是文件上傳的服務(wù)器,則接受線程會(huì)一直處理某個(gè)連接的海量數(shù)據(jù),其他客 戶端的數(shù)據(jù)處理產(chǎn)生了饑餓。所以,如果是文件上傳服務(wù)器一類的場(chǎng)景,就不能這樣設(shè)計(jì)。
8、發(fā)送線程單獨(dú)一個(gè):
·發(fā)送線程從發(fā)送隊(duì)列獲取需要發(fā)送數(shù)據(jù)的SOCKET句柄,在這些句柄上調(diào)用send()將數(shù)據(jù)發(fā)到客戶端。隊(duì)列中指保存了SOCKET句柄,具體的信息 還需要通過(guò)socket句柄在hash表中查找,定位到具體的對(duì)象。如同上面所講,客戶端信息的對(duì)象不但有一個(gè)變長(zhǎng)的接收數(shù)據(jù)緩沖區(qū),還有一個(gè)變長(zhǎng)的發(fā)送 數(shù)據(jù)緩沖區(qū)。具體的工作線程發(fā)送數(shù)據(jù)的時(shí)候并不直接調(diào)用send()函數(shù),而是將數(shù)據(jù)寫(xiě)到發(fā)送數(shù)據(jù)緩沖區(qū),然后把SOCKET句柄放到發(fā)送線程隊(duì)列。
·SOCKET句柄放到發(fā)送線程隊(duì)列的另一種情況是:事件線程中發(fā)生了EPOLLOUT事件,說(shuō)明TCP的發(fā)送緩沖區(qū)又有了可用的空間,這個(gè)時(shí)候可以把 SOCKET句柄放到發(fā)送線程隊(duì)列,一邊觸發(fā)send()的調(diào)用;
·需要注意的是:發(fā)送線程發(fā)送大量數(shù)據(jù)的時(shí)候,當(dāng)頻繁調(diào)用send()直到TCP的發(fā)送緩沖區(qū)滿后,便無(wú)法再發(fā)送了。這個(gè)時(shí)候如果循環(huán)等待,則其他用戶的 發(fā)送工作受到影響;如果不繼續(xù)發(fā)送,則EPOLL的ET模式可能不會(huì)再產(chǎn)生事件。解決這個(gè)問(wèn)題的辦法是在發(fā)送線程內(nèi)再建立隊(duì)列,或者在用戶信息對(duì)象上設(shè)置 標(biāo)志,等到線程空閑的時(shí)候,再去繼續(xù)發(fā)送這些未發(fā)送完成的數(shù)據(jù)。
9、需要一個(gè)定時(shí)器線程:
·一位將epoll使用的高手說(shuō)道:“單純靠epoll來(lái)管理描述符不泄露幾乎是不可能的。完全解決方案很簡(jiǎn)單,就是對(duì)每個(gè)fd設(shè)置超時(shí)時(shí)間,如果超過(guò) timeout的時(shí)間,這個(gè)fd沒(méi)有活躍過(guò),就close掉”。
·所以,定時(shí)器線程定期輪訓(xùn)整個(gè)hash表,檢查socket是否在規(guī)定的時(shí)間內(nèi)未活動(dòng)。未活動(dòng)的SOCKET認(rèn)為是超時(shí),然后服務(wù)器主動(dòng)關(guān)閉句柄,回收 資源。
10、多個(gè)工作線程:
·工作線程由接收線程去觸發(fā):每次接收線程收到數(shù)據(jù)后,將有數(shù)據(jù)的SOCKET句柄放入一個(gè)工作隊(duì)列中;工作線程再?gòu)墓ぷ麝?duì)列獲取SOCKET句柄,查詢 hash表,定位到用戶信息對(duì)象,處理業(yè)務(wù)邏輯。
·工作線程如果需要發(fā)送數(shù)據(jù),先把數(shù)據(jù)寫(xiě)入用戶信息對(duì)象的發(fā)送緩沖區(qū),然后把SOCKET句柄放到發(fā)送線程隊(duì)列中去。
·對(duì)于任務(wù)隊(duì)列,接收線程是生產(chǎn)者,多個(gè)工作線程是消費(fèi)者;對(duì)于發(fā)送線程隊(duì)列,多個(gè)工作線程是生產(chǎn)者,發(fā)送線程是消費(fèi)者。在這里需要注意鎖的問(wèn)題,如果采 用tbb::concurrent_queue,會(huì)輕松很多。
11、僅僅只用scoket句柄作為hash表的鍵,并不夠:
·假設(shè)這樣一種情況:事件線程剛把某SOCKET因發(fā)生EPOLLIN事件放入了接收隊(duì)列,可是隨即客戶端異常斷開(kāi)了,事件線程又因?yàn)镋POLLERR事 件刪除了hash表中的這一項(xiàng)。假設(shè)接收隊(duì)列很長(zhǎng),發(fā)生異常的SOCKET還在隊(duì)列中,等到接收線程處理到這個(gè)SOCKET的時(shí)候,并不能通過(guò) SOCKET句柄索引到hash表中的對(duì)象。
·索引不到的情況也好處理,難點(diǎn)就在于,這個(gè)SOCKET句柄立即被另一個(gè)客戶端使用了,接入線程為這個(gè)SCOKET建立了hash表中的某個(gè)對(duì)象。此 時(shí),句柄相同的兩個(gè)SOCKET,其實(shí)已經(jīng)是不同的兩個(gè)客戶端了。極端情況下,這種情況是可能發(fā)生的。
·解決的辦法是,使用socket fd + sequence為hash表的鍵,sequence由接入線程在每次accept()后將一個(gè)整型值累加而得到。這樣,就算SOCKET句柄被重用,也 不會(huì)發(fā)生問(wèn)題了。
12、監(jiān)控,需要考慮:
·框架中最容易出問(wèn)題的是工作線程:工作線程的處理速度太慢,就會(huì)使得各個(gè)隊(duì)列暴漲,最終導(dǎo)致服務(wù)器崩潰。因此必須要限制每個(gè)隊(duì)列允許的最大大小,且需要 監(jiān)視每個(gè)工作線程的處理時(shí)間,超過(guò)這個(gè)時(shí)間就應(yīng)該采用某個(gè)辦法結(jié)束掉工作線程。
對(duì)于linux socket與epoll配合相關(guān)的一些心得記錄 2008-07-29 17:57
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
1、通過(guò)上面語(yǔ)句可以簡(jiǎn)單設(shè)置緩沖區(qū)大小,測(cè)試證明:跟epoll結(jié)合的時(shí)候只有當(dāng) 單次發(fā)送的數(shù)據(jù)全被從緩沖區(qū)讀完畢之后才會(huì)再次被觸發(fā),多次發(fā)送數(shù)據(jù)如果沒(méi)有 讀取完畢當(dāng)緩沖區(qū)未滿的時(shí)候數(shù)據(jù)不會(huì)丟失,會(huì)累加到后面。
2、 如果緩沖區(qū)未滿,同一連接多次發(fā)送數(shù)據(jù)會(huì)多次收到EPOLLIN事件。 單次發(fā)送數(shù)據(jù)>socket緩沖區(qū)大小的數(shù)據(jù)數(shù)據(jù)會(huì)被阻塞分次發(fā)送,所以循環(huán)接收可 以用ENLIGE錯(cuò)誤判斷。
3、如果緩沖區(qū)滿,新發(fā)送的數(shù)據(jù)不會(huì)觸發(fā)epoll事件(也無(wú)異常),每次recv 都會(huì)為緩沖區(qū)騰出空間,只有當(dāng)緩沖區(qū)空閑大小能夠再次接收數(shù)據(jù)epollIN事件可 以再次被觸發(fā) 接收時(shí)接收大小為0表示客戶端斷開(kāi)(不可能有0數(shù)據(jù)包觸發(fā)EPOLLIN),-1表示異 常,針對(duì)errorno進(jìn)行判斷可以確定是合理異常還是需要終止的異常,>0而不等于 緩沖區(qū)大小表示單次發(fā)送結(jié)束。
4、 如果中途臨時(shí)調(diào)整接收緩存區(qū)大小,并且在上一次中數(shù)據(jù)沒(méi)有完全接收到 用戶空間,數(shù)據(jù)不會(huì)丟失,會(huì)累加在一起 所以總結(jié)起來(lái),系統(tǒng)對(duì)于數(shù)據(jù)的完整性還是做了相當(dāng)?shù)谋U?#xff0c;至于穩(wěn)定性沒(méi)有作更 深一步的測(cè)試
新增加:
5、如果主accept監(jiān)聽(tīng)的soctet fd也設(shè)置為非阻塞,那么單純靠epoll事件來(lái)驅(qū) 動(dòng)的服務(wù)器模型會(huì)存在問(wèn)題,并發(fā)壓力下發(fā)現(xiàn),每次accept只從系統(tǒng)中取得第一 個(gè),所以如果恰馮多個(gè) 連接同時(shí)觸發(fā)server fd的EPOLLIN事件,在返回的event數(shù) 組中體現(xiàn)不出來(lái),會(huì)出現(xiàn)丟失事件的現(xiàn)象,所以當(dāng)用ab等工具簡(jiǎn)單的壓載就會(huì)發(fā)現(xiàn) 每次都會(huì)有最后幾條信息得 不到處理,原因就在于此,我現(xiàn)在的解決辦法是將 server fd的監(jiān)聽(tīng)去掉,用一個(gè)線程阻塞監(jiān)聽(tīng),accept成功就處理檢測(cè)client fd, 然后在主線程循環(huán)監(jiān)聽(tīng)client事件,這樣epoll在邊緣模式下出錯(cuò)的概率就小,測(cè) 試表明效果明顯
6、對(duì)于SIG部分信號(hào)還是要做屏蔽處理,不然對(duì)方socket中斷等正常事件都會(huì)引起 整個(gè)服務(wù)的退出
7、sendfile(fd, f->SL->sendBuffer.inFd, (off_t *)&f->SL->sendBuffer.offset, size_need);注意sendfile函數(shù)的地三個(gè)變量是傳 送地址,偏移量會(huì)自動(dòng)增加,不需要手動(dòng)再次增加,否則就會(huì)出現(xiàn)文件傳送丟失現(xiàn)象
8、單線程epoll驅(qū)動(dòng)模型誤解:以前我一直認(rèn)為單線程是無(wú)法處理web服務(wù)器這樣 的有嚴(yán)重網(wǎng)絡(luò)延遲的服務(wù),但nginx等優(yōu)秀服務(wù)器都是機(jī)遇事件驅(qū)動(dòng) 模型,開(kāi)始我 在些的時(shí)候也是擔(dān)心這些問(wèn)題,后來(lái)測(cè)試發(fā)現(xiàn),當(dāng)client socket設(shè)為非阻塞模式 的時(shí)候,從讀取數(shù)據(jù)到解析http協(xié)議,到發(fā)送數(shù)據(jù)均在epoll的驅(qū)動(dòng)下速度非常 快,沒(méi)有必要采用多線程,我的單核 cpu(奔三)就可以達(dá)到 10000page/second,這在公網(wǎng)上是遠(yuǎn)遠(yuǎn)無(wú)法達(dá)到的一個(gè)數(shù)字(網(wǎng)絡(luò)延遲更為嚴(yán) 重),所以單線程的數(shù)據(jù)處理能力已經(jīng)很 高了,就不需要多線程了,所不同的是 你在架構(gòu)服務(wù)器的時(shí)候需要將所有阻塞的部分拆分開(kāi)來(lái),當(dāng)epoll通知你可以讀取 的時(shí)候,實(shí)際上部分?jǐn)?shù)據(jù)已經(jīng)到了 socket緩沖區(qū),你所讀取用的事件是將數(shù)據(jù)從 內(nèi)核空間拷貝到用戶空間,同理,寫(xiě)也是一樣的,所以epoll重要的地方就是將這 兩個(gè)延時(shí)的部分做了類似 的異步處理,如果不需要處理更為復(fù)雜的業(yè)務(wù),那單線 程足以滿足1000M網(wǎng)卡的最高要求,這才是單線程的意義。 我以前構(gòu)建的web服務(wù)器就沒(méi)有理解epoll,采用epoll的邊緣觸發(fā)之程處后怕事件 丟失,或者單線理阻塞,所以自己用多線程構(gòu)建了一個(gè)任務(wù)調(diào)度器, 所有收 到的事件統(tǒng)統(tǒng)壓進(jìn)任無(wú)調(diào)度器中,然后多任務(wù)處理,我還將read和write分別用兩 個(gè)調(diào)度器處理,并打算如果中間需要特殊的耗時(shí)的處理就增加一套 調(diào)度器,用少量線程+epoll的方法來(lái)題高性能,后來(lái)發(fā)現(xiàn)read和write部分調(diào)度器是多余 的,epoll本來(lái)就是一個(gè)事件調(diào)度器,在后面再次緩存 事件分部處理還不如將 epoll設(shè)為水平模式,所以多此一舉,但是這個(gè)調(diào)度起還是有用處的 上面講到如果中間有耗時(shí)的工作,比如數(shù)據(jù)庫(kù)讀寫(xiě),外部資源請(qǐng)求(文 件,socket)等這些操作就不能阻塞在主線程里面,所以我設(shè)計(jì)的這個(gè)任務(wù)調(diào)度器 就有 用了,在epoll能處理的事件驅(qū)動(dòng)部分就借用epoll的,中間部分采用模塊化 的設(shè)計(jì),用函數(shù)指針達(dá)到面相對(duì)象語(yǔ)言中的“委托”的作用,就可以滿足不同 的需 要將任務(wù)(fd標(biāo)識(shí))加入調(diào)度器,讓多線程循環(huán)執(zhí)行,如果中間再次遇到阻塞就會(huì) 再次加入自定義的阻塞器,檢測(cè)完成就加入再次存入調(diào)度器,這樣就可以將 多種 復(fù)雜的任務(wù)劃分開(kāi)來(lái),相當(dāng)于在處理的中間環(huán)節(jié)在自己購(gòu)置一個(gè)類似于epoll的事 件驅(qū)動(dòng)器
9、多系統(tǒng)兼容:我現(xiàn)在倒是覺(jué)得與其構(gòu)建一個(gè)多操作系統(tǒng)都支持的服務(wù)器不 如構(gòu)建特定系統(tǒng)的,如果想遷移再次改動(dòng),因?yàn)橐坏┘骖櫟蕉鄠€(gè)系統(tǒng)的化會(huì)大大增 加系 統(tǒng)的復(fù)雜度,并且不能最優(yōu)性能,每個(gè)系統(tǒng)都有自己的獨(dú)有的優(yōu)化選項(xiàng),所 以我覺(jué)得遷移的工作量遠(yuǎn)遠(yuǎn)小于兼顧的工作量
10模塊化編程,雖然用c還是要講求一些模塊化的設(shè)計(jì)的,我現(xiàn)在才發(fā)現(xiàn)幾乎面相 對(duì)想的語(yǔ)言所能實(shí)現(xiàn)的所有高級(jí)特性在c里面幾乎都有對(duì)應(yīng)的解決辦法(暫時(shí)發(fā)現(xiàn) 除了操作符重載),所有學(xué)過(guò)高級(jí)面相對(duì)象的語(yǔ)言的朋友不放把模式用c來(lái)實(shí)現(xiàn), 也是一種樂(lè)趣,便于維護(hù)和自己閱讀
11、養(yǎng)成注釋的好習(xí)慣
setsockopt -設(shè)置socket
?
1.closesocket(一般不會(huì)立即關(guān)閉而經(jīng)歷TIME_WAIT的過(guò)程)后想繼續(xù)重用該socket:
BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
2.?如果要已經(jīng)處于連接狀態(tài)的soket在調(diào)用closesocket后強(qiáng)制關(guān)閉,不經(jīng)歷
TIME_WAIT的過(guò)程:
BOOL bDontLinger = FALSE;
setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
3.在send(),recv()過(guò)程中有時(shí)由于網(wǎng)絡(luò)狀況等原因,發(fā)收不能預(yù)期進(jìn)行,而設(shè)置收發(fā)時(shí)限:
int nNetTimeout=1000;//1秒
//發(fā)送時(shí)限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收時(shí)限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
4.在send()的時(shí)候,返回的是實(shí)際發(fā)送出去的字節(jié)(同步)或發(fā)送到socket緩沖區(qū)的字節(jié)
(異步);系統(tǒng)默認(rèn)的狀態(tài)發(fā)送和接收一次為8688字節(jié)(約為8.5K);在實(shí)際的過(guò)程中發(fā)送數(shù)據(jù)
和接收數(shù)據(jù)量比較大,可以設(shè)置socket緩沖區(qū),而避免了send(),recv()不斷的循環(huán)收發(fā):
// 接收緩沖區(qū)
int nRecvBuf=32*1024;//設(shè)置為32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//發(fā)送緩沖區(qū)
int nSendBuf=32*1024;//設(shè)置為32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
5.?如果在發(fā)送數(shù)據(jù)的時(shí),希望不經(jīng)歷由系統(tǒng)緩沖區(qū)到socket緩沖區(qū)的拷貝而影響
程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
6.同上在recv()完成上述功能(默認(rèn)情況是將socket緩沖區(qū)的內(nèi)容拷貝到系統(tǒng)緩沖區(qū)):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
7.一般在發(fā)送UDP數(shù)據(jù)報(bào)的時(shí)候,希望該socket發(fā)送的數(shù)據(jù)具有廣播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
8.在client連接服務(wù)器過(guò)程中,如果處于非阻塞模式下的socket在connect()的過(guò)程中可
以設(shè)置connect()延時(shí),直到accpet()被呼叫(本函數(shù)設(shè)置只有在非阻塞的過(guò)程中有顯著的
作用,在阻塞的函數(shù)調(diào)用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
如果在發(fā)送數(shù)據(jù)的過(guò)程中(send()沒(méi)有完成,還有數(shù)據(jù)沒(méi)發(fā)送)而調(diào)用了closesocket(),以前我們
一般采取的措施是"從容關(guān)閉"shutdown(s,SD_BOTH),但是數(shù)據(jù)是肯定丟失了,如何設(shè)置讓程序滿足具體
應(yīng)用的要求(即讓沒(méi)發(fā)完的數(shù)據(jù)發(fā)送出去后在關(guān)閉socket)?
struct linger {
u_short l_onoff;
u_short l_linger;
};
linger m_sLinger;
m_sLinger.l_onoff=1;//(在closesocket()調(diào)用,但是還有數(shù)據(jù)沒(méi)發(fā)送完畢的時(shí)候容許逗留)
// 如果m_sLinger.l_onoff=0;則功能和2.)作用相同;
m_sLinger.l_linger=5;//(容許逗留的時(shí)間為5秒)
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));
/
線程于進(jìn)程的好處在于:
方便通信,線程共享了代碼與數(shù)據(jù)空間,所以對(duì)共享空間提供了最原始的支持
可以用線程運(yùn)行完銷毀的方式而不需要回收線程資源,只要進(jìn)程退出,所有線程就銷毀了,不需要擔(dān)心有僵尸進(jìn)程的出現(xiàn),也就是資源不能回收的問(wèn)題。
對(duì)于并發(fā)比較高的服務(wù)器,并且每個(gè)處理時(shí)間又不是太長(zhǎng)的情況下,可以采用線程池的方式
在同等情況下,線程所占資源略少于進(jìn)程,因?yàn)榫€程在訪問(wèn)一共享變量時(shí),在物理內(nèi)存中僅有一份此變量所占空間,若是進(jìn)程間需要改寫(xiě)同一全局變量時(shí),此時(shí)就會(huì)產(chǎn)生“寫(xiě)時(shí)復(fù)制”,會(huì)產(chǎn)生兩份空間(對(duì)一個(gè)變量的改寫(xiě),會(huì)造成多占用大于等于4K的物理空間)
進(jìn)程于線程的好處在于:
不需要擔(dān)心太多因?yàn)樵L問(wèn)共享資源而造成的各種同步與互斥問(wèn)題,如果需要共享某部分內(nèi)容,需要走專用的進(jìn)程間通信手段,也就是說(shuō)對(duì)于共享空間是可控制的,不會(huì)出現(xiàn)隨機(jī)性
不用擔(dān)心一不小心就造成函數(shù)重入的問(wèn)題
在不同的進(jìn)程中,可以使用不同的ELF文件作為執(zhí)行體,當(dāng)然,在線程中也可以再進(jìn)行fork+execv來(lái)實(shí)現(xiàn)這種方案
無(wú)論是線程還是進(jìn)程,其調(diào)度方式是一樣的
長(zhǎng)連接,用poll/select/epoll做多路復(fù)用的方式優(yōu)缺點(diǎn):
由于不會(huì)造成多線程與多進(jìn)程,所以所有代碼都在一個(gè)執(zhí)行體內(nèi),都在同一調(diào)度單元中,節(jié)省了資源的開(kāi)銷,如內(nèi)存的占用,進(jìn)程切換的開(kāi)銷。
由于所有處理都在同一個(gè)調(diào)度單元內(nèi),也就是多個(gè)連接共用一個(gè)進(jìn)程的時(shí)間片,如果系統(tǒng)中還有很多其它優(yōu)先級(jí)較高進(jìn)程或者實(shí)時(shí)進(jìn)程,平均下來(lái)每個(gè)連接所占用的 CPU時(shí)間就較少,且如果一個(gè)連接處于死循環(huán)中,若不加其它控制,其它連接就永遠(yuǎn)得不到響應(yīng),也就是說(shuō)每個(gè)連接的響應(yīng)實(shí)時(shí)性會(huì)受到其它連接的影響。
如果對(duì)于每個(gè)連接的處理方式不同,會(huì)造成代碼的不好控制,因?yàn)闀?huì)有太多的邏輯判斷。
以上三種方式,各有優(yōu)缺點(diǎn),主要是樓主的需求,這三種方式并非是互斥的,可以交叉使用,靈活控制,從而最優(yōu)化你的軟件。當(dāng)然,如果并發(fā)連接達(dá)到2000個(gè) 以上,并發(fā)處理也達(dá)到幾百上千個(gè)以上(且每個(gè)處理過(guò)程會(huì)執(zhí)行很長(zhǎng)),那么推薦你采用分布式處理,單個(gè)PC機(jī)是無(wú)法承受這種負(fù)荷的(若使用專用服務(wù)器,性能 會(huì)好一些,這個(gè)相對(duì)限制會(huì)寬一些),至少會(huì)造成響應(yīng)時(shí)間過(guò)長(zhǎng)
、、、、、、、、、、、、、、、、、、、、、、、、、、、
設(shè)置套接口的選項(xiàng)。
???#include <winsock.h>
???int PASCAL FAR?setsockopt( SOCKET s, int level, int optname,
???const char FAR* optval, int optlen);
???s:標(biāo)識(shí)一個(gè)套接口的描述字。
???level:選項(xiàng)定義的層次;目前僅支持SOL_SOCKET和IPPROTO_TCP層次。
???optname:需設(shè)置的選項(xiàng)。
???optval:指針,指向存放選項(xiàng)值的緩沖區(qū)。
???optlen:optval緩沖區(qū)的長(zhǎng)度。
注釋:
setsockopt()函數(shù)用于任意類型、任意狀態(tài)套接口的設(shè)置選項(xiàng)值。盡管在不同協(xié)議層上存在選項(xiàng),但本函數(shù)僅定義了最高的“套接口”層次上的選項(xiàng)。選項(xiàng)影響套接口的操作,諸如加急數(shù)據(jù)是否在普通數(shù)據(jù)流中接收,廣播數(shù)據(jù)是否可以從套接口發(fā)送等等。
???有兩種套接口的選項(xiàng):一種是布爾型選項(xiàng),允許或禁止一種特性;另一種是整形或結(jié)構(gòu)選項(xiàng)。允許一個(gè)布爾型選項(xiàng),則將optval指向非零整形數(shù);禁止一個(gè)選 項(xiàng)optval指向一個(gè)等于零的整形數(shù)。對(duì)于布爾型選項(xiàng),optlen應(yīng)等于sizeof(int);對(duì)其他選項(xiàng),optval指向包含所需選項(xiàng)的整形數(shù) 或結(jié)構(gòu),而optlen則為整形數(shù)或結(jié)構(gòu)的長(zhǎng)度。SO_LINGER選項(xiàng)用于控制下述情況的行動(dòng):套接口上有排隊(duì)的待發(fā)送數(shù)據(jù),且 closesocket()調(diào)用已執(zhí)行。參見(jiàn)closesocket()函數(shù)中關(guān)于SO_LINGER選項(xiàng)對(duì)closesocket()語(yǔ)義的影響。應(yīng)用 程序通過(guò)創(chuàng)建一個(gè)linger結(jié)構(gòu)來(lái)設(shè)置相應(yīng)的操作特性:
???struct linger {
int l_onoff;
int l_linger;
???};
???為了允許SO_LINGER,應(yīng)用程序應(yīng)將l_onoff設(shè)為非零,將l_linger設(shè)為零或需要的超時(shí)值(以秒為單位),然后調(diào)用setsockopt()。為了允許SO_DONTLINGER(亦即禁止SO_LINGER),l_onoff應(yīng)設(shè)為零,然后調(diào)用setsockopt()。
???缺省條件下,一個(gè)套接口不能與一個(gè)已在使用中的本地地址捆綁(參見(jiàn)bind())。但有時(shí)會(huì)需要“重用”地址。因?yàn)槊恳粋€(gè)連接都由本地地址和遠(yuǎn)端地址的組 合唯一確定,所以只要遠(yuǎn)端地址不同,兩個(gè)套接口與一個(gè)地址捆綁并無(wú)大礙。為了通知WINDOWS套接口實(shí)現(xiàn)不要因?yàn)橐粋€(gè)地址已被一個(gè)套接口使用就不讓它與 另一個(gè)套接口捆綁,應(yīng)用程序可在bind()調(diào)用前先設(shè)置SO_REUSEADDR選項(xiàng)。請(qǐng)注意僅在bind()調(diào)用時(shí)該選項(xiàng)才被解釋;故此無(wú)需(但也無(wú) 害)將一個(gè)不會(huì)共用地址的套接口設(shè)置該選項(xiàng),或者在bind()對(duì)這個(gè)或其他套接口無(wú)影響情況下設(shè)置或清除這一選項(xiàng)。
???一個(gè)應(yīng)用程序可以通過(guò)打開(kāi)SO_KEEPALIVE選項(xiàng),使得WINDOWS套接口實(shí)現(xiàn)在TCP連接情況下允許使用“保持活動(dòng)”包。一個(gè)WINDOWS套 接口實(shí)現(xiàn)并不是必需支持“保持活動(dòng)”,但是如果支持的話,具體的語(yǔ)義將與實(shí)現(xiàn)有關(guān),應(yīng)遵守RFC1122“Internet主機(jī)要求-通訊層”中第 4.2.3.6節(jié)的規(guī)范。如果有關(guān)連接由于“保持活動(dòng)”而失效,則進(jìn)行中的任何對(duì)該套接口的調(diào)用都將以WSAENETRESET錯(cuò)誤返回,后續(xù)的任何調(diào)用 將以WSAENOTCONN錯(cuò)誤返回。
???TCP_NODELAY選項(xiàng)禁止Nagle算法。Nagle算法通過(guò)將未確認(rèn)的數(shù)據(jù)存入緩沖區(qū)直到蓄足一個(gè)包一起發(fā)送的方法,來(lái)減少主機(jī)發(fā)送的零碎小數(shù)據(jù) 包的數(shù)目。但對(duì)于某些應(yīng)用來(lái)說(shuō),這種算法將降低系統(tǒng)性能。所以TCP_NODELAY可用來(lái)將此算法關(guān)閉。應(yīng)用程序編寫(xiě)者只有在確切了解它的效果并確實(shí)需 要的情況下,才設(shè)置TCP_NODELAY選項(xiàng),因?yàn)樵O(shè)置后對(duì)網(wǎng)絡(luò)性能有明顯的負(fù)面影響。TCP_NODELAY是唯一使用IPPROTO_TCP層的選 項(xiàng),其他所有選項(xiàng)都使用SOL_SOCKET層。
???如果設(shè)置了SO_DEBUG選項(xiàng),WINDOWS套接口供應(yīng)商被鼓勵(lì)(但不是必需)提供輸出相應(yīng)的調(diào)試信息。但產(chǎn)生調(diào)試信息的機(jī)制以及調(diào)試信息的形式已超出本規(guī)范的討論范圍。
setsockopt()支持下列選項(xiàng)。其中“類型”表明optval所指數(shù)據(jù)的類型。
選項(xiàng) ???????類型???意義
SO_BROADCAST BOOL 允許套接口傳送廣播信息。
SO_DEBUG BOOL 記錄調(diào)試信息。
SO_DONTLINER BOOL 不要因?yàn)閿?shù)據(jù)未發(fā)送就阻塞關(guān)閉操作。設(shè)置本選項(xiàng)相當(dāng)于將SO_LINGER的l_onoff元素置為零。
SO_DONTROUTE BOOL 禁止選徑;直接傳送。
SO_KEEPALIVE BOOL 發(fā)送“保持活動(dòng)”包。
SO_LINGER struct linger FAR*???如關(guān)閉時(shí)有未發(fā)送數(shù)據(jù),則逗留。
SO_OOBINLINE BOOL 在常規(guī)數(shù)據(jù)流中接收帶外數(shù)據(jù)。
SO_RCVBUF int 為接收確定緩沖區(qū)大小。
SO_REUSEADDR BOOL 允許套接口和一個(gè)已在使用中的地址捆綁(參見(jiàn)bind())。
SO_SNDBUF int 指定發(fā)送緩沖區(qū)大小。
TCP_NODELAY BOOL 禁止發(fā)送合并的Nagle算法。
setsockopt()不支持的BSD選項(xiàng)有:
選項(xiàng)名 ???類型 意義
SO_ACCEPTCONN BOOL 套接口在監(jiān)聽(tīng)。
SO_ERROR int 獲取錯(cuò)誤狀態(tài)并清除。
SO_RCVLOWAT int 接收低級(jí)水印。
SO_RCVTIMEO int 接收超時(shí)。
SO_SNDLOWAT int 發(fā)送低級(jí)水印。
SO_SNDTIMEO int 發(fā)送超時(shí)。
SO_TYPE ????int 套接口類型。
IP_OPTIONS ???在IP頭中設(shè)置選項(xiàng)。
返回值:
???若無(wú)錯(cuò)誤發(fā)生,setsockopt()返回0。否則的話,返回SOCKET_ERROR錯(cuò)誤,應(yīng)用程序可通過(guò)WSAGetLastError()獲取相應(yīng)錯(cuò)誤代碼。
錯(cuò)誤代碼:
???WSANOTINITIALISED:在使用此API之前應(yīng)首先成功地調(diào)用WSAStartup()。
???WSAENETDOWN:WINDOWS套接口實(shí)現(xiàn)檢測(cè)到網(wǎng)絡(luò)子系統(tǒng)失效。
???WSAEFAULT:optval不是進(jìn)程地址空間中的一個(gè)有效部分。
???WSAEINPROGRESS:一個(gè)阻塞的WINDOWS套接口調(diào)用正在運(yùn)行中。
???WSAEINVAL:level值非法,或optval中的信息非法。
???WSAENETRESET:當(dāng)SO_KEEPALIVE設(shè)置后連接超時(shí)。
???WSAENOPROTOOPT:未知或不支持選項(xiàng)。其中,SOCK_STREAM類型的套接口不支持SO_BROADCAST選項(xiàng),SOCK_DGRAM 類型的套接口不支持SO_DONTLINGER 、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE選項(xiàng)。
???WSAENOTCONN:當(dāng)設(shè)置SO_KEEPALIVE后連接被復(fù)位。
???WSAENOTSOCK:描述字不是一個(gè)套接口。
參見(jiàn):
???bind(), getsockopt(), ioctlsocket(), socket(), WSAAsyncSelect().
總結(jié)
以上是生活随笔為你收集整理的linux socket高性能服务器处理框架的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: C/C++面试题—使用STL两个队列实现
- 下一篇: linux 其他常用命令