1. 程式人生 > >linux 高併發socket通訊模型

linux 高併發socket通訊模型

------select

1 一個誤區很多人認為它最大可以監聽1024個,實際上卻是檔案描述符的值不能大於等於1024,所以除掉標準輸入、輸出、錯誤輸出,一定少於1024個,如果在之前還打開了其他檔案,那會更少

2 select返回後,一般要輪詢fd_set,發現新連線要加上,連線斷開要去掉,這個過程一定要這樣做:select之前把fd_set臨時拷貝一份,輪詢中對它的修改只在臨時fd_set上做,輪詢完了,再對這個臨時fd_set select,否則你可能明明有連線進來,卻accept不到,這可能是因為輪詢中如果直接修改fdset,select的底層就會定位錯亂

------poll

效能測試發現,select與poll有相似的呼叫時間與cpu佔用率,都隨著資料量變大或者連線數變大(活動連線不變)而變大

連線進入時,返回POLLIN,連線關閉時返回POLLERR 或者 POLLIN

------epoll

正如傳說的那樣,epoll的呼叫時間與cpu佔用率只會隨資料量變大,而幾乎不受連線數影響

當連線關閉時,會收到EPOLLIN事件

在ET模式下,不管是監聽socket還是連線客戶端的socket,在EPOLLIN時,都應該重複read一直到EAGAIN(多次連線進入或者客戶端的多次send呼叫都只產生一次EPOLL事件),否則下次等待EPOLLIN將會掛起,這樣對上層應用處理起來更復雜

所以還是推薦用預設的LT模式

在對客戶端的傳送也可能出現阻塞,所以epoll也應該註冊EPOLLOUT,但不是在一開始(那會讓所有檔案描述符都返回可用,降低epoll的效率,合理的機制應該是這樣:對accecpt的客戶端連線一開始只註冊EPOLLIN事件,觸發後接收客戶端訊息,生成回覆,將回復放到一個程式自己的緩衝區內,修改該檔案描述符的註冊事件為EPOLLIN|EPOLLOUT(視業務邏輯而定,如果要求必須應答傳送之前不能接收請求,可只註冊EPOLLOUT事件),當EPOLLOUT觸發時,將回復發送出去,從緩衝區中刪除回覆,再修改該連線為註冊EPOLLIN事件

即使在單執行緒程式中(執行在家用筆記本的虛擬機器上),在3萬個連線的1萬個活動連線上,epoll也可以一秒內收發100MB資料(已經接近於Gbit網絡卡的理論上限),所以如果沒有其它的IO活動或者計算處理,單執行緒的epoll完全可以應付高併發socket通訊

如果連線爆發,比如一秒1萬個,epoll server會在10+秒內accept完,沒必要擔心它accept過慢,因為當監聽佇列不足時,tcp會忽略客戶端的SYN報文,這樣客戶端就會重傳,只要給客戶端設定一個合適的超時時間,例如15妙,epoll server處理每秒10000個新加連線沒有問題

-----一般處理模型

生產者消費者模式,一個執行緒單獨負責從監聽socket上accept,它收到新連線後,加鎖放入公共buffer,若干個工作執行緒加鎖從公共buffer上取得連線,加入自己的epoll等待集中,等待一定的時間,有資料則進行收發,沒資料繼續從公共buffer上取連線,但是這裡並不適用線上程間用條件變數通知,因為即使公共buffer上沒有新連線,工作執行緒也不應該等待accept執行緒通知,而是應立即用epoll wait自己已有的連線

不能採用多個執行緒自主搶佔連線的方式,資料在不同連線上是不均勻的,如果一些連線現在資料量現在過大,就會得到很少的新連線,以後又會出現資料飢餓,而那些當時搶佔到過多連線的執行緒以後則會壓力過大,處理變慢。應該由單獨執行緒,例如負責accept的執行緒,分配到每個執行緒自己的連線佇列中等待處理,另外,每個處理執行緒都採用LT模式,每個活動連線上輪流接收一次訊息,然後就取回佇列中的新連線,如果採用ET模式,就可能一直忙於在舊連線上收發資料,而冷落新連線。

公司的網路備份軟體,採用的是poll/select模型,因為客戶端一旦執行備份/恢復任務,在連線就一定有資料收發任務,這種情況下,epoll不能加快效能

對於某些輸入io只有一路的程式,資料接收執行緒 + circle buffer + 資料處理執行緒是一個比較簡單的模型

上面的方案仍然造成資料量的執行緒處理不過來,資料量小的執行緒又很空閒,應該採用如下方案

主執行緒內用epoll接收資料和accept新連線,並解析出訊息,放入佇列中讓所有的執行緒去搶,至於如何多個執行緒同時對一個連線傳送訊息,可以採用與dedupe中多執行緒處理FP cache(一個hash table)的方案類似,分配與執行緒數目相同的鎖,當處理完訊息需要傳送時,將連線的檔案描述符數除以執行緒數目,餘是多少,就加鎖哪個鎖,這樣,多個執行緒能儘量分配到不同的鎖上增加併發性,而對同一個連線加同一個鎖進行互斥的傳送

另外,這還需要處理SIGPIPE訊息,以免前面一個執行緒關閉了連線,另一個執行緒又去傳送,產生SIGPIPE訊號,使程序exit

原文:http://blog.csdn.net/piaoairy219/article/details/17398545