Socket編程中的幾點(diǎn)問題總結(jié)
epoll_ctl中 epoll_event參數(shù)設(shè)置
對(duì)于
EPOLLERR和EPOLLHUP,不需要在epoll_event時(shí)針對(duì)fd作設(shè)置,一樣也會(huì)觸發(fā);-
EPOLLRDHUP實(shí)測在對(duì)端關(guān)閉時(shí)會(huì)觸發(fā),需要注意的是:- 對(duì)
EPOLLRDHUP的處理應(yīng)該放在EPOLLIN和EPOLLOUT前面,處理方式應(yīng)該 是close掉相應(yīng)的fd后,作其他應(yīng)用層的清理動(dòng)作; - 如果采用的是LT觸發(fā)模式,且沒有close相應(yīng)的fd,
EPOLLRDHUP會(huì)持續(xù)被觸發(fā); -
EPOLLRDHUP想要被觸發(fā),需要顯式地在epoll_ctl調(diào)用時(shí)設(shè)置在events中; - 對(duì)端關(guān)閉包括:ctrl + c, kill, kill -9。
- 對(duì)
-
對(duì)于
EPOLLOUT:- 有寫需要時(shí)才通過
epoll_ctl添加相應(yīng)fd,不然在LT模式下會(huì)頻繁觸發(fā); - 對(duì)于寫操作,大部分情況下都處于可寫狀態(tài),可先直接調(diào)用
write來發(fā)送數(shù)據(jù),直到返回EAGAIN后再使能EPOLLOUT,待觸發(fā)后再繼續(xù)write。
- 有寫需要時(shí)才通過
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此時(shí)應(yīng)優(yōu)先處理
EPOLLRDHUP,它明確表明對(duì)端已經(jīng)關(guān)閉,處理時(shí)close相應(yīng)fd后,無需再繼續(xù)處理其他事件;如果不處理
EPOLLRDHUP的話,也可以處理EPOLLIN事件,此時(shí)read返回0, 同樣表明對(duì)端已經(jīng)關(guān)閉;-
如果以上兩個(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)可以看到相比之前多了
EPOLLERR和EPOLLHUP,是因?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通訊層面的問題。 下圖可以看到發(fā)送了
FIN包

- 對(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`包:

阻塞與非阻塞
- 針對(duì)Epoll的
LT模式,socket fd可以設(shè)置成阻塞也可以設(shè)置成非阻塞; - 針對(duì)Epoll的
ET模式,socket fd只能設(shè)置成非阻塞;- 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)用上; - 當(dāng)然如果在
LT模式下也每次循環(huán)讀取,也有類似的問題; - 采用非阻塞循環(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ù)其讀操作。
- ET狀態(tài)有變化才觸發(fā),因此在收數(shù)據(jù)時(shí)必須循環(huán)讀取,收盡當(dāng)前可收數(shù)據(jù)。因?yàn)椴恢老乱淮握{(diào)用
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)