Le modèle de blocage d'acceptation est un modèle relativement ancien, mais il contient beaucoup de connaissances intéressantes, telles que le blocage/non-blocage, les verrous, la retransmission du délai d'attente...
<?php set_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);
<?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 < 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 "当前文件" "脚本参数" } }
Tout d'abord, expliquez la logique du code ci-dessus : le client acceptClient envoie des données dans. une boucle, et envoie enfin le terminateur ; le serveur accepte Server.php utilise la méthode de blocage accept pour recevoir la connexion socket, puis reçoit les données dans une boucle jusqu'à ce que le terminateur soit reçu, et renvoie les données de résultat (le nombre d'octets reçu) reçu par le client. Les données renvoyées par le serveur sont écrites dans le journal. Bien que la logique soit très simple, il existe plusieurs situations qui méritent d'être analysées :
A> Par défaut, lors de l'exécution du test php socket_client.php, le client imprimera 10 W, et le serveur imprimera plusieurs R, suivis de Pour les données reçues, socket.log enregistre les données de résultat de réception renvoyées par le serveur. L'effet est le suivant :
Cette situation est facile à comprendre et ne sera pas décrite. encore. Ensuite, utilisez la commande telnet pour ouvrir plusieurs clients en même temps. Vous constaterez que le serveur ne traite qu'un seul client à la fois, comme le montre la figure :
. Les autres doivent être mis en file d'attente plus tard. "; C'est la caractéristique du blocage des IO. Les faiblesses de ce mode sont évidentes et l'efficacité est extrêmement faible.
B> Ouvrez uniquement le code de commentaire sur la ligne 29 de socket_client.php et exécutez à nouveau le test php socket_client.php Le client imprime un W et le serveur imprime également un R. Après cela, les deux programmes sont bloqués. Pourquoi ? Après avoir analysé la logique, vous constaterez que cela est dû au fait que le client souhaite renvoyer les données au serveur avant d'envoyer le terminateur et que le serveur n'a pas reçu le terminateur, il demande également le terminateur au client ; Provoque une impasse. La raison pour laquelle un seul W et R est saisi est que fread bloque par défaut. Pour résoudre ce blocage, vous devez ouvrir le code de commentaire sur la ligne 17 de socket_client.php et définir un timeout de 0,1 seconde pour le socket. Si vous l'exécutez à nouveau, vous constaterez qu'un W et un R apparaissent. toutes les 0,1 secondes, puis se terminent normalement. Les données de résultat de réception renvoyées par le serveur sont également enregistrées normalement. On peut voir que fread bloque par défaut. Nous devons prêter une attention particulière lors de la programmation. Si le délai d'attente n'est pas défini, un blocage se produira facilement.
C> Ouvrez seulement 14 lignes de commentaires, définissez le script sur non bloquant et exécutez le test php socket_client.php Le résultat est fondamentalement le même que dans le cas A. La seule différence est que le socket.log. n'enregistre pas les données de retour. En effet, dans notre mode non bloquant, le client n'a pas à attendre le résultat de la réponse du serveur pour continuer l'exécution. Lorsque le débogage est exécuté, les données lues sont toujours vides, donc le socket. .log est également vide. Ici, vous pouvez voir la différence entre le client fonctionnant en mode bloquant et non bloquant. Bien entendu, lorsque le client ne se soucie pas d'accepter les résultats, le mode non bloquant peut être utilisé pour obtenir une efficacité maximale.
D> Exécuter php socket_client.php consiste à exécuter la logique ci-dessus 10 fois en continu. Cela ne pose aucun problème, mais ce qui est très étrange, c'est que si vous utilisez 39 à 45 lignes de code, utilisez popen pour. ouvrir 10 processus en même temps, provoquera une boucle infinie côté serveur, ce qui est très bizarre ! Plus tard, après enquête, il a été constaté que tant que la connexion créée par le processus ouvert avec popen provoquerait une erreur fread ou socket_read et renverrait directement une chaîne vide, entraînant une boucle infinie, après vérification du code source PHP, c'était a découvert que les fonctions popen et fread de PHP ne sont pas du tout natives de C. , une grande quantité de logique d'implémentation de php_stream_* y a été insérée. On estime initialement que cela est dû à l'interruption de la connexion Socket causée par un bug. La solution est d'ouvrir les 33 lignes de code dans socket_server.php. Si la connexion est interrompue, sortez de la boucle, mais de cette façon, beaucoup de données seront perdues, et ce problème nécessite une attention particulière !
Recommandations associées :
Applications de base de PHP et Apache
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!