美文网首页
【tcp】关于用本地同一个ip:port跟不同服务端建立tcp连

【tcp】关于用本地同一个ip:port跟不同服务端建立tcp连

作者: Bogon | 来源:发表于2023-06-29 00:06 被阅读0次

    我们看一个案例

    # echo  > /dev/tcp/www.example.com/8090
    
    # ss  -tan | grep TIME-WAIT
    TIME-WAIT  0      0      172.22.54.177:58044              60.29.63.124:8090
    
    
    # curl  -sSI  --local-port   58044   https://www.baidu.com
    curl: (45) bind failed with errno 98: Address already in use
    
    

    这段代码展示了一系列命令和输出。解读如下:

    1. 第一个命令 echo > /dev/tcp/www.example.com/8090 是一个简单的方法来测试网络连接。它尝试在指定的主机和端口上打开一个TCP连接。如果连接成功,命令会立即退出,否则会报错。

    2. 第二个命令 ss -tan | grep TIME-WAIT 用于列出当前系统中处于TIME-WAIT状态的TCP连接。TIME-WAIT状态是指在TCP连接关闭后,等待一段时间以确保所有相关数据都被正确处理的状态。

    3. 输出结果显示了一个处于TIME-WAIT状态的TCP连接,其中本地IP地址是172.22.54.177,本地端口是58044,远程IP地址是60.29.63.124,远程端口是8090。

    4. 第三个命令 curl -sSI --local-port 58044 https://www.baidu.com 是一个HTTP请求命令,尝试通过指定本地端口(58044)来访问百度网站。然而,由于该端口已经被之前的TCP连接占用,curl命令无法绑定到该端口,因此返回了一个错误信息。

    综上所述,这段代码的目的是测试网络连接和查看处于TIME-WAIT状态的TCP连接,同时展示了一个端口被占用的错误情况。

    网络编程中的SO_REUSEADDR和SO_REUSEPORT

    SO_REUSEADDR

    目前为止我见到的设置SO_REUSEADDR的使用场景:

    server端在调用bind函数时

    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));
    

    目的:当服务端(注意不是客户端)出现time-wait状态的链接时,确保server能够重启成功。

    注意:SO_REUSEADDR只有针对time-wait链接(linux系统time-wait连接持续时间为1min),确保server重启成功的这一个作用,至于网上有文章说:如果有socket绑定了0.0.0.0:port;设置该参数后,其他socket可以绑定本机ip:port。
    本人经过试验后均提示“Address already in use”错误,绑定失败。

    举个例子: server监听9980端口,由于主动关闭链接,产生了一个time-wait状态的链接

    image.png

    如果此时server重启,并且server没有设置SO_REUSEADDR参数,server重启失败,报错:“Address already in use”
    如果设置SO_REUSEADDR,重启ok。

    SO_REUSEPORT

    SO_REUSEPORT使用场景:Linux kernel 3.9 引入了最新的SO_REUSEPORT选项,使得多进程或者多线程创建多个绑定同一个ip:port的监听socket,提高服务器的接收链接的并发能力,程序的扩展性更好,此时需要设置SO_REUSEPORT(注意所有进程都要设置才生效)。

    setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));
    

    目的:每一个进程有一个独立的监听socket,并且bind相同的ip:port,独立的listen()和accept();提高接收连接的能力。(例如nginx多进程同时监听同一个ip:port)

    解决的问题:

    (1)避免了应用层多线程或者进程监听同一ip:port的“惊群效应”。

    (2)内核层面实现负载均衡,保证每个进程或者线程接收均衡的连接数。

    (3)只有effective-user-id相同的服务器进程才能监听同一ip:port (安全性考虑)

    代码示例:server_128.c

    1 #include <stdio.h>                                                                                                               
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <netinet/in.h>
     5 #include <sys/socket.h>
     6 #include <arpa/inet.h>
     7 #include <sys/types.h>
     8 #include <errno.h>
     9 #include <time.h>
    10 #include <unistd.h>
    11 #include <sys/wait.h>
    12 void work () {
    13         int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    14         if (listenfd < 0) {
    15                 perror("listen socket");
    16                 _exit(-1);
    17         }
    18         int ret = 0;
    19         int reuse = 1;
    20         ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));
    21         if (ret < 0) {
    22                 perror("setsockopt");
    23                 _exit(-1);
    24         }
    25         ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));
    26         if (ret < 0) {
    27             perror("setsockopt");
    28             _exit(-1);
    29         }
    30         struct sockaddr_in addr;
    31         memset(&addr, 0, sizeof(addr));
    32         addr.sin_family = AF_INET;
    33         //addr.sin_addr.s_addr = inet_addr("10.95.118.221");
    34         addr.sin_addr.s_addr = inet_addr("0.0.0.0");                                                                             
    35         addr.sin_port = htons(9980);
    36         ret = bind(listenfd, (struct sockaddr *)&addr, sizeof(addr));
    37         if (ret < 0) {
    38                 perror("bind addr");
    39                 _exit(-1);
    40        }
    41         printf("bind success\n");
    42         ret = listen(listenfd,10);
    43         if (ret < 0) {
    44                 perror("listen");
    45                 _exit(-1);
    46         }
    47         printf("listen success\n");
    48         struct sockaddr clientaddr;
    49         int len = 0;
    50         while(1) {
    51                 printf("process:%d accept...\n", getpid());
    52                 int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
    53                 if (clientfd < 0) {
    54                         printf("accept:%d %s", getpid(),strerror(errno));
    55                         _exit(-1);
    56                 }
    57                 close(clientfd);
    58                 printf("process:%d close socket\n", getpid());
    59         }
    60 }
    61 int main(){
    62         printf("uid:%d euid:%d\n", getuid(),geteuid());
    63         int i = 0;
    64         for (i = 0; i< 6; i++) {
    65                 pid_t pid = fork();
    66                 if (pid == 0) {
    67                         work();
    68                 }
    69                 if(pid < 0) {
    70                         perror("fork");
    71                         continue;
    72                 }
    73         }
    74         int status,id;
    75         while((id=waitpid(-1, &status, 0)) > 0) {
    76                 printf("%d exit\n", id);
    77         }
    78         if(errno == ECHILD) {
    79                 printf("all child exit\n");
    80         }
    81         return 0;
    82 }
    

    上述示例程序,启动了6个子进程
    每个子进程创建自己的监听socket,bind相同的ip:port;
    独立的listen(),accept();
    如果每个子进程不设置SO_REUSEADDR选项,会提示“Address already in use”错误。

    安全性:是不是只要设置了SO_REUSEPORT选项的服务器程序,就能够监听相同的ip:port 并获取报文呢?
    当然不是,当前Linux内核要求后来启动的服务器程序与前面启动的服务器程序的effective-user-id要相同。

    image.png

    上图中的server_127和server128两个服务器程序都设置了SO_REUSEPORT,监听同一ip:port,用root用户启动两个服务器程序,因为effective-user-id相等,都为0,所以启动没问题。

    修改server127的权限:chmod u+s server_127。

    image.png

    启动完server_128后,在启动server_127,提示错误: Address already in use。

    image.png

    参考

    网络编程中的SO_REUSEADDR和SO_REUSEPORT参数详解
    https://blog.51cto.com/u_15076236/4191893

    网络编程:SO_REUSEADDR的使用
    https://zhuanlan.zhihu.com/p/79999012

    深入理解Linux端口重用这一特性
    https://mp.weixin.qq.com/s/SYCUMvzktgeGbyAfRdqhmg

    相关文章

      网友评论

          本文标题:【tcp】关于用本地同一个ip:port跟不同服务端建立tcp连

          本文链接:https://www.haomeiwen.com/subject/wqwoydtx.html