connect和bind0
socket通信中,客户端需要选取随机端口和服务端进行连接。通常情况下,使用相关的系统调用即可,相关函数中会实现选取随机端口的逻辑。比如TCP使用connect()来连 接,UDP使用sendto()来连接。另一种方法是使用bind(0)进行随机选择端口。
随机端口选取测试
getRandomPortConnectTest.c,使用connect系统调用:
#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调用,作作为监听端口:
#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保证源端口是偶数: