Socket編程中的幾點(diǎn)問題總結(jié)

Socket編程中的幾點(diǎn)問題總結(jié)

epoll_ctl中 epoll_event參數(shù)設(shè)置
  • 對(duì)于 EPOLLERREPOLLHUP,不需要在epoll_event時(shí)針對(duì)fd作設(shè)置,一樣也會(huì)觸發(fā);

  • EPOLLRDHUP實(shí)測在對(duì)端關(guān)閉時(shí)會(huì)觸發(fā),需要注意的是:

    1. 對(duì)EPOLLRDHUP的處理應(yīng)該放在EPOLLINEPOLLOUT前面,處理方式應(yīng)該 是close掉相應(yīng)的fd后,作其他應(yīng)用層的清理動(dòng)作;
    2. 如果采用的是LT觸發(fā)模式,且沒有close相應(yīng)的fd, EPOLLRDHUP會(huì)持續(xù)被觸發(fā);
    3. EPOLLRDHUP想要被觸發(fā),需要顯式地在epoll_ctl調(diào)用時(shí)設(shè)置在events中;
    4. 對(duì)端關(guān)閉包括:ctrl + c, kill, kill -9。
  • 對(duì)于EPOLLOUT

    1. 有寫需要時(shí)才通過epoll_ctl添加相應(yīng)fd,不然在LT模式下會(huì)頻繁觸發(fā);
    2. 對(duì)于寫操作,大部分情況下都處于可寫狀態(tài),可先直接調(diào)用write來發(fā)送數(shù)據(jù),直到返回 EAGAIN后再使能EPOLLOUT,待觸發(fā)后再繼續(xù)write。
accept相關(guān):
  • accept接收對(duì)端連接,會(huì)觸載EPOLLIN, 這里可以循環(huán)多次調(diào)用accept, 直至返回 EAGAIN, 同時(shí)適用于LT和ET。
對(duì)已經(jīng)close的fd繼續(xù)操作
  • read: 返回-1, errno = 9, Bad file descriptor ;
  • close: 同上;
  • write:同上;
如何判斷對(duì)端關(guān)閉
  • 優(yōu)先使用上面介紹的EPOLLRDHUP;
  • 使用EPOLLIN, 然后調(diào)用read, 此時(shí)返回的ssize_t類型結(jié)果為0;
  • 對(duì)端關(guān)閉包括:ctrl + c, kill, kill -9。
對(duì)端正常 close時(shí)本端行為

這部分有些內(nèi)容上面已闡述過,這里統(tǒng)一歸納一下。

  • 對(duì)端close時(shí),如果接收緩沖區(qū)內(nèi)已無數(shù)據(jù),則走tcp四次揮手流程,發(fā)送FIN 包,此時(shí)本端會(huì)觸發(fā)事件如下:

    EPOLLRDHUP  (需要主動(dòng)在epoll_ctal時(shí)加入events)
    EPOLLIN     
    EPOLLOUT    
    
    1. 此時(shí)應(yīng)優(yōu)先處理EPOLLRDHUP,它明確表明對(duì)端已經(jīng)關(guān)閉,處理時(shí)close相應(yīng)fd后,無需再繼續(xù)處理其他事件;

    2. 如果不處理EPOLLRDHUP的話,也可以處理EPOLLIN事件,此時(shí)read返回0, 同樣表明對(duì)端已經(jīng)關(guān)閉;

    3. 如果以上兩個(gè)事件都沒有處理,而是在EPOLLOUT事件里又向fd寫了數(shù)據(jù),數(shù)據(jù)只是寫入到本地tcp發(fā)送緩沖區(qū),此時(shí)write調(diào)用會(huì)返回成功,但是緊接著epoll_wait又會(huì)返回如下事件組合:

      EPOLLERR    
      EPOLLHUP
      EPOLLIN     
      EPOLLOUT 
      POLLRDHUP  (需要主動(dòng)在epoll_ctal時(shí)加入events)
      

      可以看到相比之前多了EPOLLERREPOLLHUP,是因?yàn)橹笆盏搅藢?duì)端close時(shí)發(fā)送的FIN 包,此時(shí)再給對(duì)端發(fā)送數(shù)據(jù),對(duì)端會(huì)返回RST包。

      如果在收到RST包后,又向?qū)Χ税l(fā)送數(shù)據(jù),會(huì)收到sigpipe異常,其默認(rèn)處理是終止當(dāng)前進(jìn)程,此時(shí)可通過忽略此異常解決,忽略后write會(huì)返回-1, erron =32, Broken pipe:

      signal(SIGPIPE, SIG_IGN);
      

      Broker pipie這個(gè)異常,說到底是應(yīng)用層沒有對(duì)相應(yīng)的fd在收到對(duì)端關(guān)閉通知時(shí),作正確的處理所致,它并不是tcp/ip通訊層面的問題。

    4. 下圖可以看到發(fā)送了FIN

tcp_fin.png
  • 對(duì)端close(kill, kill -9)時(shí),如果接收緩沖區(qū)內(nèi)還有數(shù)據(jù),不會(huì)發(fā)送FIN包,而是發(fā)送RST,此時(shí)本端:
1. 收到`RST`后的第一次寫操作,寫失敗,errno = 104,  Connection reset by peer; 之后將觸發(fā)下列事件:

   ```
   EPOLLIN
   EPOLLOUT
   EPOLLHUP
   EPOLLRDHUP(需要主動(dòng)在epoll_ctal時(shí)加入events)
   ```

   

2. 收到`RST`后的第二次及后序的寫操作,寫失敗,在忽略了`SIGPIPE`后,erron =32, Broken pipe;

3. 收到`RST`后的讀操作:errno = 104,  Connection reset by peer

4. 下面可以看到發(fā)送了`RST`包:
tcp_rst.png
阻塞與非阻塞
  • 針對(duì)Epoll的LT模式,socket fd可以設(shè)置成阻塞也可以設(shè)置成非阻塞;
  • 針對(duì)Epoll的ET模式,socket fd只能設(shè)置成非阻塞;
    1. ET狀態(tài)有變化才觸發(fā),因此在收數(shù)據(jù)時(shí)必須循環(huán)讀取,收盡當(dāng)前可收數(shù)據(jù)。因?yàn)椴恢老乱淮握{(diào)用read時(shí)還有沒有數(shù)據(jù),一旦沒有數(shù)據(jù),又沒有用非阻塞方式,則將一直阻塞在read調(diào)用上;
    2. 當(dāng)然如果在LT模式下也每次循環(huán)讀取,也有類似的問題;
    3. 采用非阻塞循環(huán)讀取方式時(shí),如果當(dāng)前socket fd上恰好有持續(xù)大數(shù)據(jù)量寫入,則這個(gè)循環(huán)讀取可能持續(xù)較長時(shí)間,從而導(dǎo)致其他socket fd上的讀寫操作將被延遲。針對(duì)這種情況,我們只能是控制當(dāng)前socket fd上的讀操作,并將其保存,在下一次event loop中不依賴ET的觸發(fā),直接針對(duì)保存的fd繼續(xù)其讀操作。
close行為
  • close時(shí),如果接收緩沖區(qū)還有數(shù)據(jù)未read到應(yīng)用層,則不會(huì)走四次揮手流程,直接發(fā)RST包,這個(gè)前面已經(jīng)介紹過;

  • close時(shí),如果發(fā)送緩沖區(qū)還有數(shù)據(jù)未發(fā)送,close立即返回,系統(tǒng)接管這個(gè)socket, 將盡力將發(fā)送緩沖區(qū)數(shù)據(jù)到對(duì)端,然后走發(fā)送FIN包;

  • 使用SO_LINGER改變close默認(rèn)行為:

    通過struct linger設(shè)置

    linger.l_onoff linger.l_linger close行為 kernel行為 備注
    0 為 disable 忽略 立即返回,同close的默認(rèn)行為 盡力將發(fā)送緩存區(qū)中數(shù)據(jù)發(fā)送到對(duì)端,然后發(fā)送FIN包,四次揮手
    > 0 為enable 0 立即返回 不走正常四次揮手,直接發(fā)送RST包,沒有TIME_WAIT狀態(tài)
    > 0 為enbale 大于0 不管socket是否為blocking或noblocking, 都會(huì)阻塞直數(shù)據(jù)發(fā)送完成并收到對(duì)端的ACK, 或者linger.l_linger超時(shí) 如超時(shí)不走正常四次揮手,直接發(fā)送RST包,沒有TIME_WAIT狀態(tài)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容