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

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

說明

  • 流,算是一種對不同事物,但有相同特性的抽象封裝,可能這樣說並不理解,但是我們早就使用過了,例如開啟檔案fopen等操作,其實就是用的流,fopen('abc.txt')實際上就是fopen('file://abc.txt'),或者是與app互動用到的php://input等獲取post資料也是流的一種
  • php官方文件可以看Streams API for PHP Extension AuthorsStream
  • 我們只看流中與socket相關的封裝,上篇我們建立一個連線需要好幾個步驟,比較繁瑣,而stream中對此進行了簡化封裝。至於流的其它包裝過濾等功能,可自己去查詢資料,好像是在《Modern PHP》中也有章節對此做過講解。
  • 相關的stream函式可以參照Workerman中具體的使用場景,Workerman中沒有使用上節的socket函式,而是呼叫的更加簡潔方便的stream函式,

相關函式

  • 服務端函式

    • stream_socket_server ( string $local_socket [, int &$errno [, string &$errstr [, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN [, resource $context ]]]] ) : resource
      • 建立socket服務端
      • 此函式可以代替上節的socket_create
        socket_bindsocket_listen這三個函式,例如:$socket = stream_socket_server('tcp://0.0.0.0:8000');相當於$socket = socket_create(AF_INET, SOCK_STREAM, 0);+socket_bind($socket, '0.0.0.0', 8000);+socket_listen($socket);
      • $flags引數,如果是用udp通訊的話,STREAM_SERVER_LISTEN 是不需要的,$context則是上下文,後面有單獨的函式來生成此型別,還有需要注意的是,stream_socket_serversocket_create
        的返回值雖然都是resource,但兩個不能通用,socket擴充套件中有單獨的函式來轉換這兩者。
    • stream_socket_accept ( resource $server_socket [, float $timeout = ini_get("default_socket_timeout") [, string &$peername ]] ) : resource
      • 接收客戶端的連線(udp通訊時,不需要用到此函式)
      • 類似socket_accept,需要注意的是第一個引數,只能是stream_socket_server的返回值。
  • 客戶端函式

    • stream_socket_client ( string $remote_socket [, int &$errno [, string &$errstr [, float $timeout = ini_get("default_socket_timeout") [, int $flags = STREAM_CLIENT_CONNECT [, resource $context ]]]]] ) : resource
      • 建立socket客戶端,連線到服務端
      • 此函式可以替代上節的socket_createsocket_connect這兩個函式,例如$socket = stream_socket_client('tcp://0.0.0.0:8000'')相當於$socket = socket_create(AF_INET, SOCK_STREAM, 0);+$result = socket_connect($socket, '0.0.0.0', 8000);
      • $flags引數主要有兩個選項STREAM_CLIENT_CONNECTSTREAM_CLIENT_ASYNC_CONNECT,第二個引數是設定非同步,與阻塞又不太相同,挺讓人費解的,而且網上也基本搜不到相關資料,當設定此引數時,將不會檢測地址埠是否真的能連通,都為立即返回正常的資源resource,那如何來確定服務端是否真的能連結上呢?目前個人所知的方式是用stream_select類似的多路IO複用來檢測,當真正連結或連結失敗後核心會通知到read或者write或者except(具體通知到哪個windows和linux不一致,需要區別處理),下面的stream_select函式和後期IO多路複用時會講到。
  • 讀寫函式

    • fread ( resource $handle , int $length ) : string
      • 讀取資料
    • fwrite ( resource $handle , string $string [, int $length ] ) : int
      • 寫入資料
    • 上面兩個函式和我們平常讀取檔案一樣,正如我們說的,操作檔案也是操作一種流,所以方法通用
    • stream_socket_recvfrom ( resource $socket , int $length [, int $flags = 0 [, string &$address ]] ) : string
      • 接收資料,最後引數是引用,用於獲取遠端連結的地址
    • stream_socket_sendto ( resource $socket , string $data [, int $flags = 0 [, string $address ]] ) : int
      • 傳送資料
    • 上面兩個函式和freadfwrite基本一致,但功能更多一些,$flags引數可以設定傳送OOB資料,而$address引數則多是用於udp通訊,由於udp通訊時不使用stream_socket_accept,所以無法獲取到新的resource,那就無法向指定的客戶端中寫資料,所以一般先用stream_socket_recvfrom獲取最後引用引數$address即為客戶端的地址,然後再用stream_socket_sendto設定$address,來向指定的客戶端傳送。
  • 其它常用函式

    • stream_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] ) : int
      • 呼叫系統select()相關IO模型
      • 類似上節的socket_select,詳細的在後面的IO模型中會總結
    • stream_set_blocking ( resource $stream , bool $mode ) : bool
      • 設定阻塞與非阻塞,false為非阻塞,true為阻塞
      • 類似上節的socket_set_blocksocket_set_nonblock,但是還是有些區別的,上節我們知道socket的阻塞影響的函式為:socket_connect、socket_accept以及各種socket讀寫函式,而stream的阻塞影響的函式僅僅為stream相關的讀寫函式,stream_socket_accept不受影響,一直是阻塞,stream_socket_client更不會受影響,因為stream_socket_client的返回值,才能作為stream_set_blocking的第一個引數,所以上方說stream_socket_client的引數flags設定為非同步,算是彌補了這一問題。
    • fclose ( resource $handle ) : bool
      • 關閉連結
    • stream_socket_shutdown ( resource $stream , int $how ) : bool
      • 關閉連結
    • 上面兩個函式功能基本一樣,stream_socket_shutdown還可以控制只關閉讀或者只關閉寫
    • socket_import_stream ( resource $stream ) : resource
      • 將stream資源轉為socket資源
    • socket_export_stream ( resource $socket ) : resource
      • 將socket資源轉為stream資源
    • 上面也提到過,socket擴充套件和stream兩者連結產生的resource是不互通的,上面兩個函式就是將兩者轉換的,而這兩個函式是定義在上節所說的socket擴充套件中的,使用前需要確定socket擴充套件有開啟
    • stream_context_create
      • 上下文建立
    • stream_socket_serverstream_socket_client最後一個引數就是上下文,相關的引數需要經過此函式的組裝後才能傳入,我們可能早就接觸過,例如我們經常用到的file_get_content可以模擬post來獲取資料,就需要用到它的第三個上下文引數。

示例

  • 服務端
    $socket = stream_socket_server('tcp://127.0.0.1:8888', $errno, $errstr);
    while ($conn = stream_socket_accept($socket)) {
      fwrite($conn, "hello\n");
      fclose($conn);
    }
    fclose($socket);
    
  • 客戶端
    $socket = stream_socket_client('tcp://127.0.0.1:8888', $errorno, $errstr);
    while (!feof($socket)) {
      echo fread($socket, 1024);
    }
    fclose($socket);
    
  • 上方是簡單的示例程式碼,更多的可以檢視官網各函式下的示例程式碼
  • stream與socket相比是更簡潔了,但是原理是一樣的,同樣的只能連線一個客戶端的侷限性並沒有解決,還是需要後續的完善

關聯