同一端口的两个TCP客户端连接不同服务器失败问题咨询
问题解答
背景
TCP套接字由源IP/源端口/目的IP/目的端口四元组唯一标识,因此同一IP下使用相同端口的两个TCP客户端,若连接不同服务器,理论上是可行的。
在MacBook上的测试步骤:
- 两台服务器(A和B)监听不同端口
- 第一个客户端绑定到端口1234并连接服务器A,成功建立连接
- 第二个客户端绑定到同一端口1234并连接服务器B,失败并返回错误码48(
Address already in use)
问题
理论上只要目的IP或端口不同,同一IP的相同端口可绑定多个TCP套接字,但实际操作系统不允许,仅服务器端处理连接请求时可分配新套接字。请问这个理解是否有误?
测试代码运行方法
编译
gcc server.c -o server gcc client.c -o client
运行
./server 9000 # 端口9000的服务器A ./server 8000 # 端口8000的服务器B ./client 9000 1234 # 第一个客户端用本地端口1234连接服务器A,成功 ./client 8000 1234 # 第二个客户端用本地端口1234连接服务器B,失败
代码
server.c
#include <stdio.h> #include <netdb.h> #include <netinet/in.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> // read(), write(), close() #define MAX 80 #define SA struct sockaddr // Function designed for chat between client and server. void func(int connfd) { char buff[MAX]; int n; // infinite loop for chat for (;;) { bzero(buff, MAX); // read the message from client and copy it in buffer read(connfd, buff, sizeof(buff)); // print buffer which contains the client contents printf("From client: %s\t To client : ", buff); bzero(buff, MAX); n = 0; // copy server message in the buffer while ((buff[n++] = getchar()) != '\n') ; // and send that buffer to client write(connfd, buff, sizeof(buff)); // if msg contains "Exit" then server exit and chat ended. if (strncmp("exit", buff, 4) == 0) { printf("Server Exit...\n"); break; } } } // Driver function int main(int argc, char **argv) { int sockfd, connfd, len; struct sockaddr_in servaddr, cli; // socket create and verification sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { printf("socket creation failed...\n"); exit(0); } else printf("Socket successfully created..\n"); bzero(&servaddr, sizeof(servaddr)); // assign IP, PORT int port = atoi(argv[1]); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); // Binding newly created socket to given IP and verification if ((bind(sockfd, (SA*)&servaddr, sizeof(servaddr))) != 0) { printf("socket bind failed...\n"); exit(0); } else printf("Socket successfully binded..\n"); // Now server is ready to listen and verification if ((listen(sockfd, 5)) != 0) { printf("Listen failed...\n"); exit(0); } else printf("Server listening..\n"); len = sizeof(cli); // Accept the data packet from client and verification connfd = accept(sockfd, (SA*)&cli, &len); if (connfd < 0) { printf("server accept failed...\n"); exit(0); } else printf("server accept the client...\n"); // Function for chatting between client and server func(connfd); printf("close connection directly"); // After chatting close the socket close(sockfd); }
client.c
#include <arpa/inet.h> // inet_addr() #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> // bzero() #include <sys/socket.h> #include <unistd.h> // read(), write(), close() #include <errno.h> #define MAX 80 #define SA struct sockaddr void func(int sockfd) { char buff[MAX]; int n; for (;;) { bzero(buff, sizeof(buff)); printf("Enter the string : "); n = 0; while ((buff[n++] = getchar()) != '\n') ; long writeRes = write(sockfd, buff, sizeof(buff)); printf("wrtie result : %d\n", writeRes); bzero(buff, sizeof(buff)); long readRes = read(sockfd, buff, sizeof(buff)); printf("read result : %d\n", readRes); printf("From Server : %s", buff); if ((strncmp(buff, "exit", 4)) == 0) { printf("Client Exit...\n"); break; } } } int main(int argc, char **argv) { int sockfd, connfd; struct sockaddr_in servaddr, cliaddr; // socket create and verification sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { printf("socket creation failed...\n"); exit(0); } else printf("Socket successfully created..\n"); bzero(&servaddr, sizeof(servaddr)); //////////////////////////////////////// // bind client socket to specific address bzero(&cliaddr, sizeof(cliaddr)); // assign client IP, PORT int cliport = atoi(argv[2]); cliaddr.sin_family = AF_INET; cliaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); cliaddr.sin_port = htons(cliport); // Binding client socket to given IP and verification if ((bind(sockfd, (SA*)&cliaddr, sizeof(cliaddr))) != 0) { printf("client socket bind failed...\n"); printf("Error code: %d\n", errno); exit(0); } else printf("client socket successfully binded..\n"); //////////////////////////////////////// // assign server IP, PORT int serverport = atoi(argv[1]); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); servaddr.sin_port = htons(serverport); // connect the client socket to server socket if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0) { printf("connection with the server failed...\n"); exit(0); } else printf("connected to the server..\n"); // function for chat func(sockfd); // close the socket close(sockfd); }
核心解答
你的理论理解是正确的:TCP连接由**四元组(源IP/源端口/目的IP/目的端口)**唯一标识,所以同一本地IP+端口,只要连接的目的地址(IP或端口)不同,就可以存在多个独立连接。
为什么默认bind失败?
默认情况下,操作系统的bind系统调用会做严格检查:只要本地IP+端口已经被任何套接字绑定(不管该套接字的连接目的是什么),就会返回EADDRINUSE(错误码48)。这是系统的默认安全限制,防止意外的端口冲突。
如何实现预期的场景?
需要在调用bind之前,给客户端套接字设置两个套接字选项:
int opt = 1; // 允许绑定到已被占用的本地端口(只要四元组不冲突) setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 允许多个套接字绑定同一本地IP+端口(macOS/Linux需要) setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
修改后的客户端main函数开头部分示例:
// socket create and verification sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { printf("socket creation failed...\n"); exit(0); } else printf("Socket successfully created..\n"); // 设置套接字选项 int opt = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)); bzero(&servaddr, sizeof(servaddr));
关于服务器端的差异
服务器端listen后能接受多个连接,是因为accept会为每个新连接创建全新的套接字,这些新套接字的四元组是(客户端IP/客户端端口/服务器IP/服务器端口),和监听套接字的四元组(0.0.0.0/服务器端口/任意/任意)完全不同,所以不会产生冲突。
内容的提问来源于stack exchange,提问作者obanadingyo
相关产品推荐
相关产品推荐

