网络编程:端口复用

问题

当通过服务端发起的关闭连接操作,引发了一个已有的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”的错误信息。注意,这里一般是连接的服务方
posted @ 2022-03-18 00:12  牛犁heart  阅读(568)  评论(0编辑  收藏  举报