connect和bind0
socket通信中,客户端需要选取随机端口和服务端进行连接。通常情况下,使用相关的系统调用即可,相关函数中会实现选取随机端口的逻辑。比如TCP使用connect()来连接,UDP使用sendto()来连接。另一种方法是使用bind(0)进行随机选择端口。
随机端口选取测试
getRandomPortConnectTest.c,使用connect系统调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> void print_local_port () { int sockfd; if (sockfd = socket(AF_INET, SOCK_STREAM, 0 ), -1 == sockfd) { perror("socket create error" ); } const struct sockaddr_in remote_addr = { .sin_family = AF_INET, .sin_port = htons(22 ), .sin_addr = htonl(INADDR_ANY) }; if (connect(sockfd, (const struct sockaddr *) &remote_addr, sizeof (remote_addr)) < 0 ) { perror("connect error" ); } const struct sockaddr_in local_addr ; socklen_t local_addr_len = sizeof (local_addr); if (getsockname(sockfd, (struct sockaddr *) &local_addr, &local_addr_len) < 0 ) { perror("getsockname error" ); } printf ("local port: %d\n" , ntohs(local_addr.sin_port)); close(sockfd); } int main () { int i; for (i = 0 ; i < 20 ; i++) { print_local_port(); } return 0 ; }
getRandomPortBindTest.c,使用bind,端口写0,进行随机选取端口。测试中,bind选取端口绑定后,再进行listen调用,作作为监听端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> int create_and_bind0_and_listen () { int sockfd; if (sockfd = socket(AF_INET, SOCK_STREAM, 0 ), -1 == sockfd) { perror("socket create error" ); } const struct sockaddr_in serv_addr = { .sin_family = AF_INET, .sin_port = htons(0 ), .sin_addr = htonl(INADDR_ANY) }; if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0 ) { perror("bind error" ); } const struct sockaddr_in server_addr ; socklen_t server_addr_len = sizeof (server_addr); if (getsockname(sockfd, (struct sockaddr *) &server_addr, &server_addr_len) < 0 ) { perror("getsockname error" ); } printf ("bind server port: %d\n" , ntohs(server_addr.sin_port)); if (listen(sockfd, 88 ) < 0 ) { perror("listen failed" ); exit (1 ); } return sockfd; } int main () { int fd_list[10 ]={0 }; int i; for (i = 0 ; i < 10 ; i++) { int sfd=create_and_bind0_and_listen(); fd_list[i]=sfd; } printf ("socket均监听完成,10s后关闭...\n" ); sleep(10 ); for (i = 0 ; i < 10 ; i++) { close(fd_list[i]); } return 0 ; }
测试结果
可见connect选取的随机端口为连续的偶数端口 ,bind0随机选取的端口则为奇数 。
4.2内核的相关改变
patch地址:kernel/git/torvalds/linux.git - Linux kernel source tree
提出patch的原因可以简述为:
local port范围太小是内核中一直存在的问题,当使用connect系统调用默认使用连续的随机端口后,高并发场景下,如果再使用bind(0)进行随机端口绑定,bind可能会fail,即使success,也需要花大量时间来从前往后寻找可用的随机端口。
主要修改如下:
其中,offset &= ~1
能保证为偶数。
具体内核代码如下:
sys_bind最终调用到inet_csk_find_open_port
,保证获取的源端口是奇数:
connect最终调用__inet_hash_connect
保证源端口是偶数: