php socket網路程式設計基礎知識(三):stream函式
阿新 • • 發佈:2020-07-19
說明
- 流,算是一種對不同事物,但有相同特性的抽象封裝,可能這樣說並不理解,但是我們早就使用過了,例如開啟檔案
fopen
等操作,其實就是用的流,fopen('abc.txt')
實際上就是fopen('file://abc.txt')
,或者是與app互動用到的php://input
等獲取post資料也是流的一種 - php官方文件可以看Streams API for PHP Extension Authors和Stream
- 我們只看流中與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_bind
、socket_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_server
和socket_create
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_create
、socket_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_CONNECT
和STREAM_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- 傳送資料
- 上面兩個函式和
fread
和fwrite
基本一致,但功能更多一些,$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_block
和socket_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_server
和stream_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相比是更簡潔了,但是原理是一樣的,同樣的只能連線一個客戶端的侷限性並沒有解決,還是需要後續的完善