上午發現一個神仙博客! 寫的東西很深刻,通俗易懂,轉載一下吧~ react
轉載自:blog.csdn.net/qq_41453285…git
感謝~github
TCP短鏈接
- **概念:**以下圖所示,客戶端與服務器創建鏈接開始通訊,一次/指定次數通訊結束以後就斷開本次TCP鏈接,當下次再次通訊時,再次創建TCP的連接
- **優勢:**不長期佔用服務器的內存,那麼服務器能處理的鏈接數量是比較多的
- 缺點:
- 由於等到要發送數據或者獲取資源時,纔去請求創建鏈接發送數據,不然就是端開鏈接的,那麼**若是服務器要想往客戶端發送數據時怎麼辦?**涼伴,沒有任何辦法,或者要等到下一次要請求數據時,才發送,好比咱們採用輪詢(30秒或者更長)拉取消息, 那麼服務器與客戶端通訊的實時性就喪失了
- 客戶端採用輪詢來實時獲取信息,或者說大量的客戶端使用短鏈接的方式通訊,那麼就浪費了大量的CPU和帶寬資源用於創建連 接和釋放鏈接,存在資源浪費,甚至是沒法創建鏈接。好比經典的http長輪詢(微信網頁客戶端端)
TCP長鏈接
- **概念:**以下圖所示,TCP與服務器創建鏈接以後一直處於鏈接狀態,直到最後再也不須要服務的時候才斷開鏈接
- 優勢:
- 傳輸數據快
- 服務器可以主動第一時間傳輸數據到客戶端
- 缺點:
- 由於客戶端與服務器一直保持這種鏈接,那麼在高併發分佈式集羣系統中客戶端數量會愈來愈多,佔 用不少的系統資源
- TCP自己是一種有狀態的數據,在高併發分佈式系統會致使後臺設計比較難作
Linux的相關內核參數
- **tcp_keepalive_time:**單位秒,表示發送探測報文以前的連接空閒時間,默認爲7200
- **tcp_keepalive_intvl:**單位秒,表示兩次探測報文發送的時間間隔,默認爲75
- **tcp_keepalive_probes:**表示探測的次數,默認爲9
Posix套接字選項
上面介紹的三個內核參數,在Linux編程中有對應的socket選項可使用編程
關於setsockopt()可參閱:blog.csdn.net/qq_41453285…緩存
int keepalive_time = 30;setsockopt(incomingsock, IPPROTO_TCP,TCP_KEEPIDLE,(void*)(&keepalive_time),(socklen_t)sizeof(keepalive_time)); int keepalive_intvl = 3;setsockopt(incomingsock, IPPROTO_TCP,TCP_KEEPINTVL,(void*)(&keepalive_intvl),(socklen_t)sizeof(keepalive_intvl)); int keepalive_probes= 3;setsockopt(incomingsock, IPPROTO_TCP,TCP_KEEPCNT,(void*)(&keepalive_probes),(socklen_t)sizeof(keepalive_probes));服務器
爲何須要長鏈接
- **服務器要主動發消息給客戶端:**若是沒有一個鏈接存在的話,服務器是永遠不能主動地找到客戶端的,那也就沒有辦法及時發送消息給客戶端
- **客戶端和服務器間頻繁地通訊:**若是是短鏈接,每次都須要創建鏈接才能發送消息,若是併發量稍高,可能就會出現大量的TIME_WAIT狀態的socket出現,也即後續創建不了鏈接
- **業務須要,好比客戶端掉線時服務器須要作一些處理:**好比清空他的緩存或者其它資源或者其它業務含義,好比QQ頭像顯示離線
TCP長鏈接設計時須要考慮到的問題
- **默認的tcp keep-alive超時時間太長:**默認是7200秒,也就是2個小時,固然是能夠修改的
- **socket proxy會讓tcp keep-alive失效:**全部的proxy應用只能轉發TCP的應用數據,作不到轉發TCP協議內部的包
- **移動網絡須要信令保活:**對於手機等智能終端來說,他們使用移動網絡來上網的,而運營商們爲了節約信道資源,會嘗試關閉超過60秒或者 45秒的沒有發送數據的socket
Netyy中的實現
編碼實現與主要原理
- Github源碼連接:github.com/dongyusheng…
- 代碼:
- 代碼就是用紅黑樹來管理定時器
- 總體框架採用Reactor模式實現
- 有相應的客戶端與服務端
- 大體思路爲:
- 咱們用一棵紅黑樹管理全部事件節點,其中key爲該事件套接字的超時時間,value爲套接字
- 調用wpoll_wait()處理全部事件,其中其最後一個參數爲超時時間,必須設置爲整棵紅黑樹中超時時間最短的那個節點的值
- 當epoll_wait()返回以後,將時間與節點的時間進行比較,若是超時了,那麼就作相應的處理
代碼解析
- 下面主要介紹以「服務端」爲第一視角,查看其如何對客戶端進行心跳檢測的
- ngx_reactor.c的ngx_reactor_loop()函數:
- Reactor的主要中心處理函數ngx_reactor_loop()中會在poll_wait()調用以前獲取一下當前時間
- 而後進行epoll_wait(),其最後一個參數爲紅黑樹中超時時間最短的那個節點的時間值
- epoll_wait()返回以後,先更新一下當前時間(全局變量ngx_current_msec)
- 最後再次獲取當前時間,而後與以前的舊的「當前時間」進行,相減獲得差值,若是超時了,那麼就調用ngx_event_expire_timers()函數
- ngx_reactor.c的ngx_event_expire_timers()函數:
- 該函數也就是該封裝事件的what標誌|上一個NGX_EVENT_TIMEOUT,將該事件置位超時了的
- 而後會調用該事件的回調函數
- 在上面的回調函數中,咱們爲其綁定的回調函數爲ngx_server.c中的client_handler()函數:
- 該函數會判斷該事件的what標誌是否有NGX_EVENT_TIMEOUT標誌,若是有就調用timeout_cb()處理函數
- timeout_cb()函數或判斷delta是否超過了KEEPALIVE_INTVAL_MSEC時間,而且超時了3次(KEEPALIVE_INTVAL_PROBES),若是是,那麼就調用close()關閉客戶端的套接字
- 上面定義的宏以下所示