【转】网络编程常见问题总结
生活随笔
收集整理的這篇文章主要介紹了
【转】网络编程常见问题总结
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
網(wǎng)絡編程常見問題總結(jié)
這里對在網(wǎng)絡程序中遇到的一些問題進行了總結(jié), 這里主要針對的是我們常用的TCP socket相關的總結(jié), 可能會存在錯誤, 有任何問題歡迎大家提出. 對于網(wǎng)絡編程的更多詳細說明建議參考下面的書籍 《UNIX網(wǎng)絡編程》 《TCP/IP 詳解》 《Unix環(huán)境高級編程》<
div>
- 網(wǎng)絡編程常見問題總結(jié)
- 相關說明
- 非阻塞IO和阻塞IO
- 基本概念
- 設置
- 區(qū)別:
- 讀:
- 寫:
- 超時控制:
- 長連接和短連接的各種可能的問題及相應的處理
- 短連接:
- 長連接:
- 主要線程模型優(yōu)缺點和注意事項
- 最簡單的線程模型
- 生產(chǎn)者消費者模型
- 異步模型
- 對一些常見錯誤號的分析
- 非阻塞IO和阻塞IO
- FAQ
- 為什么網(wǎng)絡程序會沒有任何預兆的就退出了
- write出去的數(shù)據(jù), read的時候知道長度嗎?
- 如何查看和觀察句柄泄露問題
- 為什么socket寫錯誤,但用recv檢查依然成功?
- 為什么接收端失敗,但客戶端仍然是write成功
- 長連接的情況下出現(xiàn)了不同程度的延時
- TIME_WAIT有什么樣的影響?
- 什么情況下會出現(xiàn)CLOSE_WAIT狀態(tài)?
- 順序發(fā)送數(shù)據(jù),接收端出現(xiàn)亂序接收到的情況
- 連接偶爾出現(xiàn)超時有哪些可能
- listen的時候的backlog有什么影響
- 長連接和短連接混用是否會有問題?
- select, epoll使用上的注意
- 一個進程的socket句柄數(shù)只能是1024嗎?
- 用limit方式啟動,程序讀寫的時候出core?
- 一臺機器最多可以建立多少連接?
- 對于一個不存在的ip建立連接是超時還是馬上返回?
- 各種超時怎么設置?
- 程序啟動監(jiān)聽端口報端口被占用怎么回事?
- 反饋建議
- 相關說明
<
div>
相關說明
<
div>
非阻塞IO和阻塞IO
在網(wǎng)絡編程中對于一個網(wǎng)絡句柄會遇到阻塞IO和非阻塞IO的概念, 這里對于這兩種socket先做一下說明<
div>
基本概念
socket的阻塞模式意味著必須要做完IO操作(包括錯誤)才會返回。 非阻塞模式下無論操作是否完成都會立刻返回,需要通過其他方式來判斷具體操作是否成功。<
div>
設置
一般對于一個socket是阻塞模式還是非阻塞模式有兩種方式 fcntl設置和recv,send系列的參數(shù). fcntl函數(shù)可以將一個socket句柄設置成非阻塞模式: flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); 設置之后每次的對于sockfd的操作都是非阻塞的 recv, send函數(shù)的最后有一個flag參數(shù)可以設置成MSG_DONTWAIT臨時將sockfd設置為非阻塞模式,而無論原有是阻塞還是非阻塞。 recv(sockfd, buff, buff_size, MSG_DONTWAIT); send(scokfd, buff, buff_size, MSG_DONTWAIT);<
div>
區(qū)別:
<
div>
讀:
讀本質(zhì)來說其實不能是讀,在實際中, 具體的接收數(shù)據(jù)不是由這些調(diào)用來進行,是由于系統(tǒng)底層自動完成的,read也好,recv也好只負責把數(shù)據(jù)從底層緩沖copy到我們指定的位置. 對于讀來說(read, 或者 recv) ,在阻塞條件下如果沒有發(fā)現(xiàn)數(shù)據(jù)在網(wǎng)絡緩沖中會一直等待,當發(fā)現(xiàn)有數(shù)據(jù)的時候會把數(shù)據(jù)讀到用戶指定的緩沖區(qū),但是如果這個時候讀到的數(shù)據(jù)量比較少,比參數(shù) 中指定的長度要小,read并不會一直等待下去,而是立刻返回。read的原則是數(shù)據(jù)在不超過指定的長度的時候有多少讀多少,沒有數(shù)據(jù)就會一直等待。所以 一般情況下我們讀取數(shù)據(jù)都需要采用循環(huán)讀的方式讀取數(shù)據(jù),一次read完畢不能保證讀到我們需要長度的數(shù)據(jù),read完一次需要判斷讀到的數(shù)據(jù)長度再決定 是否還需要再次讀取。在非阻塞的情況下,read的行為是如果發(fā)現(xiàn)沒有數(shù)據(jù)就直接返回,如果發(fā)現(xiàn)有數(shù)據(jù)那么也是采用有多少讀多少的進行處理.對于讀而言, 阻塞和非阻塞的區(qū)別在于沒有數(shù)據(jù)到達的時候是否立刻返回. recv中有一個MSG_WAITALL的參數(shù) recv(sockfd, buff, buff_size, MSG_WAITALL), 在正常情況下 recv是會等待直到讀取到buff_size長度的數(shù)據(jù),但是這里的WAITALL也只是盡量讀全,在有中斷的情況下recv還是可能會 被打斷,造成沒有讀完指定的buff_size的長度。所以即使是采用recv + WAITALL參數(shù)還是要考慮是否需要循環(huán)讀取的問題,在實驗中對于多數(shù)情況下recv還是可以讀完buff_size,所以相應的性能會比直接read 進行循環(huán)讀要好一些。不過要注意的是這個時候的sockfd必須是處于阻塞模式下,否則WAITALL不能起作用。<
div>
寫:
寫的本質(zhì)也不是進行發(fā)送操作,而是把用戶態(tài)的數(shù)據(jù)copy到系統(tǒng)底層去,然后再由系統(tǒng)進行發(fā)送操作,返回成功只表示數(shù)據(jù)已經(jīng)copy到底層緩沖,而不表示數(shù)據(jù)以及發(fā)出,更不能表示對端已經(jīng)接收到數(shù)據(jù). 對于write(或者send)而言,在阻塞的情況是會一直等待直到write完全部的數(shù)據(jù)再返回.這點行為上與讀操作有所不同,究其原因主要 是讀數(shù)據(jù)的時候我們并不知道對端到底有沒有數(shù)據(jù),數(shù)據(jù)是在什么時候結(jié)束發(fā)送的,如果一直等待就可能會造成死循環(huán),所以并沒有去進行這方面的處理;而對于 write, 由于需要寫的長度是已知的,所以可以一直再寫,直到寫完.不過問題是write是可能被打斷造成write一次只write一部分數(shù)據(jù), 所以write的過程還是需要考慮循環(huán)write, 只不過多數(shù)情況下一次write調(diào)用就可能成功. 非阻塞寫的情況下,是采用可以寫多少就寫多少的策略.與讀不一樣的地方在于,有多少讀多少是由網(wǎng)絡發(fā)送的那一端是否有數(shù)據(jù)傳輸?shù)綖闃藴?#xff0c;但是對 于可以寫多少是由本地的網(wǎng)絡堵塞情況為標準的,在網(wǎng)絡阻塞嚴重的時候,網(wǎng)絡層沒有足夠的內(nèi)存來進行寫操作,這時候就會出現(xiàn)寫不成功的情況,阻塞情況下會盡 可能(有可能被中斷)等待到數(shù)據(jù)全部發(fā)送完畢, 對于非阻塞的情況就是一次寫多少算多少,沒有中斷的情況下也還是會出現(xiàn)write到一部分的情況.<
div>
超時控制:
對于網(wǎng)絡IO,我們一般情況下都需要超時機制來避免進行操作的線程被handle住, 經(jīng)典的做法就是采用select+非阻塞IO進行判斷, select在超時時間內(nèi)判斷是否可以讀寫操作,然后采用非堵塞讀寫,不過一般實現(xiàn)的時候讀操作不需要設置為非堵塞,上面已經(jīng)說過讀操作只有在沒有數(shù)據(jù) 的時候才會阻塞,select的判斷成功說明存在數(shù)據(jù),所以即使是阻塞讀在這種情況下也是可以做到非阻塞的效果,就沒有必要設置成非阻塞的情況了. 這部分的代碼可以參考ullib中ul_sreado_ms_ex和ul_swriteo_ms_ex. ullib中對于讀寫超時存在了多套的函數(shù), 這些說明參見下表( 在ullib的wiki中也有相關說明)| 接口 | 存在問題 | 是否推薦 | 備注 |
| ul_sread,ul_swrite | 沒有超時控制, 只是簡單的封裝 | 否 | |
| ul_sreado_ms, ul_swriteo_ms | ms級超時控制,但是超時時間是針對每次read或者write操作而言,而不是總體的超時時間, 存在超時控制不準確的問題.另外存在的問題就是write前沒有每次判斷是否可寫導致在極端網(wǎng)絡擁塞的情況下會對CPU產(chǎn)生負面影響 | 否 | 老代碼中還大量存在, 盡量修改為ul_sreado_ms_ex, ul_swriteo_ms_ex |
| ul_sreado, ul_swriteo | 秒級控制, 其他問題與ul_sreado_ms, ul_swriteo_ms一樣 | 否 | |
| public/mynet下的 MyReadO ,MyWrite | 與ul_sreado_ms_ex, ul_swriteo_ms_ex類似,write的時候是堵塞write,需要外部設置非阻塞模式 ,網(wǎng)絡擁塞條件下如果發(fā)送數(shù)據(jù)包很大可能有hand住的風險 | ||
| ul_sreado_ms_ex, ul_swriteo_ms_ex | ul_xxx_ms系列的改進版本,去除了上面列舉的問題 | 是 | |
| ul_sreado_ms_ex2, ul_swriteo_ms_ex2 | 采用recv, send + setsockopt改進的超時控制,在CPU表現(xiàn)上好于ul_xxxx_ms_ex系列 | 是 | 只能針對網(wǎng)絡,不能寫磁盤 |
<
div>
長連接和短連接的各種可能的問題及相應的處理
這里主要是發(fā)起連接的客戶端的問題,這里列出的問題主要是在采用同步模型的情況下才會存在的問題.<
div>
短連接:
采用短連接的情況一般是考慮到下面的一些問題:- 后端服務的問題, 考慮最簡單的情況下一個線程一個連接, 如果這個連接采用了長連接那么就需要我們處理連接的線程和后端保持一一對應,然后按照某些原則進行處理(n對n的關系), 但由于一方面服務器可能增加,這樣導致需要前后端保持一致,帶來了更多的麻煩,另一方面線程數(shù)上不去對應處理能力也會產(chǎn)生影響,而短連接每次連接的時候只 需要關注當前的機器,問題相對會少一些. 其實這個問題可以采用連接池的方式來解決,后面會提到.
- 不需要考慮由于異常帶來的臟數(shù)據(jù)
- 負載均衡方面可以簡單考慮, 無論線程數(shù)是多少還是后端服務器的數(shù)量是多少都沒有關系, 每次考慮單個連接就可以了. 當然如果負載邏輯簡單,并且機器相對固定,一個線程一個長連接問題也不大.
- 規(guī)避一些問題, 在過去有些情況下出現(xiàn)長連接大延時,數(shù)據(jù)沒響應等問題, 測試的時候發(fā)現(xiàn)換短連接問題就解決了,由于時間關系就沒有再繼續(xù)追查, 事實上這些問題現(xiàn)在基本上都已經(jīng)定位并且有相關的解決方案了.
<
div>
長連接:
長連接相比短連接減少了連接的時間消耗, 可以承受更高的負載. 但在使用的時候需要考慮一些問題<
div>
主要線程模型優(yōu)缺點和注意事項
這里所列出的線程模型,目前在我們的public/ub下都有相關的實現(xiàn),在 ubFAQ中也有相關的說明,這里主要針對這些模型的使用做相關的說明<
div>
最簡單的線程模型
同時啟動多個線程, 每個線程都采用accept的方式進行阻塞獲取連接(具體實現(xiàn)上一般是先select在accept, 一方面規(guī)避低內(nèi)核的驚群效應,另一方面可以做到優(yōu)雅退出). 多個線程競爭一個連接, 拿到連接的線程就進行自己的邏輯處理, 包括讀寫IO全部都在一個線程中進行. 短連接每次重新accept, 長連接,第一次的時候accept然后反復使用. 一般來說在總連接數(shù)很少的情況下效果會比較好,相對適用于少量短連接(可以允許比線程數(shù)多一些)和不超過線程總數(shù)的長連接(超過的那些連接,除非 accept的連接斷開,否則不可能會有線程對它進行accept). 但如果同一時候連接數(shù)過多會造成沒有工作線程與客戶端進行連接,客戶端會出現(xiàn)大量的連接失敗, 因為這個時候線程可能存在不能及時accept造成超時問題, 在有重試機制的情況下可能導致問題更糟糕. 有些程序在出現(xiàn)幾次超時之后會長時間一直有連接超時往往就是在這種情況下發(fā)生的. 這種模型的最大優(yōu)點在于編寫簡單, 在正常情況下工作效果不錯. 在public/ub中的xpool就是屬于這種模型,建議針對連接數(shù)少的服務進行使用,比如一些一對一的業(yè)務邏輯.<
div>
生產(chǎn)者消費者模型
普通線程模型在長連接方面存在使用限制(需要對于線程數(shù)進行變化, 而線程又不是無限的), 短連接在處理同時大量連接(比如流量高峰期)的時候存在問題. 生產(chǎn)者消費者模型是可以把這種影響減少. 一般是采用下面的模式 對于有數(shù)據(jù)的活動連接放到異步隊列中, 其他線程競爭這個隊列獲取句柄然后進行相關的操作. 由于accept是專門的線程進行處理, 出現(xiàn)被handle的情況比較少,不容易出現(xiàn)連接失敗的情況.在大流量的情況下有一定的緩沖,雖然有些請求會出現(xiàn)延時,但只要在可以接受的范圍內(nèi),服務還 是可以正常進行. 一般來說隊列的長度主要是考慮可以接受的延時程度. 這種模式也是我們現(xiàn)在許多服務比較常用的模型.可以不用關心客戶端和服務的線程數(shù)對應關系,業(yè)務邏輯上也是比較簡單的。 但這種模式在編程的時候,對于長連接有一個陷阱,判斷句柄是否可讀寫以前一般采用的是select, 如果長連接的連接數(shù)比工作線程還少,當所有的連接都被處理了,有連接需要放回pool中,而這個時候如果正常建立連接的監(jiān)聽線程正好處于select狀 態(tài),這個時候必須要等到 select超時才能重新將連接放入select中進行監(jiān)聽,因為這之前被放入select進行監(jiān)聽的處理socket為空,不會有響應,這個時候由于時 間的浪費造成l長連接的性能下降。一般來說某個連接數(shù)少,某個連接特別活躍就可能造成問題. 過去的一些做法是控制連接數(shù)和服務端的工作線程數(shù)以及通過監(jiān)聽一個管道fd,在工作線程結(jié)束每次都激活這個fd跳出這次select來控制。現(xiàn)在的2.6 內(nèi)核中的epoll在判斷可讀寫的時候不會存在這個問題(epoll在進行監(jiān)聽的時候,其它線程放入或者更改, 在epoll_wait的時候是可以馬上激活的), 我們現(xiàn)在的服務多采用epoll代替select來解決這個, 但是主要的邏輯沒有變化. ub_server中epool和public/ependingpool都是采用種模式<
div>
異步模型
這里只做一些簡單的介紹,更多的可以參考 異步編程講座 ComFrameWorkDev Asynchronous_io KylinFramework 上面兩者模型本質(zhì)都是同步的處理業(yè)務邏輯,在一個線程中處理了讀請求,業(yè)務邏輯和寫回響應三個過程(很多業(yè)務更復雜,但是都是可以做相應的拆封 的), 但是讀和寫這兩個IO的處理往往需要阻塞等待, 這樣造成了線程被阻塞, 如果要應付慢連接(比如外圍抓取等待的時間是秒級的甚至更多), 在等待的時候其實CPU沒有干多少事情, 這個時候就造成了浪費. 一種考慮是增加線程數(shù),通過提高并發(fā)來解決這個問題, 但是我們目前的線程數(shù)還是有限的,不可能無限增加. 而且線程的增加會帶來cpu對于上下文切換的代價,另一方面多個線程從一個隊列中獲取可用連接, 這里存在互斥線程多的時候會導致性能下降,當然這里可以通過把一個隊列改多隊列減少互斥來實現(xiàn). 引入異步化的處理, 就是把對于IO的等待采用IO復用的方式,專門放入到一個或者若干個線程中去, 處理主邏輯的程序可以被釋放出來, 只有在IO處理完畢才進行處理, 這樣可以提高CPU的使用率,減少等待的時間. 一般情況下幾個線程(一般和CPU的核數(shù)相當)可以應付很大的流量請求 public/kylin , ub/ub(ub事件模型)都是基于純異步思想的異步框架。而ub中的appool是簡化版本將原本ub框架中網(wǎng)絡IO處理進行了異步化,不過目前只支持采用nshead頭的模式<
div>
對一些常見錯誤號的分析
| 錯誤號 | 錯誤 | 可能的原因 |
| EAGAIN | Try again | 在讀數(shù)據(jù)的時候,沒有數(shù)據(jù)在底層緩沖的時候會遇到,一般的處理是循環(huán)進行讀操作,異步模式還會等待讀事件的發(fā)生再讀 |
| EWOULDBLOCK | Operation would block | 在我們的環(huán)境中和EAGAIN是一個值, 一般情況下只關心EAGAIN就可以了 |
| EPIPE | Broken pipe | 接收端關閉(緩沖中沒有多余的數(shù)據(jù)),但是發(fā)送端還在write. |
| ECONNRESET | Connection reset by peer | 收 到RST包 可能是 接收到數(shù)據(jù)后不進行讀取或者沒有讀取完畢直接close,另一端再調(diào)用write或者read操作,這個時候需要檢查一下是否存在臟數(shù)據(jù)或者一端某些情況 下斷開的情況. 另外 使用了SO_LINGER后close連接,另一端也會收到這個錯誤. 另外在epoll中一般也是可能返回EPOLLHUP事件。 連接的時候也可能出現(xiàn)這樣的錯誤,這個參考后面的 "listen的時候的backlog有什么影響 "中的說明 |
| EINTR | Interrupted system call | 被其他的系統(tǒng)調(diào)用中斷了, 對于句柄進行操作比較容易出現(xiàn),一般裸用recv都是需要判斷的, 處理也很簡單, 再進行一次操作就可以了 |
| ETIMEDOUT | Connection timed out | 連接超時, 但是在我們ul_sread_xxx或者ul_swrite_xx系列中也被我們用來表示讀寫超時 |
| ECONNREFUSED | Connection refused | 拒絕連接, 一般在機器存在但是相應的端口上沒有數(shù)據(jù)的時候出現(xiàn) |
| ENETUNREACH | Network is unreachable | 網(wǎng)絡不可達,可能是由于路器的限制不能訪問,需要檢查網(wǎng)絡 |
| EADDRNOTAVAIL | Cannot assign requested address | 不能分配本地地址,一般在端口不夠用的時候會出現(xiàn),很可能是短連接的TIME_WAIT問題造成 |
| EADDRINUSE | Address already in use | 地址已經(jīng)被使用, 已經(jīng)有相應的服務程序占用了這個端口, 或者占用端口的程序退出了但沒有設置端口復用 |
| ENOTCONN | Transport endpoint is not connected | 連接沒有鏈上。 在一個socket出來還沒有accept或者connenct, 還有一種情況就是收到對方發(fā)送過來的RST包,系統(tǒng)已經(jīng)確認連接被斷開了 |
<
div>
注意,這些錯誤號只有在相關函數(shù)返回<0的時候才有意義,否則他們的結(jié)果其實是上一次調(diào)用失敗的結(jié)果. 比如 調(diào)用ul_sreado_ms_ex,返回0,輸出結(jié)果Interrupted system call, 由于ul_sreado_ms_ex內(nèi)部多次read操作,完全有可能其中有一次read返回Interrupted system call, 但是內(nèi)部會進行重試, 但最后結(jié)果是read 0. 這個時候 Interrupted system call這個信息其實是無意義的.FAQ
<
div>
為什么網(wǎng)絡程序會沒有任何預兆的就退出了
一般情況都是沒有設置忽略PIPE信號 在我們的環(huán)境中當網(wǎng)絡觸發(fā)broken pipe (一般情況是write的時候,沒有write完畢, 接受端異常斷開了), 系統(tǒng)默認的行為是直接退出。在我們的程序中一般都要在啟動的時候加上 signal(SIGPIPE, SIG_IGN); 來強制忽略這種錯誤<
div>
write出去的數(shù)據(jù), read的時候知道長度嗎?
嚴格來說, 交互的兩端, 一端write調(diào)用write出去的長度, 接收端是不知道具體要讀多長的. 這里有幾個方面的問題<
div>
<
div>
如何查看和觀察句柄泄露問題
一般情況句柄只有1024個可以使用,所以一般情況下比較容易出現(xiàn), 也可以通過觀察/proc/進程號/fd來觀察。 另外可以采用valgrind來檢查, valgrind參數(shù)中加上 --track-fds = yes 就可以看到最后退出的時候沒有被關閉的句柄,以及打開句柄的位置<
div>
為什么socket寫錯誤,但用recv檢查依然成功?
首先采用recv檢查連接的是基于我們目前的一個請求一個應答的情況 對于客戶端的請求,邏輯一般是這樣 建立連接->發(fā)起請求->接受應答->長連接繼續(xù)發(fā)請求 recv檢查一般是這樣采用下面的方式: ret = recv(sock, buf, sizeof(buf), MSG_DONTWAIT); 通過判斷ret 是否為-1并且errno是EAGAIN 在非堵塞方式下如果這個時候網(wǎng)絡沒有收到數(shù)據(jù), 這個時候認為網(wǎng)絡是正常的 這是由于在網(wǎng)絡交換模式下 我們作為一個客戶端在發(fā)起請求前, 網(wǎng)絡中是不應該存在上一次請求留下來的臟數(shù)據(jù)或者被服務端主動斷開(服務端主動斷開會收到FIN包,這個時候是recv返回值為0), 異常斷開會返回錯誤. 當然這種方式來判斷連接是否存在并不是非常完善,在特殊的交互模式(比如異步全雙工模式)或者延時比較大的網(wǎng)絡中都是存在問題的,不過對于我們目前內(nèi)網(wǎng)中的交互模式還是基本適用的. 這種方式和socket寫錯誤并不矛盾, 寫數(shù)據(jù)超時可能是由于網(wǎng)慢或者數(shù)據(jù)量太大等問題, 這時候并不能說明socket有錯誤, recv檢查完全可能會是正確的. 一般來說遇到socket錯誤,無論是寫錯誤還讀錯誤都是需要關閉重連.<
div>
為什么接收端失敗,但客戶端仍然是write成功
這個是正常現(xiàn)象, write數(shù)據(jù)成功不能表示數(shù)據(jù)已經(jīng)被接收端接收導致,只能表示數(shù)據(jù)已經(jīng)被復制到系統(tǒng)底層的緩沖(不一定發(fā)出), 這個時候的網(wǎng)絡異常都是會造成接收端接收失敗的.<
div>
長連接的情況下出現(xiàn)了不同程度的延時
在一些長連接的條件下, 發(fā)送一個小的數(shù)據(jù)包,結(jié)果會發(fā)現(xiàn)從數(shù)據(jù)write成功到接收端需要等待一定的時間后才能接收到, 而改成短連接這個現(xiàn)象就消失了(如果沒有消失,那么可能網(wǎng)絡本身確實存在延時的問題,特別是跨機房的情況下) 在長連接的處理中出現(xiàn)了延時,而且時間固定,基本都是40ms, 出現(xiàn)40ms延時最大的可能就是由于沒有設置TCP_NODELAY 在長連接的交互中,有些時候一個發(fā)送的數(shù)據(jù)包非常的小,加上一個數(shù)據(jù)包的頭部就會導致浪費,而且由于傳輸?shù)臄?shù)據(jù)多了,就可能會造成網(wǎng)絡擁塞的情 況, 在系統(tǒng)底層默認采用了Nagle算法,可以把連續(xù)發(fā)送的多個小包組裝為一個更大的數(shù)據(jù)包然后再進行發(fā)送. 但是對于我們交互性的應用程序意義就不大了,在這種情況下我們發(fā)送一個小數(shù)據(jù)包的請求,就會立刻進行等待,不會還有后面的數(shù)據(jù)包一起發(fā)送, 這個時候Nagle算法就會產(chǎn)生負作用,在我們的環(huán)境下會產(chǎn)生40ms的延時,這樣就會導致客戶端的處理等待時間過長, 導致程序壓力無法上去. 在代碼中無論是服務端還是客戶端都是建議設置這個選項,避免某一端造成延時 所以對于長連接的情況我們建議都需要設置TCP_NODELAY, 在我們的ub框架下這個選項是默認設置的. 小提示: 對于服務端程序而言, 采用的模式一般是 bind-> listen -> accept, 這個時候accept出來的句柄的各項屬性其實是從listen的句柄中繼承, 所以對于多數(shù)服務端程序只需要對于listen進行監(jiān)聽的句柄設置一次TCP_NODELAY就可以了,不需要每次都accept一次. 設置了NODELAY選項但還是時不時出現(xiàn)10ms(或者某個固定值)的延時 這種情況最有可能的就是服務端程序存在長連接處理的缺陷. 這種情況一般會發(fā)生在使用我們的pendingpool模型(ub中的cpool)情況下,在 模型的說明中有提到. 由于select沒有及時跳出導致一直在浪費時間進行等待. 上面的2個問題都處理了,還是發(fā)現(xiàn)了40ms延時? 協(xié)議棧在發(fā)送包的時候,其實不僅受到TCP_NODELAY的影響,還受到協(xié)議棧里面擁塞窗口大小的影響. 在連接發(fā)送多個小數(shù)據(jù)包的時候會導致數(shù)據(jù)沒有及時發(fā)送出去. 這里的40ms延時其實是兩方面的問題: 對于發(fā)送端, 由于擁塞窗口的存在,在TCP_NODELAY的情況,如果存在多個數(shù)據(jù)包,后面的數(shù)據(jù)包可能會有延時發(fā)出的問題. 這個時候可以采用 TCP_CORK參數(shù), TCP_CORK 需要在數(shù)據(jù)write前設置,并且在write完之后取消,這樣可以把write的數(shù)據(jù)發(fā)送出去( 要注意設置TCP_CORK的時候不能與TCP_NODELAY混用,要么不設置TCP_NODELAY要么就先取消TCP_NODELAY) 但是在做了上面的設置后可能還是會導致40ms的延時, 這個時候如果采用tcpdump查看可以注意是發(fā)送端在發(fā)送了數(shù)據(jù)包后,需要等待服務端的一個ack后才會再次發(fā)送下一個數(shù)據(jù)包,這個時候服務端出現(xiàn)了延 時返回的問題.對于這個問題可以通過設置server端TCP_QUICKACK選項來解決. TCP_QUICKACK可以讓服務端盡快的響應這個ack包. 這個問題的主要原因比較復雜,主要有下面幾個方面 當TCP協(xié)議棧收到數(shù)據(jù)的時候, 是否進行ACK響應(沒有響應是不會發(fā)下一個包的),在我們linux上返回ack包是下面這些條件中的一個- 接收的數(shù)據(jù)足夠多
- 處于快速回復模式(TCP_QUICKACK)
- 存在亂序的包
- 如果有數(shù)據(jù)馬上返回給發(fā)送端,ACK也會一起跟著發(fā)送
<
div>
TIME_WAIT有什么樣的影響?
對于TIME_WAIT的出現(xiàn)具體可以參考<<UNIX網(wǎng)絡編程>>中的章節(jié),總的來說對于一個已經(jīng)建立的連接如果是 主動close, 那么這個連接的端口(注意:不是socket)就會進入到TIME_WAIT狀態(tài),在我們的機器上需要等待60s的時間(有些書上可能提到的是 2MSL,1MSL為1分鐘,但我們的linux實現(xiàn)是按照1分鐘的). 在這一段時間內(nèi),這個端口將不會被釋放,新建立的連接就無法使用這個端口(連接的時候會報Cannot assign requested address的錯誤).可以通過/proc/sys/net/ipv4/ip_local_port_range看到 可用端口的范圍,我們的機器上一般是 32768 61000, 不足3W個,這樣的結(jié)果就是導致如果出現(xiàn)500/s的短連接請求,就會導致端口不夠用連接不上。 這種情況一般修改系統(tǒng)參數(shù)tcp_tw_reuse或者在句柄關閉前設置SO_LINGER選項來解決,也可以通過增大 ip_local_port_range來緩解, 設置SO_LINGER后句柄會被系統(tǒng)立刻關閉,不會進入TIME_WAIT狀態(tài),不過在一些大壓力的情況還是有可能出現(xiàn)連接的替身,導致數(shù)據(jù)包丟失。 系統(tǒng)參數(shù)/proc/sys/net/ipv4/tcp_tw_reuse設為1 會復用TIME_WAIT狀態(tài)socket,如果開啟,客戶端在調(diào)用connect調(diào)用時,會自動復用TIME_WAIT狀態(tài)的端口,相比 SO_LINGER選項更加安全。 一般情況下還可以考慮打開/proc/sys/net/ipv4/tcp_tw_recycle, tcp_tw_recycle打開會去計算和動態(tài)改變每次TIME_WAIT的時間,而不是采用固定值,這樣可以讓一下處于TIME_WAIT的端口提前 釋放,不過在有些情況下會出現(xiàn)問題<
div>
對于服務器端如果出現(xiàn)TIME_WAIT狀態(tài),是不會產(chǎn)生端口不夠用的情況,所有的連接都是用同一個端口的,從一個端口上分配出多個fd給程序 accept出來使用。但是TIME_WAIT過多在服務器端還是會占用一定的內(nèi)存資源, 在/proc/sys/net/ipv4 /tcp_max_xxx 中我們可以系統(tǒng)默認情況下的所允許的最大TIME_WAIT的個數(shù),一般機器上都是180000, 這個對于應付一般程序已經(jīng)足夠了.但對于一些壓力非常大的程序而言,這個時候系統(tǒng)會不主動進入TIME_WAIT狀態(tài)而且是直接跳過, 這個時候如果去看 dmsg中的信息會看到 "TCP: time wait bucket table overflow" , 一般來說這種情況是不會產(chǎn)生太多的負面影響, 這種情況下后來的socket在關閉時不會進入TIME_WAIT狀態(tài),而是直接發(fā)RST包, 并且關閉socket. 不過還是需要關注為什么會短時間內(nèi)出現(xiàn)這么大量的請求. 有關TIME_WAIT的問題可以參考 下面的wiki HttpDV TCP Linger參數(shù)設置分析 處于TIME_WAIT狀態(tài)的socket太多,導致新的連接無法建立 開放評價項目中fwui壓力測試的一些情況<
div>
小提示: 如果需要設置SO_LINGER選項, 需要在FD連接上之后設置才有效果<
div>
什么情況下會出現(xiàn)CLOSE_WAIT狀態(tài)?
一般來說,連接的一端在被動關閉的情況下,已經(jīng)接收到FIN包(對端調(diào)用close)后,這個時候如果接收到FIN包的一端沒有主動close 就會出現(xiàn)CLOSE_WAIT的情況。 一般來說,對于普通正常的交互,處于CLOSE_WAIT的時間很短,一般的邏輯是檢測到網(wǎng)絡出錯,馬上關閉。 但是在一些情況下會出現(xiàn)大量的CLOS_WAIT, 有的甚至維持很長的時間, 這個主要有幾個原因:<
div>
<
div>
順序發(fā)送數(shù)據(jù),接收端出現(xiàn)亂序接收到的情況
網(wǎng)絡壓力大的情況下,有時候會出現(xiàn),發(fā)送端是按照順序發(fā)送, 但是接收端接收的時候順序不對. 一般來說在正常情況下是不會出現(xiàn)數(shù)據(jù)順序錯誤的情況, 但某些異常情況還是有可能導致的. 在我們的協(xié)議棧中,服務端每次建立連接其實都是從accpet所在的隊列中取出一個已經(jīng)建立的fd, 但是在一些異常情況下,可能會出現(xiàn)短時間內(nèi)建立大量連接的情況, accept的隊列長度是有限制, 這里其實有兩個隊列,一個完成隊列另一個是未完成隊列,只有完成了三次握手的連接會放到完成隊列中。如果在短時間內(nèi)accept中的fd沒有被取出導致隊 列變滿,但未完成隊列未滿, 這個時候連接會在未完成隊列中,對于發(fā)起連接的一端來說表現(xiàn)的情況是連接已經(jīng)成功,但實際上連接本身并沒有完成,但這個時候我們依然可以發(fā)起寫操作并且成 功, 只是在進行讀操作的時候,由于對端沒有響應會造成讀超時。對于超時的情況我們一般就把連接直接close關閉了, 但是句柄雖然被關閉了,但是由于TIME_WAIT狀態(tài)的存在, TCP還是會進行重傳。在重傳的時候,如果完成隊列有句柄被處理,那么此時會完成三次握手建立連接,這個時候服務端照樣會進行正常的處理(不過在寫響應的 時候可能會發(fā)生錯誤)。從接收上看,由于重傳成功的情況我們不能控制,對于接收端來說就可能出現(xiàn)亂序的情況。 完成隊列的長度和未完成隊列的長度由listen時候的baklog決定((ullib庫中ul_tcplisten的最后一個參數(shù)),在我們的 linux環(huán)境中baklog是完成隊列的長度,baklog * 1.5是兩個隊列的總長度(與一些書上所說的兩個隊列長度不超過baklog有出入). 兩個隊列的總長度最大值限制是128, 既使設置的結(jié)果超過了128也會被自動改為128。128這個限制可以通過系統(tǒng)參數(shù) /proc/sys/net/core/somaxconn 來更改, 在我們 5-6-0-0 內(nèi)核版本以后,STL將其提高到2048. 另外客戶端也可以考慮使用SO_LINGER參數(shù)通過強制關閉連接來處理這個問題,這樣在close以后就不啟用重傳機制。另外的考慮就是對重試機制根據(jù) 業(yè)務邏輯進行改進。 更詳細的說明參考雙周刊 Transfer命令非順序到達的調(diào)查<
div>
連接偶爾出現(xiàn)超時有哪些可能
主要幾個方面的可能<
div>
listen的時候的backlog有什么影響
backlog代表連接的隊列, 這里對于內(nèi)核中其實會維護2個隊列<
div>
長連接和短連接混用是否會有問題?
雖然這種方式并不合適,但嚴格來說如果程序中做好相關的守護操作(包括一些情況下系統(tǒng)參數(shù)的調(diào)整) 是不會出現(xiàn)問題,基本來說在長短連接混用情況下出現(xiàn)的問題都是由于我們的程序存在不同程度上的缺陷造成的. 可能出現(xiàn)的問題: 只要有一端采用了短連接,那么就可以認為總體是短連接模式。 服務端長連接, 客戶端短連接 客戶端主動關閉, 服務端需要接收到close的FIN包, read返回0 后才知道客戶端已經(jīng)被關閉。在這一段時間內(nèi)其實服務端多維護了一個沒有必要連接的狀態(tài)。在同步模式(pendingpool,ub-xpool, ub-cpool, ub-epool)中由于read是在工作線程中,這個連接相當于線程多做了一次處理,浪費了系統(tǒng)資源。如果是IO異步模式(ub/apool或者使用 ependingpool讀回調(diào))則可以馬上發(fā)現(xiàn),不需要再讓工作線程進行處理 服務端如果采用普通線程模型(ub-xpool)那么在異常情況下FIN包如果沒有及時到達,在這一小段時間內(nèi)這個處理線程不能處理業(yè)務邏輯。如果出現(xiàn)問題的地方比較多這個時候可能會有連鎖反應短時間內(nèi)不能相應。 服務端為長連接,對于服務提供者來說可能早期測試也是采用長連接來進行測試,這個時候accept的baklog可能設置的很小,也不會出現(xiàn)問 題。 但是一旦被大量短連接服務訪問就可能出現(xiàn)問題。所以建議listen的時候baklog都設置為128, 我們現(xiàn)在的系統(tǒng)支持這么大的baklog沒有什么問題。 每次總是客戶端主動斷開,這導致客戶端出現(xiàn)了TIME_WIAT的狀態(tài),在沒有設置SO_LINGER或者改變系統(tǒng)參數(shù)的情況下,比較容易出現(xiàn)客戶端端口不夠用的情況。 服務端短連接,客戶端長連接 這個時候的問題相對比較少, 但是如果客戶端在發(fā)送數(shù)據(jù)前(或者收完數(shù)據(jù)后)沒有對臟數(shù)據(jù)進行檢查,在寫的時候都會出現(xiàn)大量寫錯誤或者讀錯誤,做一次無用的操作,浪費系統(tǒng)資源 一般的建議是采用長連接還是短連接,兩端保持一致, 但采用配置的方式并不合適,這個需要在上線的時候檢查這些問題。比較好的方式是把采用長連接還是短連接放到數(shù)據(jù)包頭部中。客戶端發(fā)送的時候標記自己是采用 短連接還是長連接,服務端接收到后按照客戶端的情況采取相應的措施,并且告知客戶端。特別的如果服務端不支持長連接,也可以告知客戶端,服務采用了短連接 要注意的是,如果采用了一些框架或者庫, 在read到0的情況下可能會多打日志,這個對性能的影響可能會比較大。<
div>
select, epoll使用上的注意
select, epoll實現(xiàn)上的區(qū)別可以參考, 本質(zhì)上來說 select, poll的實現(xiàn)是一樣的,epoll由于內(nèi)部采用了樹的結(jié)構(gòu)來維護句柄數(shù),并且使用了通知機制,省去了輪詢的過程,在對于需要大量連接的情況下在CPU上會有一定的優(yōu)勢. select默認情況下可以支持句柄數(shù)是1024, 這個可以看/usr/include/bits/typesizes.h 中的__FD_SETSIZE, 在我們的編譯機(不是開發(fā)機,是SCMPF平臺的機器)這個值已經(jīng)被修改為51200, 如果select在處理fd超過1024的情況下出現(xiàn)問題可用檢查一下編譯程序的機器上__FD_SETSIZE是否正確. epoll在句柄數(shù)的限制沒有像select那樣需要通過改變系統(tǒng)環(huán)境中的宏來實現(xiàn)對更多句柄的支持 另外我們發(fā)現(xiàn)有些程序在使用epoll的時候打開了邊緣觸發(fā)模式(EPOLLET), 采用邊緣觸發(fā)其實是存在風險的,在代碼中需要很小心,避免由于連接兩次數(shù)據(jù)到達,而被只讀出一部分的數(shù)據(jù). EPOLLET的本意是在數(shù)據(jù)情況發(fā)生變化的時候激活(比如不可讀進入可讀狀態(tài)), 但問題是這個時候如果在一次處理完畢后不能保證fd已經(jīng)進入了不可讀狀態(tài)(一般來說是讀到EAGIN的情況), 后續(xù)可能就一直不會被激活. 一般情況下建議使用EPOLLET模式.一個最典型的問題就是監(jiān)聽的句柄被設置為EPOLLET, 當同時多個連接建立的時候, 我們只accept出一個連接進行處理, 這樣就可能導致后來的連接不能被及時處理,要等到下一次連接才會被激活. 小提示: ullib 中常用的ul_sreado_ms_ex,ul_swriteo_ms_ex內(nèi)部是采用select的機制,即使是在scmpf平臺上編譯出來也還是受到 51200的限制,可用ul_sreado_ms_ex2,和ul_swriteo_ms_ex2這個兩個接口來規(guī)避這個問題,他們內(nèi)部不是采用 select的方式來實現(xiàn)超時控制的(需要ullib 3.1.22以后版本) ullib 中現(xiàn)在對于所有fd的處理在有select的地方都改成了poll, poll使用時候沒有這個宏的限制。采用最新版本的ullib就可以避免這個宏產(chǎn)生的問題<
div>
一個進程的socket句柄數(shù)只能是1024嗎?
答案是否定的, 一臺機器上可以使用的socket句柄數(shù)是由系統(tǒng)參數(shù) /proc/sys/fs/file-max 來決定的.這里的 1024只不過是系統(tǒng)對于一個進程socket的限制,我們完全可以采用ulimit的參數(shù)把這個值增大,不過增大需要采用root權(quán)限,這個不是每個工 程師都可以采用的.所以 在公司內(nèi)采用了一個limit的程序,我們的所有的機器上都有預裝這個程序,這個程序已經(jīng)通過了提權(quán)可以以root的身份設置 ulimit的結(jié)果.使用的時候 limit ./myprogram 進行啟動即可, 默認是可以支持51200個句柄,采用limit -n num 可以設置實際的句柄數(shù). 如果還需要更多的連接就需要用ulimit進行專門的操作. 另外就是對于內(nèi)核中還有一個宏NR_OPEN會限制fd的做大個數(shù),目前這個值是1024*1024 小提示: linux系統(tǒng)中socket句柄和文件句柄是不區(qū)分的,如果文件句柄+socket句柄的個數(shù)超過1024同樣也會出問題,這個時候也需要limit提高句柄數(shù). ulimit對于非root權(quán)限的帳戶而言只能往小的值去設置, 在終端上的設置的結(jié)果一般是針對本次shell的, 要還原退出終端重新進入就可以了.<
div>
用limit方式啟動,程序讀寫的時候出core?
這個又是另外一個問題,前面已經(jīng)提到了在網(wǎng)絡程序中對于超時的控制是往往會采用select或者poll的方式.select的時候?qū)τ谥С值?FD其實是有上限的,可以看/usr/inclue/sys/select.h中對于fd_set的聲明,其實一個__FD_SETSIZE /(8*sizeof(long))的long數(shù)組,在默認情況下__FD_SETSIZE的定義是1024,這個可以看 /usr/include/bits/typesizes.h 中的聲明,如果這個時候這個宏還是1024,那么對于采用select方式實現(xiàn)的讀寫超時控制程序在處理超過1024個句柄的時候就會導致內(nèi)存越界出 core .我們的程序如果是線下編譯,由于許多開發(fā)機和測試這個參數(shù)都沒有修改,這個時候就會造成出core,其實不一定出core甚至有些情況下會出現(xiàn)有數(shù)據(jù)但 還是超時的情況. 但對于我們的SCMPF平臺上編譯出來的程序是正常的,SCMPF平臺上這個參數(shù)已經(jīng)進行了修改,所以有時會出現(xiàn)QA測試沒問題,RD 自測有問題的情況. (注意,這里的fd數(shù)量是全部的fd,包括磁盤文件)<
div>
一臺機器最多可以建立多少連接?
理論上來說這個是可以非常多的,取決于可以使用多少的內(nèi)存.我們的系統(tǒng)一般采用一個四元組來表示一個唯一的連接{客戶端ip, 客戶端端口, 服務端ip, 服務端端口} (有些地方算上TCP, UDP表示成5元組), 在網(wǎng)絡連接中對于服務端采用的一般是bind一個固定的端口, 然后監(jiān)聽這個端口,在有連接建立的時候進行accept操作,這個時候所有建立的連接都只 用到服務端的一個端口.對于一個唯一的連接在服務端ip和 服務端端口都確定的情況下,同一個ip上的客戶端如果要建立一個連接就需要分別采用不同的端,一臺機器上的端口是有限,最多65535(一個 unsigned char)個,在系統(tǒng)文件/proc/sys/net/ipv4/ip_local_port_range 中我們一般可以看到32768 61000 的結(jié)果,這里表示這臺機器可以使用的端口范圍是32768到61000, 也就是說事實上對于客戶端機器而言可以使用的連接數(shù)還不足3W個,當然我們可以調(diào)整這個數(shù)值把可用端口數(shù)增加到6W. 但是這個時候?qū)τ诜斩说某绦蛲耆皇苓@個限制因為它都是用一個端口,這個時候服務端受到是連接句柄數(shù)的限制,在上面對于句柄數(shù)的說明已經(jīng)介紹過了,一個 進程可以建立的句柄數(shù)是由/proc/sys/fs/file-max決定上限和ulimit來控制的.所以這個時候服務端完全可以建立更多的連接,這個 時候的主要問題在于如何維護和管理這么多的連接,經(jīng)典的一個連接對應一個線程的處理方式這個時候已經(jīng)不適用了,需要考慮采用一些異步處理的方式來解決, 畢竟線程數(shù)的影響放在那邊 小提示: 一般的服務模式都是服務端一個端口,客戶端使用不同的端口進行連接,但是其實我們也是可以把這個過程倒過來,我們客戶端只用一個端但是服務端確是不同的端口,客戶端做下面的修改 原有的方式 socket分配句柄-> connect 分配的句柄 改為 socket分配句柄 ->對socket設置SO_REUSEADDR選項->像服務端一樣bind某個端口->connect 就可以實現(xiàn) 不過這種應用相對比較少,對于像網(wǎng)絡爬蟲這種情況可能相對會比較適用,只不過6w連接已經(jīng)夠多了,繼續(xù)增加的意義不一定那么大就是了.<
div>
對于一個不存在的ip建立連接是超時還是馬上返回?
這個要根據(jù)情況來看, 一般情況connect一個不存在的ip地址,發(fā)起連接的服務需要等待ack的返回,由于ip地址不存在,不會有返回,這個時候會一直等到超時才返回。如 果連接的是一個存在的ip,但是相應的端口沒有服務,這個時候會馬上得到返回,收到一個ECONNREFUSED(Connection refused)的結(jié)果。 但是在我們的網(wǎng)絡會存在一些有限制的路由器,比如我們一些機器不允許訪問外網(wǎng),這個時候如果訪問的ip是一個外網(wǎng)ip(無論是否存在),這個時候也會馬上返回得到一個Network is unreachable的錯誤,不需要等待。<
div>
各種超時怎么設置?
對于超時時間的設置相對比較復雜,取決于各種業(yè)務不同的邏輯 外網(wǎng)情況比較復雜, 這里主要針對內(nèi)網(wǎng)小數(shù)據(jù)包的情況進行分析- 連接超時: 一般來說連接超時主要發(fā)生在下游服務的accept隊列滿掉和下游服務掛掉的情況下. 對于accept隊列滿的情況下參考前面 對于backlog的說明,可以通過下游設置tcp_abort_on_overflow來解決. 對于TCP來說默認情況下如果出現(xiàn)丟包,那么需要3s的時間才能重發(fā), 對于內(nèi)網(wǎng)來說設置超時1s和2s沒有多大區(qū)別考慮到線程的使用可以減少這樣的超時時間.
- 寫超時: 一般情況寫超時是很少見的,只有在網(wǎng)絡壓力過大的情況下才容易出現(xiàn)。 寫的數(shù)據(jù) 如果在K級,write的時間基本可以忽略不記。TCP由于網(wǎng)絡丟包,對于write來說需要200ms才可以重發(fā),這個時間內(nèi)50ms, 100ms也沒有多大的差別。對于小數(shù)據(jù)包這個時間建議設置小于200ms, 因為write超時的情況一般不容易出現(xiàn)
- 讀超時: 服務性程序的讀超時,其實不是讀超時, 是在等待后端數(shù)據(jù)的數(shù)據(jù)時間。這個超時取決于后端的處理業(yè)務邏輯處理消耗的時間
<
div>對于大數(shù)據(jù)量,由于本身數(shù)據(jù)傳輸就存在大量時間消耗,可以適當?shù)?/p>
轉(zhuǎn)載于:https://www.cnblogs.com/ixxonline/p/4106555.html
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的【转】网络编程常见问题总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。