Accept 阻塞模型是一种相对古老的模型,不过里面蕴含了许多有趣的知识,比如阻塞/非阻塞、锁、超时重传…
服务端程序 acceptSever.php
<?phpset_time_limit(0); # 设置脚本执行时间无限制class SocketServer { private static $socket; function SocketServer($port) { global $errno, $errstr; if ($port < 1024) { die("Port must be a number which bigger than 1024/n"); } $socket = stream_socket_server("tcp://0.0.0.0:{$port}", $errno, $errstr); if (!$socket) die("$errstr ($errno)"); while ($conn = stream_socket_accept($socket, -1)) { // 这样设置不超时才有用 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);
客户端程序 acceptClient.php
<?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($sock<mark>(本文来)源gaodaimacom搞#^代%!码&网(</mark><pre>搞gaodaima代码
et_client, 0); /* 设置脚本超时时间 */# stream_set_timeout($socket_client, 0, 100000); if (!$socket_client) { die("$errstr ($errno)"); } else { # 填充容器 $msg = trim($argv[1]); for ($i = 0; $i < 10; $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 < 10; $i++) {// $phArr[$i] = popen("php ".__FILE__." '{$i}:test'", 'r');// }// foreach ($phArr as $ph) {// pclose($ph);// } for ($i = 0; $i < 10; $i++) { system("php ".__FILE__." '{$i}:test'"); # 这里等于 php "当前文件" "脚本参数" }}
代码解析
首先,解释一下以上的代码逻辑:客户端 acceptClient.php 循环发送数据,最后发送结束符;服务端 accept Server.php 使用 accept 阻塞方式接收 socket 连接,然后循环接收数据,直到收到结束符,返回结果数据(接收到的字节数)客户端收到服务器返回的数据,写入日志。虽然逻辑很简单,但是其中有几种情况很值得分析一下: