socket常見選項之SO_REUSEADDR,SO_REUSEPORT

目錄
SO_REUSEADDR

通常來講,一個端口釋放後會等待兩分鐘以後才能再被使用,SO_REUSEADDR是讓端口釋放後當即就能夠被再次使用php

SO_REUSEADDR用於對TCP套接字處於TIME_WAIT狀態下的socket,才能夠重複綁定使用
server程序老是應該在調用bind()以前設置SO_REUSEADDR套接字選項
TCP,先調用close()的一方會進入TIME_WAIT狀態html

SO_REUSEADDR提供以下四個功能:

  • 容許啓動一個監聽服務器並捆綁其衆所周知端口,即便之前創建的將此端口用作他們的本地端口的鏈接仍存在。這一般是重啓監聽服務器時出現,若不設置此選項,則bind時將出錯
  • 容許在同一端口上啓動同一服務器的多個實例,只要每一個實例捆綁一個不一樣的本地IP地址便可。對於TCP,咱們根本不可能啓動捆綁相同IP地址和相同端口號的多個服務器。
  • 容許單個進程捆綁同一端口到多個套接口上,只要每一個捆綁指定不一樣的本地IP地址便可。這通常不用於TCP服務器。
  • SO_REUSEADDR容許徹底重複的捆綁:
    當一個IP地址和端口綁定到某個套接口上時,還容許此IP地址和端口捆綁到另外一個套接口上。通常來講,這個特性僅在支持多播的系統上纔有,並且只對UDP套接口而言(TCP不支持多播)。
SO_REUSEPORT選項有以下語義:

此選項容許徹底重複捆綁,但僅在想捆綁相同IP地址和端口的套接口都指定了此套接口選項才行。java

若是被捆綁的IP地址是一個多播地址,則SO_REUSEADDR和SO_REUSEPORT等效。git

使用這兩個套接口選項的建議:編程

  • 在全部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,而且保證喚醒的均衡性。

http://www.javashuo.com/article/p-rnpyfyiz-hm.html

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

沒用reuseport的
socket_fork_no_reuseport.php

用了的
socket_fork_reuseport.php

相關文章
相關標籤/搜索