如果在本地访问时,使用了一个源和目的端口都相同的 TCP 链接,会出现什么情况?
简介
在同一台机器上部署的程序,通常会通过 Socket 进行通讯,如果监听的端口刚好在随机分配的端口范围内,那么就有可能出现源和目的端口相同的情况。
这个问题出现的概率很低,但是一旦出现相同的通讯就会发生异常。
原理
对于客户端,如果没有调用 bind()
设置端口,那么当调用 connect()
函数时会为套接字选择一个临时接口,那么,当服务端监听的端口也在这一范围内时,那么就有可能出现客户端和服务端端口相同的情况。
一般只会发生在本地,如果是远程,那么至少其 IP 地址是不同的。
也就是在当前机器上绑定了客户端和服务端都是相同的端口,假设在内核中创建了一个套接字 sk
,对应的上述的 IP:Port ,那么,基本处理流程如下:
- 客户端发送 SYN 请求,服务端接收到 SYN 之后,通过
__inet_lookup()
查找内核中的套接字,也就是上述的sk
,状态迁移到SYN_RCVD
; - 然后发送
SYN+ACK
响应,套接字迁移到ESTABLISED
状态。
注意,此时收到的序号要比 SYN+ACK
段的序号大 1 ,相当于收到了重复的段,会再发一个 D-ACK
标识收到了重复段,但是不影响状态。
测试代码
竟然仍可以发送接收数据。
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
int main(void)
{
char buff[1024] = "Hello World!";
int rc, sockfd, i;
struct sockaddr_in addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
fprintf(stderr, "create socket failed, %d:%s.\n", errno, strerror(errno));
return -1;
}
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(9090);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
rc = bind(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr));
if (rc < 0) {
fprintf(stderr, "bind socket failed, %d:%s.\n", errno, strerror(errno));
close(sockfd);
return -1;
}
rc = connect(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr));
if (rc < 0) {
fprintf(stderr, "connect failed, %d:%s.\n", errno, strerror(errno));
close(sockfd);
return -1;
}
#if 0
for (i = 0; i < 3; i++) {
rc = send(sockfd, buff, strlen(buff), 0);
rc = recv(sockfd, buff, sizeof(buff) - 1, 0);
buff[rc] = 0;
printf("received:%s\n", buff);
//rc = scanf("%3s", sizeof(buff) - 1, buff);
//rc = scanf("%3s", buff);
//printf("%d %s\n", rc, buff);
}
#else
rc = recv(sockfd, buff, sizeof(buff) - 1, 0);
if (rc < 0) {
fprintf(stderr, "connect failed, %d:%s.\n", errno, strerror(errno));
close(sockfd);
return -1;
}
buff[rc] = 0;
#endif
close(sockfd);
return 0;
}