使用setsockopt实现端口复用

Posted milaiko

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用setsockopt实现端口复用相关的知识,希望对你有一定的参考价值。

端口复用

使用场景

我们知道主动关闭连接方,最终会进入一个状态——timewait, 而当服务器主动关闭的时候,它会进入这个状态并等待2MSL的时长。

假设一个场景,当服务器需要更新时, 需要服务器主动关闭连接,这时候服务器就需要等待2msl才能使用这个端口,对于用户而言,2msl的时长太长,(linux的2msl时长大约有40s到60s),所以需要复用这个端口,使得服务端能够正常运行。

函数简介

#inlude<sys/socket.h>

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t *optlen);
  • level(级别)指定系统中解释选项的代码为通用套接字代码,或为某个特定协议的代码。

  • optval是一个指向某个变量(*optval)的指针,setsockopt从*optval中取得选项待设置的新值,getsockopt则把已获得的选项当前值存放到*optval中。

对于端口复用的实现, 需要设置level,optname

  • level(级别) 选为SOL_SOCKET

  • optname设为SO_REUSEADDR(允许重用本地地址)或者SO_REUSEPORT(允许重用本地端口)

其中opt_val = 1表示启用,为0表示禁用。

实现例子

注意:这个是基于多线程实现的服务器端

server.cpp

#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<ctype.h>
#include<pthread.h>
#include<string.h>
#include<stdio.h>

#include"wrap.h"

const int  MAXVALUE = 8192;
const int  SERV_PROT = 8888;

//定义一个结构体将客户端地址和套接字绑定起来
struct s_info{
    struct sockaddr_in cliaddr;
    int connfd;
};

void* do_work(void *arg){								//子线程
    int n,i;
    struct s_info *ts = (struct s_info*)arg;
    char buf[MAXVALUE];
    char str[INET_ADDRSTRLEN];                          //define Inet_addrstrlen 16 
    while(1){
        n  = Read(ts->connfd, buf, MAXVALUE);
        if(n == 0){
            printf("the client %d closed...\\n", ts->connfd);
            break;				
        }
        printf("recieved from %s at port %d\\n", 
        inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)), 
        ntohs((*ts).cliaddr.sin_port));                 //note inet_ntop 网络字节序转换为ip地址,返回的是str的地址,

        for(i = 0;i<n;i++){
            buf[i] = toupper(buf[i]); 
        }

        Write(STDOUT_FILENO, buf, n);
        Write(ts->connfd, buf, n);
    }
    Close(ts->connfd);
    return (void*)0;
    //pthread_exit(0);
}


int main(){
    struct sockaddr_in seraddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    pthread_t tid;

    struct s_info ts[256];
    int i=0;
    //创建套接字
    listenfd = Socket(AF_INET, SOCK_STREAM, 0);
	//设置端口复用 
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));
	//设置服务器地址结构体
    bzero(&seraddr, sizeof(seraddr));
    seraddr.sin_family = AF_INET;
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    seraddr.sin_port = htons(SERV_PROT);
	//绑定套接字
    Bind(listenfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
    //监听套接字
    Listen(listenfd, 128);
    printf("Accepting client connect ......");

    while(1){
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);    //阻塞监听客户端请求
        ts[i].cliaddr = cliaddr;
        ts[i].connfd = connfd;
		//创建子线程
        pthread_create(&tid, NULL, do_work, (void*)&ts[i]); 
        pthread_detach(tid);                                                    //子线程分离,防止僵尸线程
        i++;
    }
    return 0;
}

实践

第一步

将当前目录下的server.cpp编译并启动

g++ server.cpp -o server -pthread

./server

第二步

开启另一个终端,并使用nc作为客户端与服务端连接 格式 nc 127.0.0.1 端口号

nc 127.0.0.1 8888

第三步

先将服务端关了,再将客户端关了,模拟服务端变成timewait状态,因为进入了timewait状态需要等待2msl时长才会真正close,而端口复用允许它复用8888这个端口和客户端通信。

为了验证一下,我们用netstat来查看8888这个端口的状态

sudo netstat -apn | grep 8888


output:

tcp        0      0 127.0.0.1:8888          127.0.0.1:47780         TIME_WAIT

第四步

重新开服务器

./server

并在另一个shell查看

sudo netstat -apn| grep 8888

tcp        0      0 0.0.0.0:8888            0.0.0.0:*               LISTEN      5346/./mult_pthread 
tcp        0      0 127.0.0.1:8888          127.0.0.1:47780         TIME_WAIT   -          

发现

这里的端口复用并不是将time_wait提前结束了,为了保证tcp的可靠性,time_wait依然存在,只不过是重新利用这个端口进行另一个通信罢了。

发现8888有两个状态 listen 和time_wait, 如果把setsockopt给关掉了,又会怎样?

以上是关于使用setsockopt实现端口复用的主要内容,如果未能解决你的问题,请参考以下文章

TCP端口复用引发的异常,用setsockopt来解决

套接字 的端口重用 作用 是啥??

socket设置地址复用

套接字编程 C++ setsockopt()

setsockopt 返回错误 10014

HaProxy 实现443 端口复用