TCP源端口选择算法与列维模型
發(fā)起一個(gè)TCP連接,4元組是必須的,即源IP,源端口,目標(biāo)IP,目標(biāo)端口。目標(biāo)IP和端口都是確定的,源IP根據(jù)路由選擇或者bind也可以確定,基本上最終的源IP都是本機(jī)的IP地址,然而通過IP_TRANSPARENT參數(shù)可以bind一個(gè)不屬于本機(jī)的IP地址。唯一麻煩的就是源端口的確定。
在繼續(xù)深入源端口選擇算法之前,必須要認(rèn)識(shí)到一個(gè)大的前提,也算是源端口選擇算法的一個(gè)大的目標(biāo),那就是“必須保證TCP四元組的唯一性”!有了這個(gè)前提以及終極目標(biāo),TCP源端口的選擇算法就非常容易理解了。在以下的情況下需要算法來選擇一個(gè)源端口:
1.調(diào)用bind,但是bind的端口是0的時(shí)候;
2.沒有調(diào)用bind,直接調(diào)用connect的時(shí)候。
這兩種情況使不同的,因?yàn)樵诘谝环N情況下,4元組中的目標(biāo)IP和目標(biāo)端口是不確定的,而在第二種情況下,除了源端口,其它的都是知道的。所以兩種情況的端口分配算法是不同的。
1.bind情形的列維搜索算法
對(duì)于bind的情形,由于缺失信息,需要采用非常嚴(yán)格的方式選擇源端口,即要做到:只要有可能四元組沖突,就不能分配。比如已經(jīng)有一個(gè)連接的四元組為:Tuple1(IPsrc,PORTsrc,IPdst,PORTdst),現(xiàn)在為一個(gè)新建立的套接字bind一個(gè)源端口,其不bind任何確定的IP地址,那么它就不能使用PORTsrc這個(gè)端口作為源端口,因?yàn)樗赡芎蚑uple1沖突,雖然僅僅是可能而已!如下是算法的實(shí)現(xiàn):#include <stdio.h> #include <stdlib.h> #include <pthread.h>#define LOW 10000 #define HIGH 65535//端口分配函數(shù) //base:一個(gè)(源IP,目標(biāo)IP,目標(biāo)端口)三元組的hash值 int get_local_port(int base ) {unsigned int i,j,port, remaining; again:remaining = (HIGH - LOW) + 1;//采用隨機(jī)的方式更容易找到空閑端口port = LOW + random()%remaining;for (i = 1; i <= remaining; i++) {int port_ok = 0;//判斷該端口是否可用,由于四元組唯一性現(xiàn)在由于信息不全無法判斷,先檢查最容易匹配的://端口沒有處在TW狀態(tài),非LISTEN狀態(tài),可用//此處要保證的是,聚集者要越往外越少。port_ok = 1;if (port_ok) {goto check_inner;}//如果不合適就以port為基準(zhǔn),遞增port ++;} check_inner:{//更深層次,但更耗時(shí)的判斷int port_inner_ok;port_inner_ok = 1;if (!port_inner_ok) {goto again;}}return port; }//分配端口函數(shù) void func() {while(1) {int port = get_local_port(0);printf(" %d \n", port);sleep(1);} }//main函數(shù) int main(int argc, char **argv) {func();return 0; }
可以看到,算法從一個(gè)隨機(jī)計(jì)算出的值為基準(zhǔn)端口,然后通過一系列的判斷來得到該端口是否可用的信息,一共是兩層的判斷,如果外層簡(jiǎn)單判斷發(fā)現(xiàn)不可用,則遞增端口數(shù)值重新判斷,如果內(nèi)層復(fù)雜判斷該端口不可用,則重新計(jì)算隨機(jī)基準(zhǔn)端口重新開始。使用這個(gè)算法可以很快定位到一個(gè)可用的端口。通過算法可以看得出,它符合列維模型,即在更近的局部細(xì)致掃描,然后飛躍到一個(gè)更遠(yuǎn)的地方繼續(xù)列維查找。
實(shí)際生活中,這種搜索是很高效的,深夜找賓館,到一個(gè)陌生的城市找工作,警察搜山...信天翁覓食...都是列維搜索!
2.connect情形的精確判定模式
connect的時(shí)候,四元組中的三元組已經(jīng)確定,因此可以精確匹配了,和bind時(shí)的端口選擇相反,此時(shí)只要有一個(gè)元組不同即可成功,記住我們的目標(biāo),即保證TCP四元組的唯一性!確定性的查找不需要列維搜索,而是大家都可以根據(jù)順序遞增加簡(jiǎn)單沖突判定的方式進(jìn)行端口選擇,最合常理的方式就是,每一個(gè)三元組(源IP,目標(biāo)IP,目標(biāo)端口)都可以有一個(gè)65534個(gè)端口可供選擇,每次遞增即可。但是這樣的話需要為每一個(gè)端口維護(hù)一個(gè)計(jì)數(shù)器,Linux使用了更加巧妙的方法,可以采用為每一個(gè)三元組用哈希計(jì)算一個(gè)確定的基準(zhǔn)端口,全局維護(hù)一個(gè)遞增的計(jì)數(shù)器,根據(jù)這個(gè)計(jì)數(shù)器與基準(zhǔn)端口之和和端口空間大小做模運(yùn)算,這樣的一個(gè)取模操作可以確定一個(gè)offset,加上最小端口確定一個(gè)候選端口,這樣就保證了候選端口和三元組的線性關(guān)系,也就是說,每一個(gè)三元組獨(dú)立選擇端口。
這么做的好處在于,對(duì)每一個(gè)三元組而言,都是從基準(zhǔn)端口開始順序分配的,相同三元組的端口都集中在一起,因?yàn)槲覀冋且拖嗤M的那些已經(jīng)確定的端口來比較,以判斷有沒有沖突,通過這種方式,將相同三元組的已經(jīng)分配的端口集中在了一起,省去了維護(hù)鏈表的麻煩,只需要從計(jì)算出的候選端口開始線性搜索整個(gè)端口空間即可,由于全局計(jì)數(shù)器是遞增的,所以除非使用bind占據(jù)了某個(gè)端口,一般都會(huì)很快找到可用端口號(hào),最多搜索幾個(gè)就能找到。算法如下所示:
#include <stdio.h> #include <stdlib.h> #include <pthread.h>#define LOW 10000 #define HIGH 65535static unsigned int hint = 0;//端口分配函數(shù) //base:一個(gè)(源IP,目標(biāo)IP,目標(biāo)端口)三元組的hash值 int get_local_port(int base ) {unsigned int i,j,port, remaining;unsigned int offset = hint + base;remaining = (HIGH - LOW) + 1;for (i = 1; i <= remaining; i++) {int port_ok = 0;port = LOW + (i + offset) % remaining;//判斷該端口是否可用,由于僅四元組唯一即可接受,現(xiàn)在假設(shè)://所有的端口均已經(jīng)安全釋放。port_ok = 1;if (port_ok) {break;}}//越過你排除的那幾個(gè)(那些!)hint += i;return port; }//hint遞增函數(shù) int inc_hint(int value) {hint += value; }//分配端口線程 void *func(void *arg) {while(1) {int port = get_local_port(0);printf(" %d \n", port);sleep(1);} }//hint遞增線程 void *func_others(void *arg) {while (1) {int rnd = random();//其它的線程選擇源端口的時(shí)候,由于使用不同的(源IP,目標(biāo)IP,目標(biāo)端口)//不會(huì)有任何沖突,因此只模擬遞增hint即可。inc_hint (1);sleep(1);} }//main函數(shù) int main(int argc, char **argv) {pthread_t id[20] = {0};int i = 0, ret = 0;//一個(gè)線程不斷分配端口ret = pthread_create(&id[0], NULL, (void*)func, NULL);if (ret) {printf("Create pthread error!/n");return 1;}//N個(gè)線程模擬其它的端口分配,僅僅遞增hintfor (i = 1; i < 20; i++) {ret = pthread_create(&id[i], NULL, (void*)func_others, NULL);if (ret) {printf("Create pthread error!/n");return 1;}}for (i = 0; i < 20; i++) {pthread_join(id[i], NULL);}return 0; }
關(guān)鍵點(diǎn)在于,三元組已經(jīng)確定,剩下的就是根據(jù)不同的三元組獨(dú)立遞增端口,效果就是同樣三元組的端口都聚集在一起,在無需鏈表的情況下高效判斷!
附:關(guān)于列維搜索
列維模型其實(shí)是一種概率分布模型,和泊松分布大大不同!它更多的體現(xiàn)在一種“生長(zhǎng),聚集”效應(yīng)上。通俗來講就是,首先隨機(jī)確定一個(gè)點(diǎn),然后在該點(diǎn)附近進(jìn)行遍歷搜索,成功后則加入,達(dá)到閥值后依然沒有找到則退出,再次隨機(jī)生成一個(gè)點(diǎn),在該點(diǎn)附近搜索,以此類推。這種模型有很精確的數(shù)學(xué)證明。如果對(duì)數(shù)學(xué)望而卻步,可以從生活中體會(huì)。剛畢業(yè)不久的人,可能會(huì)留在一個(gè)城市工作,兩三年內(nèi)換了N份工作,接下來突然到達(dá)一個(gè)陌生的城市,從新開始...這就是列維模型!本質(zhì)上講,整個(gè)人類文明都是遵循列維模型,一開始原始人從來不知道哪里適合居住,也不知道自己要到哪里去,只是漂泊蕩漾,但是過了千百萬年以后,我們發(fā)現(xiàn)人類的分布并不是平均的,也就是說,有些地方發(fā)展成了大都市,有些地方依然沒有人煙,這難道說發(fā)展成都市的地方自然條件優(yōu)于沒有人煙的地方嗎?非也!列維模型在起作用,它正如莫菲法則一樣是個(gè)真理!列維模型一直都在主宰著我們,并且工作的很好,由于列維模型,我們現(xiàn)在擁有了典型的幾個(gè)不錯(cuò)的國際化大都市...列維模型正如磁石一樣在發(fā)揮著作用。它本質(zhì)上就是要把同類同質(zhì)的東西聚集在一起!Linux在bind的時(shí)候分配端口正是使用這種列維搜索方式。列維模型天生具備一個(gè)閥值,即,在由于列維模型聚集在一起的東西超過閥值后,將不再聚集,而是選擇“長(zhǎng)跳”,即隨機(jī)到達(dá)一個(gè)比較遠(yuǎn)的地方重新開始聚集!列維模型總結(jié)起來就是,局部搜索,達(dá)到范圍閥值后,到一個(gè)很遠(yuǎn)但是隨機(jī)的地方重新開始局部搜索!如下圖所示:
本文轉(zhuǎn)自 dog250 51CTO博客,原文鏈接:http://blog.51cto.com/dog250/1318982
總結(jié)
以上是生活随笔為你收集整理的TCP源端口选择算法与列维模型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: conda创建环境、安装包、删除环境的方
- 下一篇: 国内常用的yum源