网络编程:端口复用
问题
当通过服务端发起的关闭连接操作,引发了一个已有的TCP连接处于TIME_WAIT状态,此时,服务器重启,继续绑定原来ip与端口号,返回了Address already in use的错误。
重用套接字选项
一个TCP连接时通过四元组(源地址、源端口、目的地址、目的端口)来唯一确定。
通过重用套接字选项,通过给套接字配置可重用属性,告诉操作系统内核,TCP连接完全可以复用TIME_WAIT状态的连接。
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
SO_REUSEADDR
套接字选项,允许启动绑定在一个端口,即使之前存在一个和该端口一样的连接。
SO_REUSEADDR
套接字选项还有一个作用,就是本机服务器如果有多个地址,可以在不同地址上使用相同的端口提供服务。
比如,一台服务器有192.168.1.101和10.10.2.102两个地址,可以在这台服务器上启动三个不同的HTTP服务,第一个以本地通配地址ANY和端口80启动;第二个以192.168.1.101和端口80启动;第三个以10.10.2.102和端口80启动。
目的地址为192.168.1.101,端口为80的连接请求会被发往第二个服务;其他两个类似
实践
服务器端:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define SERV_PORT 43211
#define LISTENQ 1024
#define MAXLINE 4096
static int count;
int main(int argc, char *argv[])
{
int listenfd;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERV_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
int rt1 = bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(rt1 < 0)
{
perror("bind failed");
return -1;
}
int rt2 = listen(listenfd, LISTENQ);
if(rt2 < 0)
{
perror("listen failed");
return -1;
}
signal(SIGPIPE, SIG_IGN);
int connfd;
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
if((connfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addr)) < 0)
{
perror("accept failed");
return -1;
}
char message[MAXLINE];
count = 0;
for(;;)
{
int n = read(connfd, message, MAXLINE);
if(n < 0)
{
perror("error read");
return -1;
}
else if(n == 0)
{
printf("client close");
return 0;
}
message[n] = 0;
printf("received %d bytes;%s\n",n,message);
count++;
}
}
通过Telnet客户端登录ip和端口
服务端:
小结
服务端程序,都应该加上SO__REUSEADDR
套接字选项,以便服务端程序可以在极短时间内服用同一个端口启动
TCP连接时通过四元组唯一区分,只要客户端不使用相同的源端口,连接服务器集OK,即使使用了相同的端口,更具序列号或时间戳,也可以区分新旧连接
PS:
在TIME_WAIT一讲中,提到一个tcp_tw_reuse
的内核配置和本节SO_REUSEADDR没有一点关系。
tcp_tw_reuse
是内核选项,主要用在连接的发起方,TIME_WAIT
状态的连接创建时间超过1秒后,新的连接才可以被复用,注意,是连接的发起方
SO_REUSEADDR
是用户态的选项,SO_REUSEADDR
选项用来告诉操作系统内核,如果端口被占用,但是TCP连接状态位于TIME_WAIT,可以重用端口。如果端口忙,而TCP处于其他状态,重用端口时依旧得到“Address already in use”的错误信息。注意,这里一般是连接的服务方