1. 程式人生 > 實用技巧 >php socket網路程式設計基礎知識(二):socket函式

php socket網路程式設計基礎知識(二):socket函式

說明

  • 我們都知道通過IP,埠等可以實現兩臺機器之間的資料互通,但具體要怎麼操作,系統給我們提供了socket介面,通過呼叫socket函式就可以實現互通。
  • php的socket擴充套件和C本身的非常相似,如果找不到php相關的資料,可以對照著C的socket函式來學習,例如:C語言SOCKET程式設計指南
  • php的socket文件,文件中有很多函式,我們只找主要通訊流程的函式理解其流程,其它函式後期用到再去檢視即可

通訊流程


圖片引用自sockets百度百科詞條,tcp通訊的流程,其它方式的通訊可參考

相關函式

  • 服務端相關函式

    • socket_create ( int $domain , int $type , int $protocol ) : resource
      建立一個socket,例如$socket = socket_create(AF_INET, SOCK_STREAM, 0);

      $domain是選擇IP4或者IP6或者UNIX本地通訊,配置過nginx的話應該會知道引數fastcgi_pass用來連線php-fpm的,有兩種方式,一種是tcp,一種是unix socket,就是對應這裡的IP方式和UNIX本地方式
      $type是SOCK_STREAM(tcp)或者SOCK_DGRAM(udp)或者其它
      $protocol 一般為0是IP協議。
      上方語句建立了ip4地址的tpc套接字,更多引數自行檢視文件。
    • socket_bind ( resource $socket , string $address [, int $port = 0 ] ) : bool
      將上方建立的socket繫結具體的IP和埠,到時候客戶端連線需要填寫此IP和介面
    • socket_listen ( resource $socket [, int $backlog = 0 ] ) : bool
      監聽客戶端的連結。(udp通訊時,不需要用到此函式)
      第二個引數backlog是accept之前握手佇列的大小,如果我們配置過php-fpm的話應該有接觸過,叫listen.backlog ,這個引數就對應著socket_listen中的第二個引數,目前php-fpm預設值是511,而workerman中預設值是102400
      相關參考:socket_listen裡面第二個引數backlog的用處TCP SOCKET中backlog引數的用途是什麼?
    • socket_accept
      ( resource $socket ) : resource
      接收客戶端連線(udp通訊時,不需要用到此函式)
      至此就可以和客戶端聯通,進行讀寫操作了,需要注意的是其返回值,是一個新的socket,而後續讀寫是在這個新的socket下進行的而不是一開始建立的socket。
  • 客戶端相關函式

    • socket_connect ( resource $socket , string $address [, int $port = 0 ] ) : bool
      客戶端連線服務端,同服務端一樣,首先要呼叫socket_create建立一個socket,然後再呼叫此函式連線到服務端
  • 讀寫函式

    • socket_send ( resource $socket , string $buf , int $len , int $flags ) : int
      傳送資料給socket
    • socket_write ( resource $socket , string $buffer [, int $length = 0 ] ) : int
      向socket中寫資料
      上兩個函式基本一樣,socket_send中最後一位$flags引數設定為0,就等於socket_write,網上關於這兩個函式的比較非常少,基本搜尋不到,而且費解的是檢視php原始碼會發現這兩個方法內部有些許差異,socket_write中引用#ifndef PHP_WIN32來判定如果是非windows系統,則呼叫系統的write方法來實現,windows系統則呼叫系統的send方法來實現,但是socket_send則沒有判定,統一呼叫系統的send來實現,不明白socket_write為什麼要增加判定?
    • socket_sendto( resource $socket , string $buf , int $len , int $flags , string $addr [, int $port = 0 ] ) : int
      主要是udp通訊時,傳送資料
    • socket_recv( resource $socket , string &$buf , int $len , int $flags ) : int
      從socket中接收資料,需要注意的是返回值是int表示接收的位元組數,而內容存在第二個引用型別的引數$buf中
    • socket_read ( resource $socket , int $length [, int $type = PHP_BINARY_READ ] ) : string
      從socket中讀資料,返回結果就是讀的資料
      上面兩個函式也很相近,除了返回值的區別,socket_recv最後$flags為0和socket_read最後$type為PHP_BINARY_READ時讀取的資料是一樣的,但是如果flags或者type變化,則不一定一樣了。
    • socket_recvfrom ( resource $socket , string &$buf , int $len , int $flags , string &$name [, int &$port ] ) : int
      主要是udp通訊是,接收資料
  • 其它常用函式

    • socket_close ( resource $socket ) : void
      關閉socket
    • socket_set_nonblock( resource $socket ) : bool
      設定為非阻塞
    • socket_set_block( resource $socket ) : bool
      設定為阻塞
      上面兩個影響的函式為:socket_connectsocket_accept以及上方的各種讀寫函式,以socket_accept為例,阻塞就是呼叫此方法後,如果沒有接受到客戶端,則此方法一直卡住,等待客戶端加入後才會進行下一步動作,而非阻塞則是如果沒有客戶端加入,則直接返回false,具體的會在後面的IO模型中總結
    • socket_select( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] ) : int
      呼叫系統select()相關IO模型
      詳細的在後面的IO模型中會總結

示例

  • 服務端socket_service.php:
    <?php
    //IP和埠
    $address = '127.0.0.1';
    $port = 8888;
    //建立
    $listenSocket = socket_create(AF_INET, SOCK_STREAM, 0);
    //繫結
    socket_bind($listenSocket, $address, $port);
    //監聽
    socket_listen($listenSocket, 5);
    //迴圈接入客戶端
    while (true) {
      //接入客戶端
      $connectSocket = socket_accept($listenSocket);
      $msg = "hello\r\n";
      //傳送給客戶端,注意此時用的是$connectSocket,而不是$listenSocket
      socket_write($connectSocket, $msg, strlen($msg));
      //關閉
      socket_close($connectSocket);
    }
    //關閉
    socket_close($listenSocket);
    
  • 客戶端socket_client.php:
    <?php
    //服務端IP和埠
    $address = '127.0.0.1';
    $port = 8888;
    //建立
    $socket = socket_create(AF_INET, SOCK_STREAM, 0);
    //連線
    $result = socket_connect($socket, $address, $port);
    //讀取
    $out = socket_read($socket, 2048);
    echo $out;
    //關閉
    socket_close($socket);
    
  • 上方是簡單的示例程式碼,在命令列cli模式下執行php socket_service.php,然後再新起一視窗執行php socket_client.php就能看到客戶端聯通後收到了服務端傳送的資料並顯示出來。
  • 我們在普通的程式設計中,通常是儘量避免使用while(true)的,害怕無限迴圈會導致卡死,但在網路程式設計中會經常用到,其實只要控制好就沒問題,上方之所以可以用是因為預設socket_accept是阻塞的,也就是沒有客戶端接入時會卡在這裡等待接入,所以不會造成效能影響。
  • 也可以檢視php文件中的示例,按照提示來測試。
  • 通過示例我們可以看到是可以連通,但是有很大的侷限性,只能一個服務端連線一個客戶端,如果想同時連線多個客戶端是不行的(上方簡單的程式碼可能不明顯,畢竟連線後接著就關閉斷開了,但是執行php文件中的例子會比較明顯,同時開啟幾個視窗用telnet來連結,會發現只有一個結束後,另一個才能接入),解決方法就是可以用fork子程序或者是IO多路複用,後續會總結。

關聯