本篇文章為大家帶來了關於php socket的相關知識,其中主要介紹了IO多路復用,以及php socket如何實作web伺服器?有興趣的朋友下面一起來看一下,希望對大家有幫助。
多路復用
前文 透過原生socket 實現了簡單的服務端與客戶端通信,但當有多個客戶端連接時,服務端僅能處理第一個客戶端的請求,而無法對後續客戶端服務
產生這種情況的原因是因為IO模型是阻塞的,同一時刻只能由一個客戶端進行訪問,解決此問題主要有兩種解決方案:
多進程,即在服務端啟動多個進程監聽
IO多路復用機制,簡單來說實作了N 個用戶端使用一條網線同時存取
同時多路復用又分為兩個不同的模型,即select
與epoll
,常見的軟體中,Apache
使用了select
模型,nginx
則使用epoll
模型。在php 中內建了select
模型,對應的函數為socket_select
,多路復用是實作http 伺服器的基礎
#在前文我們介紹了php 原生socket 內建了socket_select
函式實作了select
模型,其語法如下:
socket_select( array &$read, array &$write, array &$except, int $seconds [, int $microseconds = 0]): int|false
read
服務端監聽的套接字資源,當他有變化(即收到新的訊息或有客戶端連線、中斷)時, socket_select
函數才會傳回(否則繼續阻塞),同時修改變數為目前發生事件(收到訊息或有客戶端連線、中斷)的套接字資源列表,並繼續往下執行。
write
監聽是否有客戶端寫數據,傳入null
則代表不關心是否有寫入變化
except
套接字內要排除的元素,傳入null
是「監聽」 全部
seconds
秒和微秒一起構成逾時參數。如果傳入null
則會阻塞,為0 非阻塞,如果是>0 則為最大阻塞時間
microseconds
我們在上篇文章 簡單實作了socket 服務端監聽與客戶端的連接,接下來我們在服務端監聽程式碼的基礎上透過多工優化程式碼:
<?php // 创建套接字 $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // 设置 ip 被释放后立即可使用 socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, true); // 绑定ip与端口 socket_bind($socket, 0, 8888); // 开始监听 socket_listen($socket); $sockets[] = $socket; while (true) { $tmp_sockets = $sockets; socket_select($tmp_sockets, $write, $except, null); foreach ($tmp_sockets as $sock) { // 如果当前套接字等于 socket_create 创建的套接字,说明是有新的连接或有新的断开连接 if ($sock == $socket) { $conn_sock = socket_accept($socket); $sockets[] = $conn_sock; socket_getpeername($conn_sock, $ip, $port); echo '请求ip: ' . $ip . '端口: ' . $port . PHP_EOL; } else { // 否则说明是之前连接的客户端发来消息 $msg = socket_read($sock, 10240); socket_write($sock, strtoupper($msg)); echo $msg; } } }
在本範例中socket_select
函數會阻塞目前進程,當$tmp_sockets
陣列內的socket 資源有新的當客戶端連線或中斷或收到新訊息時,會將$tmp_sockets
陣列修改為目前活躍的socket 資源,隨後透過遍歷該陣列處理業務邏輯
#使用socket實作簡易http伺服器
http 協定是在socket 的基礎上規定了指定的資料格式,所以我們只需在socket_write
時按照格式發送數據,瀏覽器就可正常回應請求
<?php // 创建套接字 $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // 设置 ip 被释放后立即可使用 socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, true); // 绑定ip与端口 socket_bind($socket, 0, 8888); // 开始监听 socket_listen($socket); $sockets[] = $socket; while (true) { $tmp_sockets = $sockets; socket_select($tmp_sockets, $write, $except, null); foreach ($tmp_sockets as $sock) { if ($sock == $socket) { $conn_sock = socket_accept($socket); $sockets[] = $conn_sock; } else { $msg = socket_read($sock, 10240); var_dump($msg); if ($msg == '') return; $output = '<h1>this is php worker</h1>'; $len = strlen($output); $response = "HTTP/1.1 200 OK\r\n"; $response .= "content-type: text/html\r\n"; $response .= "server: php socket\r\n"; $response .= "Content-Length: {$len}\r\n\r\n"; $response .= $output; socket_write($sock, $response); } } }
在服務端運行此範例,隨後在瀏覽器存取ip:8888
,可以看到如下:
同時服務端會輸出如下內容:
GET / HTTP/1.1 Host: 124.222.**.**:8888 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: jenkins-timestamper-offset=-28800000; _ga=GA1.1.1403944751.1652010033; _ga_2GM6102E19=GS1.1.1652802985.7.1.1652803014.0
該內容即為用戶端請求原始數據,可解析此數據並根據請求做出回應,例如使用file_get_content
讀取指定檔案內容回傳至瀏覽器
推薦學習:《PHP視訊教學》 #
#
以上是PHP+Socket系列之IO多路復用及實作web伺服器的詳細內容。更多資訊請關注PHP中文網其他相關文章!