ReactPHP 的非同步、串流純文字 TCP/IP 和安全 TLS 套接字伺服器和用戶端連線。
開發版本:此分支包含即將發布的 v3 版本的程式碼。有關目前穩定 v1 版本的程式碼,請查看
1.x
分支。即將發布的 v3 版本將是該軟體包的前進方向。不過,我們仍然會積極支援尚未使用最新版本的 v1。另請參閱安裝說明以了解更多詳細資訊。
套接字庫為基於EventLoop
和Stream
元件的通訊端層伺服器和用戶端提供可重複使用的介面。它的伺服器元件可讓您建立接受來自網路用戶端(例如 HTTP 伺服器)的傳入連接的網路伺服器。它的客戶端元件可讓您建立網路用戶端,以建立到網頁伺服器的傳出連線(例如 HTTP 或資料庫用戶端)。該庫為所有這些提供了非同步、串流方式,因此您可以處理多個並發連接而不會阻塞。
目錄
這是一個伺服器,如果您發送任何內容,它就會關閉連接:
$ socket = new React Socket SocketServer ( ' 127.0.0.1:8080 ' );
$ socket -> on ( ' connection ' , function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( " Hello " . $ connection -> getRemoteAddress () . " ! n" );
$ connection -> write ( " Welcome to this amazing server! n" );
$ connection -> write ( " Here's a tip: don't say anything. n" );
$ connection -> on ( ' data ' , function ( $ data ) use ( $ connection ) {
$ connection -> close ();
});
});
另請參閱範例。
這是一個客戶端,它輸出所述伺服器的輸出,然後嘗試向其發送字串:
$ connector = new React Socket Connector ();
$ connector -> connect ( ' 127.0.0.1:8080 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> pipe ( new React Stream WritableResourceStream ( STDOUT ));
$ connection -> write ( " Hello World! n" );
}, function ( Exception $ e ) {
echo ' Error: ' . $ e -> getMessage () . PHP_EOL ;
});
ConnectionInterface
用於表示任何傳入和傳出連接,例如普通的 TCP/IP 連線。
傳入或傳出連線是實作 React 的DuplexStreamInterface
的雙工流(可讀和可寫入)。它包含已建立此連線的本機和遠端位址(客戶端 IP)的附加屬性。
最常見的是,實作此ConnectionInterface
實例是由所有實作ServerInterface
類別發出,並由所有實作ConnectorInterface
類別使用。
因為ConnectionInterface
實作了底層DuplexStreamInterface
所以您可以照常使用它的任何事件和方法:
$ connection -> on ( ' data ' , function ( $ chunk ) {
echo $ chunk ;
});
$ connection -> on ( ' end ' , function () {
echo ' ended ' ;
});
$ connection -> on ( ' error ' , function ( Exception $ e ) {
echo ' error: ' . $ e -> getMessage ();
});
$ connection -> on ( ' close ' , function () {
echo ' closed ' ;
});
$ connection -> write ( $ data );
$ connection -> end ( $ data = null );
$ connection -> close ();
// …
有關更多詳細信息,請參閱DuplexStreamInterface
。
getRemoteAddress(): ?string
方法傳回建立此連線的完整遠端位址 (URI)。
$ address = $ connection -> getRemoteAddress ();
echo ' Connection with ' . $ address . PHP_EOL ;
如果此時無法確定或未知遠端位址(例如在連線關閉後),則它可以傳回NULL
值。
否則,它將傳回完整位址(URI)作為字串值,例如tcp://127.0.0.1:8080
、 tcp://[::1]:80
、 tls://127.0.0.1:443
、 unix://example.sock
或unix:///path/to/example.sock
。請注意,各個 URI 組件是特定於應用程式的,並且取決於底層傳輸協定。
如果這是基於 TCP/IP 的連接,並且您只需要遠端 IP,則可以使用以下內容:
$ address = $ connection -> getRemoteAddress ();
$ ip = trim ( parse_url ( $ address , PHP_URL_HOST ), ' [] ' );
echo ' Connection with ' . $ ip . PHP_EOL ;
getLocalAddress(): ?string
方法傳回建立此連線的完整本機位址 (URI)。
$ address = $ connection -> getLocalAddress ();
echo ' Connection with ' . $ address . PHP_EOL ;
如果此時無法確定或未知本機位址(例如連線關閉後),則它可以傳回NULL
值。
否則,它將傳回完整位址(URI)作為字串值,例如tcp://127.0.0.1:8080
、 tcp://[::1]:80
、 tls://127.0.0.1:443
、 unix://example.sock
或unix:///path/to/example.sock
。請注意,各個 URI 組件是特定於應用程式的,並且取決於底層傳輸協定。
此方法補充了getRemoteAddress()
方法,因此不應將它們混淆。
如果您的TcpServer
實例正在偵聽多個介面(例如使用位址0.0.0.0
),您可以使用此方法找出哪個介面實際接受此連線(例如公用或本機介面)。
如果您的系統有多個介面(例如 WAN 和 LAN 介面),您可以使用此方法找出實際用於此連線的介面。
ServerInterface
負責提供接受傳入流連線的介面,例如普通的 TCP/IP 連線。
大多數高階元件(例如 HTTP 伺服器)接受實作此介面的實例來接受傳入的流連線。這通常是透過依賴注入來完成的,因此實際上將此實作與此介面的任何其他實作交換是相當簡單的。這表示您應該針對此介面進行類型提示,而不是此介面的特定實作。
除了定義一些方法之外,該介面還實作了EventEmitterInterface
,它允許您對某些事件做出反應。
每當建立新連線時就會發出connection
事件,也就是新用戶端連線到該伺服器套接字:
$ socket -> on ( ' connection ' , function ( React Socket ConnectionInterface $ connection ) {
echo ' new connection ' . PHP_EOL ;
});
另請參閱ConnectionInterface
以取得有關處理傳入連線的更多詳細資訊。
每當接受來自客戶端的新連線時出現錯誤時,就會發出error
事件。
$ socket -> on ( ' error ' , function ( Exception $ e ) {
echo ' error: ' . $ e -> getMessage () . PHP_EOL ;
});
請注意,這不是致命錯誤事件,即即使在該事件發生後,伺服器仍會繼續偵聽新連線。
getAddress(): ?string
方法可用來傳回該伺服器目前正在偵聽的完整位址 (URI)。
$ address = $ socket -> getAddress ();
echo ' Server listening on ' . $ address . PHP_EOL ;
如果此時無法確定或未知位址(例如在套接字關閉後),則它可能會傳回NULL
值。
否則,它將傳回完整位址(URI)作為字串值,例如tcp://127.0.0.1:8080
、 tcp://[::1]:80
、 tls://127.0.0.1:443
unix://example.sock
或unix:///path/to/example.sock
。請注意,各個 URI 組件是特定於應用程式的,並且取決於底層傳輸協定。
如果這是基於 TCP/IP 的伺服器並且您只需要本地端口,則可以使用以下內容:
$ address = $ socket -> getAddress ();
$ port = parse_url ( $ address , PHP_URL_PORT );
echo ' Server listening on port ' . $ port . PHP_EOL ;
此pause(): void
方法可用來暫停接受新的傳入連線。
從 EventLoop 中刪除套接字資源,從而停止接受新連線。請注意,偵聽套接字保持活動狀態並且未關閉。
這意味著新的傳入連線將在作業系統積壓中保持待處理狀態,直到其可配置積壓被填滿。一旦積壓被填滿,作業系統可能會拒絕進一步的傳入連接,直到透過恢復接受新連接而再次耗盡積壓。
一旦伺服器暫停,就不應再發出任何connection
事件。
$ socket -> pause ();
$ socket -> on ( ' connection ' , assertShouldNeverCalled ());
此方法僅供參考,儘管通常不推薦,但伺服器可以繼續發出connection
事件。
除非另有說明,成功開啟的伺服器不應以暫停狀態啟動。
您可以透過再次呼叫resume()
來繼續處理事件。
請注意,這兩種方法都可以呼叫任意多次,特別是多次呼叫pause()
不應產生任何效果。類似地,在close()
之後呼叫它也是一個 NO-OP。
resume(): void
方法可用來恢復接受新的傳入連線。
在先前的pause()
之後將套接字資源重新附加到EventLoop。
$ socket -> pause ();
Loop:: addTimer ( 1.0 , function () use ( $ socket ) {
$ socket -> resume ();
});
請注意,這兩種方法都可以呼叫任意多次,特別是在沒有事先pause()
的情況下呼叫resume()
不應產生任何效果。類似地,在close()
之後呼叫它也是一個 NO-OP。
close(): void
方法可用來關閉此監聽套接字。
這將停止偵聽此套接字上的新傳入連線。
echo ' Shutting down server socket ' . PHP_EOL ;
$ socket -> close ();
在同一個實例上多次呼叫此方法是 NO-OP。
SocketServer
類別是此套件中的主類,它實作ServerInterface
並允許您接受傳入的流連接,例如純文字 TCP/IP 或安全 TLS 連接流。
為了接受純文字 TCP/IP 連接,您可以簡單地傳遞主機和連接埠組合,如下所示:
$ socket = new React Socket SocketServer ( ' 127.0.0.1:8080 ' );
偵聽本機主機位址127.0.0.1
表示無法從該系統外部存取它。為了變更套接字正在偵聽的主機,您可以提供介面的 IP 位址或使用特殊的0.0.0.0
位址來偵聽所有介面:
$ socket = new React Socket SocketServer ( ' 0.0.0.0:8080 ' );
如果您想監聽 IPv6 位址,則必須將主機括在方括號中:
$ socket = new React Socket SocketServer ( ' [::1]:8080 ' );
為了使用隨機連接埠分配,您可以使用連接埠0
:
$ socket = new React Socket SocketServer ( ' 127.0.0.1:0 ' );
$ address = $ socket -> getAddress ();
若要偵聽 Unix 網域套接字 (UDS) 路徑,您必須在 URI 前面加上unix://
方案:
$ socket = new React Socket SocketServer ( ' unix:///tmp/server.sock ' );
為了監聽現有的文件描述符 (FD) 編號,您必須在 URI 前面加上php://fd/
前綴,如下所示:
$ socket = new React Socket SocketServer ( ' php://fd/3 ' );
如果給定的 URI 無效、不包含連接埠、任何其他方案或包含主機名,它將拋出InvalidArgumentException
:
// throws InvalidArgumentException due to missing port
$ socket = new React Socket SocketServer ( ' 127.0.0.1 ' );
如果給定的 URI 看起來有效,但監聽失敗(例如連接埠已在使用中或低於 1024 的連接埠可能需要 root 存取權限等),它將拋出RuntimeException
:
$ first = new React Socket SocketServer ( ' 127.0.0.1:8080 ' );
// throws RuntimeException because port is already in use
$ second = new React Socket SocketServer ( ' 127.0.0.1:8080 ' );
請注意,這些錯誤情況可能會因您的系統和/或配置而異。有關實際錯誤情況的更多詳細信息,請參閱異常訊息和代碼。
或者,您可以為底層流套接字資源指定 TCP 套接字上下文選項,如下所示:
$ socket = new React Socket SocketServer ( ' [::1]:8080 ' , [
' tcp ' => [
' backlog ' => 200 ,
' so_reuseport ' => true ,
' ipv6_v6only ' => true
]
]);
請注意,可用的套接字上下文選項、其預設值以及更改這些選項的效果可能會有所不同,具體取決於您的系統和/或 PHP 版本。傳遞未知的上下文選項沒有任何效果。除非明確指定,否則
backlog
上下文選項預設為511
。
您只需在前面新增tls://
URI 方案即可啟動安全 TLS(以前稱為 SSL)伺服器。在內部,它將等待純文字 TCP/IP 連接,然後為每個連接執行 TLS 握手。因此,它需要有效的 TLS 上下文選項,如果您使用 PEM 編碼的憑證文件,其最基本的形式可能如下所示:
$ socket = new React Socket SocketServer ( ' tls://127.0.0.1:8080 ' , [
' tls ' => [
' local_cert ' => ' server.pem '
]
]);
請注意,憑證檔案不會在實例化時載入,而是在傳入連線初始化其 TLS 上下文時載入。這意味著任何無效的證書檔案路徑或內容只會在以後導致
error
事件。
如果您的私鑰使用密碼加密,則必須如下指定:
$ socket = new React Socket SocketServer ( ' tls://127.0.0.1:8000 ' , [
' tls ' => [
' local_cert ' => ' server.pem ' ,
' passphrase ' => ' secret '
]
]);
預設情況下,此伺服器支援 TLSv1.0+,但不支援舊版 SSLv2/SSLv3。您也可以明確選擇要與遠端協商的 TLS 版本:
$ socket = new React Socket SocketServer ( ' tls://127.0.0.1:8000 ' , [
' tls ' => [
' local_cert ' => ' server.pem ' ,
' crypto_method ' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
]
]);
請注意,可用的 TLS 上下文選項、其預設值以及更改這些選項的效果可能會有所不同,具體取決於您的系統和/或 PHP 版本。外部上下文數組可讓您同時使用
tcp
(可能還有更多)上下文選項。傳遞未知的上下文選項沒有任何效果。如果不使用tls://
方案,則傳遞tls
上下文選項無效。
每當用戶端連線時,它都會發出一個帶有實作ConnectionInterface
的連線實例的connection
事件:
$ socket -> on ( ' connection ' , function ( React Socket ConnectionInterface $ connection ) {
echo ' Plaintext connection from ' . $ connection -> getRemoteAddress () . PHP_EOL ;
$ connection -> write ( ' hello there! ' . PHP_EOL );
…
});
另請參閱ServerInterface
以取得更多詳細資訊。
此類別採用可選的LoopInterface|null $loop
參數,可用於傳遞要用於此物件的事件循環實例。您可以在此處使用null
值以使用預設循環。除非您確定要明確使用給定的事件循環實例,否則不應給出此值。
請注意,
SocketServer
類別是 TCP/IP 套接字的具體實作。如果您想在更高層級的協定實作中輸入提示,您應該使用通用的ServerInterface
。
TcpServer
類別實作ServerInterface
並負責接受純文字 TCP/IP 連線。
$ server = new React Socket TcpServer ( 8080 );
如上所述, $uri
參數只能包含一個端口,在這種情況下,伺服器將預設偵聽本地主機位址127.0.0.1
,這意味著從系統外部無法存取它。
為了使用隨機連接埠分配,您可以使用連接埠0
:
$ server = new React Socket TcpServer ( 0 );
$ address = $ server -> getAddress ();
為了變更套接字正在偵聽的主機,您可以透過提供給建構函式的第一個參數提供 IP 位址,可以選擇在前面加上tcp://
方案:
$ server = new React Socket TcpServer ( ' 192.168.0.1:8080 ' );
如果您想監聽 IPv6 位址,則必須將主機括在方括號中:
$ server = new React Socket TcpServer ( ' [::1]:8080 ' );
如果給定的 URI 無效、不包含連接埠、任何其他方案或包含主機名,它將拋出InvalidArgumentException
:
// throws InvalidArgumentException due to missing port
$ server = new React Socket TcpServer ( ' 127.0.0.1 ' );
如果給定的 URI 看起來有效,但監聽失敗(例如連接埠已在使用中或低於 1024 的連接埠可能需要 root 存取權限等),它將拋出RuntimeException
:
$ first = new React Socket TcpServer ( 8080 );
// throws RuntimeException because port is already in use
$ second = new React Socket TcpServer ( 8080 );
請注意,這些錯誤情況可能會因您的系統和/或配置而異。有關實際錯誤情況的更多詳細信息,請參閱異常訊息和代碼。
此類別採用可選的LoopInterface|null $loop
參數,可用於傳遞要用於此物件的事件循環實例。您可以在此處使用null
值以使用預設循環。除非您確定要明確使用給定的事件循環實例,否則不應給出此值。
或者,您可以為底層流套接字資源指定套接字上下文選項,如下所示:
$ server = new React Socket TcpServer ( ' [::1]:8080 ' , null , [
' backlog ' => 200 ,
' so_reuseport ' => true ,
' ipv6_v6only ' => true
]);
請注意,可用的套接字上下文選項、其預設值以及更改這些選項的效果可能會有所不同,具體取決於您的系統和/或 PHP 版本。傳遞未知的上下文選項沒有任何效果。除非明確指定,否則
backlog
上下文選項預設為511
。
每當用戶端連線時,它都會發出一個帶有實作ConnectionInterface
的連線實例的connection
事件:
$ server -> on ( ' connection ' , function ( React Socket ConnectionInterface $ connection ) {
echo ' Plaintext connection from ' . $ connection -> getRemoteAddress () . PHP_EOL ;
$ connection -> write ( ' hello there! ' . PHP_EOL );
…
});
另請參閱ServerInterface
以取得更多詳細資訊。
SecureServer
類別實作ServerInterface
並負責提供安全 TLS(以前稱為 SSL)伺服器。
它透過包裝一個TcpServer
實例來實現這一點,該實例等待純文字 TCP/IP 連接,然後為每個連接執行 TLS 握手。因此,它需要有效的 TLS 上下文選項,如果您使用 PEM 編碼的憑證文件,其最基本的形式可能如下所示:
$ server = new React Socket TcpServer ( 8000 );
$ server = new React Socket SecureServer ( $ server , null , [
' local_cert ' => ' server.pem '
]);
請注意,憑證檔案不會在實例化時載入,而是在傳入連線初始化其 TLS 上下文時載入。這意味著任何無效的證書檔案路徑或內容只會在以後導致
error
事件。
如果您的私鑰使用密碼加密,則必須如下指定:
$ server = new React Socket TcpServer ( 8000 );
$ server = new React Socket SecureServer ( $ server , null , [
' local_cert ' => ' server.pem ' ,
' passphrase ' => ' secret '
]);
預設情況下,此伺服器支援 TLSv1.0+,但不支援舊版 SSLv2/SSLv3。您也可以明確選擇要與遠端協商的 TLS 版本:
$ server = new React Socket TcpServer ( 8000 );
$ server = new React Socket SecureServer ( $ server , null , [
' local_cert ' => ' server.pem ' ,
' crypto_method ' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
]);
請注意,可用的 TLS 上下文選項、其預設值以及更改這些選項的效果可能會有所不同,具體取決於您的系統和/或 PHP 版本。傳遞未知的上下文選項沒有任何效果。
每當用戶端完成 TLS 握手時,它將發出一個connection
事件,其中的連線實例實作ConnectionInterface
:
$ server -> on ( ' connection ' , function ( React Socket ConnectionInterface $ connection ) {
echo ' Secure connection from ' . $ connection -> getRemoteAddress () . PHP_EOL ;
$ connection -> write ( ' hello there! ' . PHP_EOL );
…
});
每當客戶端未能成功執行 TLS 握手時,它將發出error
事件,然後關閉底層 TCP/IP 連線:
$ server -> on ( ' error ' , function ( Exception $ e ) {
echo ' Error ' . $ e -> getMessage () . PHP_EOL ;
});
另請參閱ServerInterface
以取得更多詳細資訊。
請注意, SecureServer
類別是 TLS 套接字的具體實作。如果您想在更高層級的協定實作中輸入提示,您應該使用通用的ServerInterface
。
此類別採用可選的LoopInterface|null $loop
參數,可用於傳遞要用於此物件的事件循環實例。您可以在此處使用null
值以使用預設循環。除非您確定要明確使用給定的事件循環實例,否則不應給出此值。
進階用法:儘管允許任何
ServerInterface
作為第一個參數,但您應該傳遞TcpServer
實例作為第一個參數,除非您知道自己在做什麼。在內部,SecureServer
必須在底層串流資源上設定所需的 TLS 上下文選項。這些資源不透過此套件中定義的任何介面公開,而僅透過內部Connection
類別公開。TcpServer
類別保證發出實現ConnectionInterface
的連接,並使用內部Connection
類別來公開這些底層資源。如果您使用自訂ServerInterface
且其connection
事件不符合此要求,則SecureServer
將發出error
事件,然後關閉底層連線。
UnixServer
類別實作ServerInterface
並負責接受 Unix 域套接字 (UDS) 上的連線。
$ server = new React Socket UnixServer ( ' /tmp/server.sock ' );
如上所述, $uri
參數只能包含套接字路徑或以unix://
方案為前綴的套接字路徑。
如果給定的 URI 看起來有效,但監聽失敗(例如套接字已在使用中或檔案不可存取等),它將拋出RuntimeException
:
$ first = new React Socket UnixServer ( ' /tmp/same.sock ' );
// throws RuntimeException because socket is already in use
$ second = new React Socket UnixServer ( ' /tmp/same.sock ' );
請注意,這些錯誤情況可能會因您的系統和/或配置而異。特別是,當 UDS 路徑已經存在且無法綁定時,Zend PHP 只會報告「未知錯誤」。在這種情況下,您可能需要檢查給定 UDS 路徑上的
is_file()
以報告更用戶友好的錯誤訊息。有關實際錯誤情況的更多詳細信息,請參閱異常訊息和代碼。
此類別採用可選的LoopInterface|null $loop
參數,可用於傳遞要用於此物件的事件循環實例。您可以在此處使用null
值以使用預設循環。除非您確定要明確使用給定的事件循環實例,否則不應給出此值。
每當用戶端連線時,它都會發出一個帶有實作ConnectionInterface
的連線實例的connection
事件:
$ server -> on ( ' connection ' , function ( React Socket ConnectionInterface $ connection ) {
echo ' New connection ' . PHP_EOL ;
$ connection -> write ( ' hello there! ' . PHP_EOL );
…
});
另請參閱ServerInterface
以取得更多詳細資訊。
LimitingServer
裝飾器包裝給定的ServerInterface
並負責限制和追蹤與此伺服器實例的開啟連線。
每當底層伺服器發出connection
事件時,它將檢查其限制,然後
connection
事件error
事件。每當連接關閉時,它都會從打開的連接清單中刪除該連接。
$ server = new React Socket LimitingServer ( $ server , 100 );
$ server -> on ( ' connection ' , function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' hello there! ' . PHP_EOL );
…
});
有關更多詳細信息,另請參閱第二個範例。
您必須傳遞最大開啟連線數,以確保一旦超過此限制,伺服器將自動拒絕(關閉)連線。在這種情況下,它將發出error
事件來通知這一點,並且不會發出任何connection
事件。
$ server = new React Socket LimitingServer ( $ server , 100 );
$ server -> on ( ' connection ' , function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' hello there! ' . PHP_EOL );
…
});
您可以傳遞null
限制,以便對開啟連接的數量不加限制,並繼續接受新連接,直到耗盡作業系統資源(例如開啟的檔案句柄)。如果您不想套用限制但仍想使用getConnections()
方法,這可能很有用。
您可以選擇將伺服器設定為在達到連線限制後暫停接受新連線。在這種情況下,它將暫停底層伺服器並不再處理任何新連接,因此不再關閉任何過多的連接。底層作業系統負責保留待處理連線的積壓,直到達到其限制,此時它將開始拒絕進一步的連線。一旦伺服器低於連接限制,它將繼續消耗積壓的連接,並處理每個連接上的任何未完成的資料。此模式對於某些設計用於等待回應訊息(例如 HTTP)的協定可能很有用,但對於需要立即回應的其他協定(例如互動式聊天中的「歡迎」訊息)可能不太有用。
$ server = new React Socket LimitingServer ( $ server , 100 , true );
$ server -> on ( ' connection ' , function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' hello there! ' . PHP_EOL );
…
});
getConnections(): ConnectionInterface[]
方法可用來傳回包含所有目前活動連線的陣列。
foreach ( $ server -> getConnection () as $ connection ) {
$ connection -> write ( ' Hi! ' );
}
ConnectorInterface
負責提供建立流連接的接口,例如普通的 TCP/IP 連線。
這是該套件中定義的主要接口,在 React 龐大的生態系統中使用。
大多數進階元件(例如 HTTP、資料庫或其他網路服務用戶端)接受實作此介面的實例來建立與底層網路服務的 TCP/IP 連線。這通常是透過依賴注入來完成的,因此實際上將此實作與此介面的任何其他實作交換是相當簡單的。
此介面僅提供一個方法:
connect(string $uri): PromiseInterface<ConnectionInterface>
方法可用來建立到給定遠端位址的流連線。
它返回一個 Promise,該 Promise 要么在成功時通過實現ConnectionInterface
流來實現,要么在連接不成功時以Exception
拒絕:
$ connector -> connect ( ' google.com:443 ' )-> then (
function ( React Socket ConnectionInterface $ connection ) {
// connection successfully established
},
function ( Exception $ error ) {
// failed to connect due to $error
}
);
另請參閱ConnectionInterface
以了解更多詳細資訊。
傳回的 Promise 必須以這樣的方式實現:當它仍然處於掛起狀態時可以將其取消。取消待處理的 Promise 必須使用Exception
拒絕其值。它應該清理任何適用的底層資源和引用:
$ promise = $ connector -> connect ( $ uri );
$ promise -> cancel ();
Connector
類別是此套件中的主類,它實作ConnectorInterface
並允許您建立流連線。
您可以使用此連接器建立任何類型的流連接,例如純文字 TCP/IP、安全 TLS 或本機 Unix 連線流。
它綁定到主事件循環,可以像這樣使用:
$ connector = new React Socket Connector ();
$ connector -> connect ( $ uri )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
}, function ( Exception $ e ) {
echo ' Error: ' . $ e -> getMessage () . PHP_EOL ;
});
為了建立純文字 TCP/IP 連接,您可以簡單地傳遞主機和連接埠組合,如下所示:
$ connector -> connect ( ' www.google.com:80 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
如果您沒有在目標 URI 中指定 URI 方案,它將採用
tcp://
作為預設值並建立明文 TCP/IP 連線。請注意,TCP/IP 連線需要目標 URI 中的主機和連接埠部分(如上所示),所有其他 URI 元件都是可選的。
為了建立安全的 TLS 連接,您可以使用tls://
URI 方案,如下所示:
$ connector -> connect ( ' tls://www.google.com:443 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
為了建立本機 Unix 網域套接字連接,您可以使用unix://
URI 方案,如下所示:
$ connector -> connect ( ' unix:///tmp/demo.sock ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
getRemoteAddress()
方法將傳回給connect()
方法的目標 Unix 域套接字 (UDS) 路徑,包括unix://
方案,例如unix:///tmp/demo.sock
。getLocalAddress()
方法很可能會傳回null
值,因為該值不適用於此處的 UDS 連線。
在底層, Connector
被實現為該包中實現的較低級別連接器的較高級別外觀。這意味著它也共享它們的所有功能和實作細節。如果您想在更高層級的協定實作中輸入提示,則應該使用通用的ConnectorInterface
。
從v1.4.0
開始, Connector
類別預設使用 happy eyeballs 演算法在給定主機名稱時透過 IPv4 或 IPv6 自動連線。這會自動嘗試同時使用 IPv4 和 IPv6 進行連線(首選 IPv6),從而避免了 IPv6 連線或設定不完善的使用者所面臨的常見問題。如果您想恢復到僅進行 IPv4 查找並僅嘗試單一 IPv4 連線的舊行為,您可以像這樣設定Connector
:
$ connector = new React Socket Connector ([
' happy_eyeballs ' => false
]);
同樣,您也可以如下影響預設 DNS 行為。 Connector
類別將嘗試偵測您的系統 DNS 設定(如果無法確定您的系統設置,則使用 Google 的公共 DNS 伺服器8.8.8.8
作為後備),以預設將所有公共主機名稱解析為底層 IP 位址。如果您明確想要使用自訂 DNS 伺服器(例如本機 DNS 中繼或公司範圍的 DNS 伺服器),您可以如下設定Connector
:
$ connector = new React Socket Connector ([
' dns ' => ' 127.0.1.1 '
]);
$ connector -> connect ( ' localhost:80 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
如果您根本不想使用 DNS 解析器而只想連接到 IP 位址,您也可以像這樣設定Connector
:
$ connector = new React Socket Connector ([
' dns ' => false
]);
$ connector -> connect ( ' 127.0.0.1:80 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
進階:如果您需要自訂 DNS ReactDnsResolverResolverInterface
實例,您也可以像這樣設定Connector
:
$ dnsResolverFactory = new React Dns Resolver Factory ();
$ resolver = $ dnsResolverFactory -> createCached ( ' 127.0.1.1 ' );
$ connector = new React Socket Connector ([
' dns ' => $ resolver
]);
$ connector -> connect ( ' localhost:80 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
預設情況下, tcp://
和tls://
URI 方案將使用符合default_socket_timeout
ini 設定的逾時值(預設為 60 秒)。如果您想要自訂超時值,您可以簡單地像這樣傳遞:
$ connector = new React Socket Connector ([
' timeout ' => 10.0
]);
同樣,如果您根本不想應用超時並讓作業系統處理此問題,您可以傳遞一個布林標誌,如下所示:
$ connector = new React Socket Connector ([
' timeout ' => false
]);
預設情況下, Connector
支援tcp://
、 tls://
和unix://
URI 方案。如果您想明確禁止其中任何一個,您可以簡單地傳遞布林標誌,如下所示:
// only allow secure TLS connections
$ connector = new React Socket Connector ([
' tcp ' => false ,
' tls ' => true ,
' unix ' => false ,
));
$ connector -> connect ( ' tls://google.com:443 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
tcp://
和tls://
也接受傳遞給底層連接器的其他上下文選項。如果您想明確傳遞其他上下文選項,您可以簡單地傳遞上下文選項數組,如下所示:
// allow insecure TLS connections
$ connector = new React Socket Connector ([
' tcp ' => [
' bindto ' => ' 192.168.0.1:0 '
],
' tls ' => [
' verify_peer ' => false ,
' verify_peer_name ' => false
],
]);
$ connector -> connect ( ' tls://localhost:443 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
預設情況下,此連接器支援 TLSv1.0+,但不支援舊版 SSLv2/SSLv3。您也可以明確選擇要與遠端協商的 TLS 版本:
$ connector = new React Socket Connector ([
' tls ' => [
' crypto_method ' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
]
]);
有關上下文選項的更多詳細信息,請參閱有關套接字上下文選項和 SSL 上下文選項的 PHP 文件。
進階:預設情況下, Connector
支援tcp://
、 tls://
和unix://
URI 方案。為此,它會自動設定所需的連接器類別。如果您想要明確傳遞其中任何一個的自訂連接器,您可以簡單地傳遞一個實作ConnectorInterface
實例,如下所示:
$ dnsResolverFactory = new React Dns Resolver Factory ();
$ resolver = $ dnsResolverFactory -> createCached ( ' 127.0.1.1 ' );
$ tcp = new React Socket HappyEyeBallsConnector ( null , new React Socket TcpConnector (), $ resolver );
$ tls = new React Socket SecureConnector ( $ tcp );
$ unix = new React Socket UnixConnector ();
$ connector = new React Socket Connector ([
' tcp ' => $ tcp ,
' tls ' => $ tls ,
' unix ' => $ unix ,
' dns ' => false ,
' timeout ' => false ,
]);
$ connector -> connect ( ' google.com:80 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
在內部,
tcp://
連接器將始終由 DNS 解析器封裝,除非您像上面的範例中那樣停用 DNS。在這種情況下,tcp://
連接器接收實際的主機名,而不僅僅是解析的 IP 位址,因此負責執行查找。在內部,自動建立的tls://
連接器將始終包裝底層tcp://
連接器,以便在啟用安全 TLS 模式之前建立底層明文 TCP/IP 連線。如果您只想將自訂底層tcp://
連接器用於安全 TLS 連接,則可以像上面那樣明確傳遞tls://
連接器。在內部,tcp://
和tls://
連接器將始終由TimeoutConnector
包裝,除非您像上面的範例一樣停用逾時。
此類別採用可選的LoopInterface|null $loop
參數,可用於傳遞要用於此物件的事件循環實例。您可以在此處使用null
值以使用預設循環。除非您確定要明確使用給定的事件循環實例,否則不應給出此值。
TcpConnector
類別實作ConnectorInterface
並允許您建立到任何 IP 連接埠組合的純文字 TCP/IP 連線:
$ tcpConnector = new React Socket TcpConnector ();
$ tcpConnector -> connect ( ' 127.0.0.1:80 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
另請參閱範例。
可以透過取消其掛起的承諾來取消掛起的連線嘗試,如下所示:
$ promise = $ tcpConnector -> connect ( ' 127.0.0.1:80 ' );
$ promise -> cancel ();
對待處理的 Promise 呼叫cancel()
將關閉底層套接字資源,從而取消待處理的 TCP/IP 連接,並拒絕產生的 Promise。
此類別採用可選的LoopInterface|null $loop
參數,可用於傳遞要用於此物件的事件循環實例。您可以在此處使用null
值以使用預設循環。除非您確定要明確使用給定的事件循環實例,否則不應給出此值。
您可以選擇將其他套接字上下文選項傳遞給建構函數,如下所示:
$ tcpConnector = new React Socket TcpConnector ( null , [
' bindto ' => ' 192.168.0.1:0 '
]);
請注意,此類僅允許您連接到 IP 連接埠組合。如果給定的 URI 無效、不包含有效的 IP 位址和連接埠或包含任何其他方案,它將拒絕並拋出InvalidArgumentException
:
如果給定的 URI 看起來有效,但連線失敗(例如遠端主機拒絕連線等),它將拒絕並拋出RuntimeException
。
如果您想連接到主機名稱-連接埠組合,請另參閱下一章。
進階用法:在內部,
TcpConnector
為每個串流資源分配一個空的上下文資源。如果目標 URI 包含hostname
查詢參數,則其值將用於設定 TLS 對等名稱。SecureConnector
和DnsConnector
使用它來驗證對等名稱,如果您需要自訂 TLS 對等名稱,也可以使用它。
HappyEyeBallsConnector
類別實作ConnectorInterface
並允許您建立到任何主機名稱-連接埠組合的純文字 TCP/IP 連線。它在內部實作了RFC6555
和RFC8305
中的 happy eyeballs 演算法,以支援 IPv6 和 IPv4 主機名稱。
它透過裝飾給定的TcpConnector
實例來實現此目的,以便它首先透過 DNS(如果適用)查找給定的域名,然後建立到解析的目標 IP 位址的底層 TCP/IP 連線。
確保設定您的 DNS 解析器和底層 TCP 連接器,如下所示:
$ dnsResolverFactory = new React Dns Resolver Factory ();
$ dns = $ dnsResolverFactory -> createCached ( ' 8.8.8.8 ' );
$ dnsConnector = new React Socket HappyEyeBallsConnector ( null , $ tcpConnector , $ dns );
$ dnsConnector -> connect ( ' www.google.com:80 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
另請參閱範例。
可以透過取消其掛起的承諾來取消掛起的連線嘗試,如下所示:
$ promise = $ dnsConnector -> connect ( ' www.google.com:80 ' );
$ promise -> cancel ();
對待處理的 Promise 呼叫cancel()
將取消底層 DNS 查找和/或底層 TCP/IP 連線並拒絕產生的 Promise。
此類別採用可選的LoopInterface|null $loop
參數,可用於傳遞要用於此物件的事件循環實例。您可以在此處使用null
值以使用預設循環。除非您確定要明確使用給定的事件循環實例,否則不應給出此值。
進階用法:在內部,
HappyEyeBallsConnector
依賴Resolver
來尋找給定主機名稱的 IP 位址。然後,它將用此 IP 取代目標 URI 中的主機名,並附加hostname
查詢參數,並將此更新的 URI 傳遞到底層連接器。 Happy Eye Balls 演算法描述尋找給定主機名稱的 IPv6 和 IPv4 位址,以便此連接器發出 A 和 AAAA 記錄的兩個 DNS 查找。然後,它使用所有 IP 位址(v6 和 v4)並嘗試以 50 毫秒的間隔連接到所有這些位址。在 IPv6 和 IPv4 位址之間切換。建立連線後,所有其他 DNS 查找和連線嘗試都將被取消。
DnsConnector
類別實作ConnectorInterface
,並允許您建立到任何主機名稱-連接埠組合的純文字 TCP/IP 連線。
它透過裝飾給定的TcpConnector
實例來實現此目的,以便它首先透過 DNS(如果適用)查找給定的域名,然後建立到解析的目標 IP 位址的底層 TCP/IP 連線。
確保設定您的 DNS 解析器和底層 TCP 連接器,如下所示:
$ dnsResolverFactory = new React Dns Resolver Factory ();
$ dns = $ dnsResolverFactory -> createCached ( ' 8.8.8.8 ' );
$ dnsConnector = new React Socket DnsConnector ( $ tcpConnector , $ dns );
$ dnsConnector -> connect ( ' www.google.com:80 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( ' ... ' );
$ connection -> end ();
});
另請參閱範例。
可以透過取消其掛起的承諾來取消掛起的連線嘗試,如下所示:
$ promise = $ dnsConnector -> connect ( ' www.google.com:80 ' );
$ promise -> cancel ();
對待處理的 Promise 呼叫cancel()
將取消底層 DNS 查找和/或底層 TCP/IP 連線並拒絕產生的 Promise。
進階用法:在內部,
DnsConnector
依賴ReactDnsResolverResolverInterface
來尋找給定主機名稱的 IP 位址。然後,它將用此 IP 取代目標 URI 中的主機名,並附加hostname
查詢參數,並將此更新的 URI 傳遞到底層連接器。因此,底層連接器負責建立到目標 IP 位址的連接,而此查詢參數可用於檢查原始主機名,並由TcpConnector
用於設定 TLS 對等名稱。如果明確給予hostname
,則不會修改此查詢參數,如果您想要自訂 TLS 對等名稱,這會很有用。
SecureConnector
類別實作ConnectorInterface
,並允許您建立與任何主機名稱-連接埠組合的安全 TLS(以前稱為 SSL)連線。
它透過裝飾給定的DnsConnector
實例來實現此目的,以便它首先建立純文字 TCP/IP 連接,然後在此流上啟用 TLS 加密。
$ secureConnector = new React Socket SecureConnector ( $ dnsConnector );
$ secureConnector -> connect ( ' www.google.com:443 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( " GET / HTTP/1.0 rn Host: www.google.com rnrn" );
. . .
});
另請參閱範例。
可以透過取消其掛起的承諾來取消掛起的連線嘗試,如下所示:
$ promise = $ secureConnector -> connect ( ' www.google.com:443 ' );
$ promise -> cancel ();
對待處理的 Promise 呼叫cancel()
將取消底層 TCP/IP 連線和/或 SSL/TLS 協商並拒絕產生的 Promise。
此類別採用可選的LoopInterface|null $loop
參數,可用於傳遞要用於此物件的事件循環實例。您可以在此處使用null
值以使用預設循環。除非您確定要明確使用給定的事件循環實例,否則不應給出此值。
您可以選擇將其他 SSL 上下文選項傳遞給建構函數,如下所示:
$ secureConnector = new React Socket SecureConnector ( $ dnsConnector , null , [
' verify_peer ' => false ,
' verify_peer_name ' => false
]);
預設情況下,此連接器支援 TLSv1.0+,但不支援舊版 SSLv2/SSLv3。您也可以明確選擇要與遠端協商的 TLS 版本:
$ secureConnector = new React Socket SecureConnector ( $ dnsConnector , null , [
' crypto_method ' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
]);
進階用法:在內部,
SecureConnector
依賴在底層串流資源上設定所需的上下文選項。因此,它應該與連接器堆疊中某處的TcpConnector
一起使用,以便它可以為每個串流資源分配一個空的上下文資源並驗證對等名稱。如果不這樣做,可能會導致 TLS 對等名稱不符合錯誤或一些難以追蹤的競爭條件,因為否則所有串流資源都將使用單一共享的預設上下文資源。
TimeoutConnector
類別實作ConnectorInterface
並允許您在任何現有連接器實例中新增逾時處理。
它透過裝飾任何給定的ConnectorInterface
實例並啟動計時器來實現這一點,如果需要太長時間,該計時器將自動拒絕併中止任何底層連接嘗試。
$ timeoutConnector = new React Socket TimeoutConnector ( $ connector , 3.0 );
$ timeoutConnector -> connect ( ' google.com:80 ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
// connection succeeded within 3.0 seconds
});
另請參閱任何範例。
此類別採用可選的LoopInterface|null $loop
參數,可用於傳遞要用於此物件的事件循環實例。您可以在此處使用null
值以使用預設循環。除非您確定要明確使用給定的事件循環實例,否則不應給出此值。
可以透過取消其掛起的承諾來取消掛起的連線嘗試,如下所示:
$ promise = $ timeoutConnector -> connect ( ' google.com:80 ' );
$ promise -> cancel ();
對待處理的 Promise 呼叫cancel()
將取消底層連線嘗試、中止計時器並拒絕產生的 Promise。
UnixConnector
類別實作ConnectorInterface
並允許您連接到 Unix 網域套接字 (UDS) 路徑,如下所示:
$ connector = new React Socket UnixConnector ();
$ connector -> connect ( ' /tmp/demo.sock ' )-> then ( function ( React Socket ConnectionInterface $ connection ) {
$ connection -> write ( " HELLO n" );
});
連接到 Unix 域套接字是一個原子操作,即它的承諾將立即解決(解決或拒絕)。因此,對結果 Promise 呼叫cancel()
沒有任何效果。
getRemoteAddress()
方法將會傳回給connect()
方法的目標 Unix 域套接字 (UDS) 路徑,前面有unix://
方案,例如unix:///tmp/demo.sock
。getLocalAddress()
方法很可能會傳回null
值,因為該值不適用於此處的 UDS 連線。
此類別採用可選的LoopInterface|null $loop
參數,可用於傳遞要用於此物件的事件循環實例。您可以在此處使用null
值以使用預設循環。除非您確定要明確使用給定的事件循環實例,否則不應給出此值。
FixedUriConnector
類別實作ConnectorInterface
並裝飾現有的 Connector 以始終使用固定的預先配置 URI。
這對於不支援某些 URI 的用戶非常有用,例如當您想要明確連接到 Unix 域套接字 (UDS) 路徑而不是連接到更高級別 API 假定的預設位址時:
$ connector = new React Socket FixedUriConnector (
' unix:///var/run/docker.sock ' ,
new React Socket UnixConnector ()
);
// destination will be ignored, actually connects to Unix domain socket
$ promise = $ connector -> connect ( ' localhost:80 ' );
建議的安裝此程式庫的方法是透過 Composer。作曲家新手?
一旦發布,該專案將遵循 SemVer。目前,這將安裝最新的開發版本:
composer require react/socket:^3@dev
有關版本升級的詳細信息,請請參閱變更日誌。
該專案旨在在任何平台上運行,因此不需要任何 PHP 擴展,並支援在 PHP 7.1 到當前 PHP 8+ 上運行。強烈建議為此專案使用最新支援的 PHP 版本。
舊版 PHP < 7.3.3(和 PHP < 7.2.15)有一個錯誤,其中 feof() 可能會在碎片 TLS 記錄上出現 100% CPU 使用率時阻塞。我們嘗試透過始終一次消耗完整的接收緩衝區來解決此問題,以避免 TLS 緩衝區中的資料過時。眾所周知,這可以解決表現良好的對等方的高 CPU 使用率問題,但這可能會在高吞吐量場景中導致非常大的資料區塊。由於網路 I/O 緩衝區或受影響版本上的惡意對等點,仍然可能會觸發錯誤行為,強烈建議升級。
舊版 PHP < 7.1.4 在透過 TLS 流一次寫入大塊資料時遇到錯誤。我們嘗試透過將舊 PHP 版本的寫入區塊大小限制為 8192 位元組來解決此問題。這只是一種解決方法,並且對受影響的版本有明顯的效能損失。
要執行測試套件,您首先需要複製此儲存庫,然後透過 Composer 安裝所有相依性:
composer install
要運行測試套件,請前往專案根目錄並運行:
vendor/bin/phpunit
該測試套件還包含許多依賴穩定網路連線的功能整合測試。如果您不想運行這些,可以像這樣跳過它們:
vendor/bin/phpunit --exclude-group internet
麻省理工學院,請參閱許可證文件。