1. SO_REUSEADDR
假如服務端出現故障,主動斷開連接以后,需要等 2 個 MSL 以后才最終釋放這個連接,而服務重啟以后要綁定同一個端口,默認情況下,操作系統的實現都會阻止新的監聽套接字綁定到這個端口上。啟用 SO_REUSEADDR 套接字選項可以解除這個限制。
2. SO_REUSEPORT
默認情況下,一個 IP、端口組合只能被一個套接字綁定,Linux 內核從 3.9 版本開始引入一個新的 socket 選項 SO_REUSEPORT,又稱為 port sharding,允許多個套接字監聽同一個IP 和端口組合。
2.1 多進程網絡模型
- 主進程 + 多個 worker 子進程監聽相同的端口(導致驚群問題)
- 多進程 + REUSEPORT
2.2 驚群問題
多進程/多線程同時監聽同一個套接字,當有網絡事件發生時,所有等待的進程/線程同時被喚醒,但是只有其中一個進程/線程可以處理該網絡事件,其它的進程/線程獲取失敗重新進入休眠。
2.3 accept系統調用的驚群問題
在Linux 2.6之前,會出現驚群問題。而在后面的版本中,引入了WQ_FLAG_EXCLUSIVE 選項解決了 accept 調用的驚群問題。
2.4 epoll系統調用的驚群問題
當有新的網絡事件發生時,阻塞在 epoll_wait 的多個進程同時被喚醒。在這種情況下,epoll 的驚群還是存在的。
2.5 SO_REUSEPORT原理
內核為處于 LISTEN 狀態的 socket 分配了大小為 32 哈希桶。監聽的端口號經過哈希算法運算打散到這些哈希桶中,相同哈希的端口采用拉鏈法解決沖突。
-
當收到客戶端的 SYN 握手報文以后,會根據目標端口號的哈希值計算出哈希沖突鏈表
-
對于設置了 SO_REUSEPORT 選項的 socket 經過二次哈希找到對應的 SO_REUSEPORT group,從中隨機選擇一個進行處理。
2.6 SO_REUSEPORT作用
- 內核有數據到來的時候就會把請求均衡的分給不同的socket,也就是不同的線程了,這樣內核可以自動的做到負載均衡
- 支持滾動升級
- 新啟動一個新版本 v2 ,監聽同一個端口,與 v1 舊版本一起處理請求。
- 發送信號給 v1 版本的進程,讓它不再接受新的請求
- 等待一段時間,等 v1 版本的用戶請求都已經處理完畢時,v1 版本的進程退出,留下 v2 版本繼續服務
2.7 SO_REUSEPORT安全性
出于安全性考慮,防止端口劫持
- 只有effective-user-id相同的服務器進程才能監聽同一ip:port
- 只有第一個啟動的進程啟用了 SO_REUSEPORT 選項,后面啟動的進程才可以綁定同一個端口。
3. SO_LINGER
設置函數close()關閉TCP連接時的行為。缺省close()的行為是,如果有數據殘留在socket發送緩沖區中則系統將繼續發送這些數據給對方,等待被確認,然后返回。
SO_LINGER選項使用如下結構:
struct linger {int l_onoff;int l_linger;
};
- l_onoff為0,則該選項關閉,l_linger的值被忽略,close()用上述缺省方式關閉連接。
- l_onoff非0,l_linger為0,close()用下面的a方式關閉連接。
- l_onoff非0,l_linger非0,close()用下面的b方式關閉連接。
-
a.立即關閉該連接,通過發送RST分組(而不是用正常的FIN|ACK|FIN|ACK四個分組)來關閉該連接。至于發送緩沖區中如果有未發送完的數據,則丟棄。主動關閉一方的TCP狀態則跳過TIMEWAIT,直接進入CLOSED。
-
b.將連接的關閉設置一個超時。如果socket發送緩沖區中仍殘留數據,進程進入睡眠,內核進入定時狀態去盡量去發送這些數據。
在超時之前,如果所有數據都發送完且被對方確認,內核用正常的FIN|ACK|FIN|ACK四個分組來關閉該連接,close()成功返回。
如果超時之時,數據仍然未能成功發送及被確認,用上述a方式來關閉此連接。close()返回EWOULDBLOCK。
參考文章
深入理解 TCP 協議:從原理到實戰