需要做udp proxy的功能,但是在網(wǎng)上看到一篇文章提到了關于UDP Socket列表變化問題的問題,相關的內(nèi)容如下,自己試驗了下,沒有出現(xiàn)文章中所提到的
UDP Socket列表變化問題,當然有可能是測試數(shù)據(jù)量不夠,不知有沒有哪位朋友了解,是否在所有的linux內(nèi)核都有這個問題?要進行多處理,就免不了要在相同的地址端口上處理數(shù)據(jù),SO_REUSEADDR允許端口的重用,只要確保四元組的唯一性即可。對于TCP,在bind的時候所有可能產(chǎn)生四元組不唯一的bind都會被禁止(于是,ip相同的情況下,TCP套接字處于TIME_WAIT狀態(tài)下的socket,才可以重復綁定使用);對于connect,由于通信兩端中的本端已經(jīng)明確了,那么只允許connect從來沒connect過的對端(在明確不會破壞四元組唯一性的connect才允許發(fā)送SYN包);對于監(jiān)聽listen端,四元組的唯一性油connect端保證就OK了。 TCP通過連接來保證四元組的唯一性,一個connect請求過來,accept進程accept完這個請求后(當然不一定要單獨accept進程),就可以分配socket資源來標識這個連接,接著就可以分發(fā)給相應的worker進程去處理該連接后續(xù)的事情了。這樣就可以在多核服務器中,同時有多個worker進程來同時處理多個并發(fā)請求,從而達到負載均衡,CPU資源能夠被充分利用。 UDP的無連接狀態(tài)(沒有已有對端的信息),使得UDP沒有一個有效的辦法來判斷四元組是否沖突,于是對于新來的請求,UDP無法進行資源的預分配,于是多處理模式難以進行,最終只能“守株待兔“,UDP按照固定的算法查找目標UDP socket,這樣每次查到的都是UDP socket列表固定位置的socket。UDP只是簡單基于目的IP和目的端口來進行查找,這樣在一個服務器上多個進程內(nèi)創(chuàng)建多個綁定相同IP地址(SO_REUSEADDR),相同端口的UDP socket,那么你會發(fā)現(xiàn),只有最后一個創(chuàng)建的socket會接收到數(shù)據(jù),其它的都是默默地等待,孤獨地等待永遠也收不到UDP數(shù)據(jù)。 UDP這種只能單進程、單處理的方式將要破滅UDP高效的神話,你在一個多核的服務器上運行這樣的UDP程序,會發(fā)現(xiàn)只有一個核在忙,其他CPU核心處于空閑的狀態(tài)。創(chuàng)建多個綁定相同IP地址,相同端口的UDP程序,只會起到容災備份的作用,不會起到負載均衡的作用。 要實現(xiàn)多處理,那么就要改變UDP Socket查找的考慮因素,對于調用了connect的UDP Client而言,由于其具有了“連接”性,通信雙方都固定下來了,那么內(nèi)核就可以根據(jù)4元組完全匹配的原則來匹配。于是對于不同的通信對端,可以查找到不同的UDP Socket從而實現(xiàn)多處理。而對于server端,在使用SO_REUSEPORT選項(linux 3.9以上內(nèi)核),這樣在進行UDP socket查找的時候,源IP地址和源端口也參與進來了,內(nèi)核查找算法可以保證:
這樣對于不同client發(fā)來的數(shù)據(jù)包就能查找到不同的UDP socket從而實現(xiàn)多處理。這樣看來,似乎采用SO_REUSEADDR、SO_REUSEPORT這兩個socket選項并利用內(nèi)核的socket查找算法,我們在多核CPU服務器上多個進程內(nèi)創(chuàng)建多個綁定相同端口,相同IP地址的UDP socket就能做到負載均衡充分利用多核CPU資源了。然而事情遠沒這么順利、簡單。
2.2 UDP Socket列表變化問題通過上面我們知道,在采用SO_REUSEADDR、SO_REUSEPORT這兩個socket選項后,內(nèi)核會根據(jù)UDP數(shù)據(jù)包的4元組來查找本機上的所有相同目的IP地址,相同目的端口的socket中的一個socket的位置,然后以這個位置上的socket作為接收數(shù)據(jù)的socket。那么要確保來至同一個Client Endpoint的UDP數(shù)據(jù)包總是被同一個socket來處理,就需要保證整個socket鏈表的socket所處的位置不能改變。 然而,如果socket鏈表中間的某個socket掛了的話,就會造成socket鏈表重新排序,這樣會引發(fā)問題。于是基本的解決方案是在整個服務過程中不能關閉UDP socket(當然也可以全部UDP socket都close掉,從新創(chuàng)建一批新的)。要保證這一點,我們需要所有的UDP socket的創(chuàng)建和關閉都由一個master進行來管理,worker進程只是負責處理對于的網(wǎng)絡IO任務,為此我們需要socket在創(chuàng)建的時候要帶有CLOEXEC標志(SOCK_CLOEXEC)。
|