zoukankan      html  css  js  c++  java
  • socket常见选项之SO_REUSEADDR,SO_REUSEPORT

    目录
    SO_REUSEADDR

    一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用

    SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用
    server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项
    TCP,先调用close()的一方会进入TIME_WAIT状态

    SO_REUSEADDR提供如下四个功能:

    • 允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错
    • 允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
    • 允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
    • SO_REUSEADDR允许完全重复的捆绑:
      当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。
    SO_REUSEPORT选项有如下语义:

    此选项允许完全重复捆绑,但仅在想捆绑相同IP地址和端口的套接口都指定了此套接口选项才行。

    如果被捆绑的IP地址是一个多播地址,则SO_REUSEADDR和SO_REUSEPORT等效。

    使用这两个套接口选项的建议:

    • 在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR套接口选项;
    • 当编写一个同一时刻在同一主机上可运行多次的多播应用程序时,设置SO_REUSEADDR选项,并将本组的多播地址作为本地IP地址捆绑

    time-wait

    TIME_WAIT状态有两个存在的理由:
    • (1)可靠地实现TCP全双工连接的终止
    • (2)允许老的重复分节在网络中消逝

    如图所示,在主机A的4次挥手过程中,如果最后的数据丢失,则主机B会认为A未能收到自己发送的FIN消息,因此重传。这时,收到FIN消息的主机A将重启time-wait计时器。因此,如果网络状态不佳,time-wait状态将持续

    (1)如果服务器最后发送的ACK因为某种原因丢失了,那么客户一定会重新发送FIN,这样因为有TIME_WAIT的存在,服务器会重新发送ACK给客户,如果没有TIME_WAIT,那么无论客户有没有收到ACK,服务器都已经关掉连接了,此时客户重新发送FIN,服务器将不会发送ACK,而是RST,从而使客户端报错。也就是说,TIME_WAIT有助于可靠地实现TCP全双工连接的终止。

    (2)如果没有TIME_WAIT,我们可以在最后一个ACK还未到达客户的时候,就建立一个新的连接。那么此时,如果客户收到了这个ACK的话,就乱套了,必须保证这个ACK完全死掉之后,才能建立新的连接。也就是说,TIME_WAIT允许老的重复分节在网络中消逝。

      回到我们的问题,由于我并不是正常地经过四次断开的方式中断连接,所以并不会存在最后一个ACK的问题。所以,这样是安全的。不过,最终的服务器版本,还是不要设置为端口可复用的

    <?php
    $address = '0.0.0.0';
    $port = $argv[1] ?? 8071;
    $listen = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    if (false === $listen) errhandle(__LINE__);
    //ctrl+c重启时 能立马重启,要在bind,listen之前
    if (true !== socket_set_option($listen, SOL_SOCKET, SO_REUSEADDR, 1)) errhandle(__LINE__);;
    
    if (true !== socket_bind($listen, '0.0.0.0', $port)) errhandle(__LINE__);;
    if (true !== socket_listen($listen, 5)) errhandle(__LINE__);;  //待连接队列长度
    //socket_set_nonblock($listen);
    
    echo "Server linsten on:{$address}:$port" . PHP_EOL;
    
    while (true) {
        //连接socket,处理连接的接入
        $sock_client = socket_accept($listen);
        if (false === $sock_client) {
            errhandle(__LINE__,false);
            continue;
        }
        processClientConn($sock_client);
    }
    
    //处理已经连入的连接
    function processClientConn($sock_client)
    {
        if (socket_getpeername($sock_client, $clinet_addr, $client_port)) {
            echo "New client " . intval($sock_client) . " come from  {$clinet_addr}:$client_port" . PHP_EOL;
            sayWelcome($sock_client);
        }
        while (true) {
            //接收到不少于len
            $len = socket_recv($sock_client, $buf, 2048, 0);
            if ($len === false) {
                echo "no data" . PHP_EOL;
                continue;
            } elseif ($len === 0) {
                errhandle(__LINE__,false);
                socket_shutdown($sock_client);
                break;
            } else {
                echo "recv:{" . $buf . "}len=" . $len . PHP_EOL;
                if ($buf == 'quit') {
                    socket_shutdown($sock_client);
                    break;
                }
            }
        }
    }
    
    function errhandle($line_num,$exit=true)
    {
        echo $line_num.":".socket_last_error() . ":" . socket_strerror(socket_last_error()) . PHP_EOL;
        if($exit){
            exit();
        }
    
    }
    function sayWelcome($client)
    {
        $buf = date("H:i:s") . " welcome to server! you id:" . intval($client) . PHP_EOL;
        socket_write($client, $buf, strlen($buf));
    }
    
    

    SO_REUSEPORT

    目前常见的网络编程模型就是多进程或多线程,根据accpet的位置,分为如下场景
    2种场景

    • (1)单进程或线程创建socket,并进行listen和accept,接收到连接后创建进程和线程处理连接
    • (2)单进程或线程创建socket,并进行listen,预先创建好多个工作进程或线程accept()在同一个服务器套接字

    这两种模型解充分发挥了多核CPU的优势,虽然可以做到线程和CPU核绑定,但都会存在:

    • 单一listener工作进程或线程在高速的连接接入处理时会成为瓶颈
    • 多个线程之间竞争获取服务套接字
    • 缓存行跳跃
    • 很难做到CPU之间的负载均衡
    • 随着核数的扩展,性能并没有随着提升

    SO_REUSEPORT解决了什么问题

    SO_REUSEPORT支持多个进程或者线程绑定到同一端口,提高服务器程序的性能,解决的问题:

    • 允许多个套接字 bind()/listen() 同一个TCP/UDP端口
    • 每一个线程拥有自己的服务器套接字
    • 在服务器套接字上没有了锁的竞争
    • 内核层面实现负载均衡
    • 安全层面,监听同一个端口的套接字只能位于同一个用户下面

    其核心的实现主要有三点:

    • 扩展 socket option,增加 SO_REUSEPORT 选项,用来设置 reuseport
    • 修改 bind 系统调用实现,以便支持可以绑定到相同的 IP 和端口
    • 修改处理新建连接的实现,查找 listener 的时候,能够支持在监听相同 IP 和端口的多个 sock 之间均衡选择。
      有了SO_RESUEPORT后,每个进程可以自己创建socket、bind、listen、accept相同的地址和端口,各自是独立平等的
      让多进程监听同一个端口,各个进程中accept socket fd不一样,有新连接建立时,内核只会唤醒一个进程来accept,并且保证唤醒的均衡性。

    https://www.cnblogs.com/Anker/p/7076537.html

    http://www.blogjava.net/yongboy/archive/2015/02/12/422893.html

    没用reuseport的
    socket_fork_no_reuseport.php

    用了的
    socket_fork_reuseport.php

  • 相关阅读:
    面试题库
    集合的交、查、并
    mysql_server安装
    maven构建jar包
    Centos7下 升级php5.4到7.1 yum安装
    redis
    常见shell用法
    Mac下的LDAP客户端 ApacheDirectoryStudio
    redis弱密码漏洞利用
    Freeradius+Cisco2500AC+OpenLdap认证
  • 原文地址:https://www.cnblogs.com/HKUI/p/11707170.html
Copyright © 2011-2022 走看看