Accept 阻塞模型是一種相對古老的模型,不過里面蘊含了許多有趣的知識,比如阻塞/非阻塞、鎖、超時重傳...
服務端程序 acceptSever.php?php客戶端程序 acceptClient.php
$socket = stream_socket_server( tcp://0.0.0.0:{$port} , $errno, $errstr); if (!$socket) die( $errstr ($errno)
while ($conn = stream_socket_accept($socket, -1)) { // 這樣設置不超時才有用 html' target='_blank'>static $id = 0; # 進程 id static $ct = 0; # 接收數據的長度 $ct_last = $ct; $ct_data = # 接受的數據 $buffer = # 分段讀取數據 $id++; echo Client $id come . PHP_EOL; # 持續監聽 while (!preg_match( {/r/n} , $buffer)) { // 沒有讀到結束符,繼續讀// if (feof($conn)) break; // 防止 popen 和 fread 的 bug 導致的死循環 $buffer = fread($conn, 1024); echo R . PHP_EOL; # 打印讀的次數 $ct += strlen($buffer); $ct_data .= preg_replace( {/r/n} , , $buffer); $ct_size = ($ct - $ct_last) * 8; echo [$id] . __METHOD__ . . $ct_data . PHP_EOL; fwrite($conn, Received $ct_size byte data./r/n fclose($conn); fclose($socket);new SocketServer(2000);
?php# 日志記錄function debug ($msg) error_log($msg, 3, ./socket.log if ($argv[1]) { $socket_client = stream_socket_client( tcp://0.0.0.0:2000 , $errno, $errstr, 30); /* 設置腳本為非阻塞 */# stream_set_blocking($socket_client, 0); /* 設置腳本超時時間 */# stream_set_timeout($socket_client, 0, 100000); if (!$socket_client) { die( $errstr ($errno) } else { # 填充容器 $msg = trim($argv[1]); for ($i = 0; $i $i++) { $res = fwrite($socket_client, $msg($i) usleep(100000); echo W // 打印寫的次數# debug(fread($socket_client, 1024)); // 將產生死鎖,因為 fread 在阻塞模式下未讀到數據時將等待 fwrite($socket_client, /r/n // 傳輸結束符 # 記錄日志 debug(fread($socket_client, 1024)); fclose($socket_client);else {// $phArr = array();// for ($i = 0; $i $i++) {// $phArr[$i] = popen( php .__FILE__. {$i}:test , r // foreach ($phArr as $ph) {// pclose($ph); for ($i = 0; $i $i++) { system( php .__FILE__. {$i}:test # 這里等于 php 當前文件 腳本參數 }代碼解析
首先,解釋一下以上的代碼邏輯:客戶端 acceptClient.php 循環發送數據,最后發送結束符;服務端 accept Server.php 使用 accept 阻塞方式接收 socket 連接,然后循環接收數據,直到收到結束符,返回結果數據(接收到的字節數)客戶端收到服務器返回的數據,寫入日志。雖然邏輯很簡單,但是其中有幾種情況很值得分析一下:
A 默認情況下,運行 php socket_client.php test,客戶端打出 10 個 W,服務端打出若干個 R 后面是接收到的數據,socket.log 記錄下服務端返回的接收結果數據,效果如下:
這種情況很容易理解,不再贅述。然后,使用 telnet 命令同時打開多個客戶端,你會發現服務器一個時間只處理一個客戶端,如圖所示:
其他需要在后面“排隊”;這就是阻塞 IO 的特點,這種模式的弱點很明顯,效率極低。
B 只打開 socket_client.php 第 29 行的注釋代碼,再次運行 php socket_client.php test 客戶端打出一個 W,服務端也打出一個 R,之后兩個程序都卡住了。這是為什么呢,分析邏輯后你會發現,這是由于客戶端在未發送結束符之前就向服務端要返回數據;而服務端由于未收到結束符,也在向客戶端要結束符,造成死鎖。而之所以只打出一個 W 和 R,是因為 fread 默認是阻塞的。要解決這個死鎖,必須打開 socket_client.php 第 17 行的注釋代碼,給 socket 設置一個 0.1 秒的超時,再次運行你會發現隔 0.1 秒出現一個 W 和 R 之后正常結束,服務端返回的接收結果數據也正常記錄了??梢?fread 缺省是阻塞的,我們在編程的時候要特別注意,如果沒有設置超時,就很容易會出現死鎖。
C 只打開 14 行注釋設置腳本為非阻塞,運行 php socket_client.php test,結果基本和情況 A 相同,唯一不同的是 socket.log 沒有記錄下返回數據,這是因為當我們非阻塞下客戶端不必等待接收到服務器的響應結果就可以繼續往下執行,當執行到 debug 的時候,讀取的數據還是空的,所以 socket.log 也是空的。這里可以看出客戶端運行在阻塞和非阻塞模式的區別,當然在客戶端不在乎接受結果的情況下,可以使用非阻塞模式來獲得最大效率。
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。
新聞熱點
疑難解答