发起一个TCP连接,4元组是必须的,即源IP,源端口,目标IP,目标端口。目标IP和端口都是确定的,源IP根据路由选择或者bind也可以确定,基本上最终的源IP都是本机的IP地址,然而通过IP_TRANSPARENT参数可以bind一个不属于本机的IP地址。唯一麻烦的就是源端口的确定。
在继续深入源端口选择算法之前,必须要认识到一个大的前提,也算是源端口选择算法的一个大的目标,那就是“必须保证TCP四元组的唯一性”!有了这个前提以及终极目标,TCP源端口的选择算法就非常容易理解了。在以下的情况下需要算法来选择一个源端口:
1.调用bind,但是bind的端口是0的时候;
2.没有调用bind,直接调用connect的时候。
这两种情况使不同的,因为在第一种情况下,4元组中的目标IP和目标端口是不确定的,而在第二种情况下,除了源端口,其它的都是知道的。所以两种情况的端口分配算法是不同的。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define LOW 10000 #define HIGH 65535 //端口分配函数 //base:一个(源IP,目标IP,目标端口)三元组的hash值 int get_local_port(int base ) { unsigned int i,j,port, remaining; again: remaining = (HIGH - LOW) + 1; //采用随机的方式更容易找到空闲端口 port = LOW + random()%remaining; for (i = 1; i <= remaining; i++) { int port_ok = 0; //判断该端口是否可用,由于四元组唯一性现在由于信息不全无法判断,先检查最容易匹配的: //端口没有处在TW状态,非LISTEN状态,可用 //此处要保证的是,聚集者要越往外越少。 port_ok = 1; if (port_ok) { goto check_inner; } //如果不合适就以port为基准,递增 port ++; } check_inner: { //更深层次,但更耗时的判断 int port_inner_ok; port_inner_ok = 1; if (!port_inner_ok) { goto again; } } return port; } //分配端口函数 void func() { while(1) { int port = get_local_port(0); printf(" %d \n", port); sleep(1); } } //main函数 int main(int argc, char **argv) { func(); return 0; }
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define LOW 10000 #define HIGH 65535 static unsigned int hint = 0; //端口分配函数 //base:一个(源IP,目标IP,目标端口)三元组的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; //判断该端口是否可用,由于仅四元组唯一即可接受,现在假设: //所有的端口均已经安全释放。 port_ok = 1; if (port_ok) { break; } } //越过你排除的那几个(那些!) hint += i; return port; } //hint递增函数 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(); //其它的线程选择源端口的时候,由于使用不同的(源IP,目标IP,目标端口) //不会有任何冲突,因此只模拟递增hint即可。 inc_hint (1); sleep(1); } } //main函数 int main(int argc, char **argv) { pthread_t id[20] = {0}; int i = 0, ret = 0; //一个线程不断分配端口 ret = pthread_create(&id[0], NULL, (void*)func, NULL); if (ret) { printf("Create pthread error!/n"); return 1; } //N个线程模拟其它的端口分配,仅仅递增hint for (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; }
列维模型一直都在主宰着我们,并且工作的很好,由于列维模型,我们现在拥有了典型的几个不错的国际化大都市...列维模型正如磁石一样在发挥着作用。它本质上就是要把同类同质的东西聚集在一起!Linux在bind的时候分配端口正是使用这种列维搜索方式。列维模型天生具备一个阀值,即,在由于列维模型聚集在一起的东西超过阀值后,将不再聚集,而是选择“长跳”,即随机到达一个比较远的地方重新开始聚集!列维模型总结起来就是,局部搜索,达到范围阀值后,到一个很远但是随机的地方重新开始局部搜索!如下图所示: