ホームページ  >  記事  >  バックエンド開発  >  PHP ネットワーク プログラミングにおける Accept ブロッキング モデルの概要

PHP ネットワーク プログラミングにおける Accept ブロッキング モデルの概要

不言
不言オリジナル
2018-07-28 10:33:442054ブラウズ
この記事の内容は、PHP ネットワーク プログラミングにおける Accept ブロッキング モデルの概要を共有することです。内容は非常に詳細です。困っている友人は参照してください。すべての人に役立つことを願っています。

受け入れブロッキング モデルは比較的古いモデルですが、ブロッキング/非ブロッキング、ロック、タイムアウト再送信など、多くの興味深い知識が含まれています...

サービス終了プログラム acceptSever.php

<?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 = &#39;&#39;; # 接受的数据
            $buffer = &#39;&#39;;  # 分段读取数据
        
            $id++; 
            echo "Client $id come" . PHP_EOL;
            
            # 持续监听
            while (!preg_match(&#39;{/r/n}&#39;, $buffer)) { // 没有读到结束符,继续读
//                if (feof($conn)) break; // 防止 popen 和 fread 的 bug 导致的死循环
                $buffer = fread($conn, 1024);
                echo &#39;R&#39; . PHP_EOL; #  打印读的次数
                $ct += strlen($buffer);
                $ct_data .= preg_replace(&#39;{/r/n}&#39;, &#39;&#39;, $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, &#39;./socket.log&#39;);
}

if ($argv[1]) {
    
    $socket_client = stream_socket_client(&#39;tcp://0.0.0.0:2000&#39;, $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 &#39;W&#39;; // 打印写的次数
#            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__." &#39;{$i}:test&#39;", &#39;r&#39;);
//    }
//    foreach ($phArr as $ph) {
//        pclose($ph);
//    }
    
    for ($i = 0; $i < 10; $i++) {
        system("php ".__FILE__." &#39;{$i}:test&#39;");    # 这里等于 php "当前文件" "脚本参数"
    }
}

コード分析

まず、上記のコード ロジックを説明します。クライアント acceptClient.php はループでデータを送信し、最後にターミネータを送信し、サーバーは accept Server.php を使用してソケット接続を受信し、ターミネータが受信されるまでループでデータを受信し、結果データ (受信バイト数) を返します。サーバーから返されたデータ、ログに書き込みます。ロジックは非常に単純ですが、分析する価値のある状況がいくつかあります:

A> デフォルトでは、phpsocket_client.php テストを実行すると、クライアントは 10 W を出力し、サーバーはいくつかの R を出力し、その後にいくつかの R が出力されます。受信データについては、socket.log にサーバーから返された受信結果データが記録されます。
PHP ネットワーク プログラミングにおける Accept ブロッキング モデルの概要
# この状況は理解しやすいため、繰り返しません。 。次に、telnet コマンドを使用して複数のクライアントを同時に開きます。図に示すように、サーバーは一度に 1 つのクライアントのみを処理することがわかります。他のものは後でキューに入れる必要があります。 "; これが IO ブロックの特徴です。このモードの弱点は明らかであり、効率は非常に低くなります。
B>socket_client.php の 29 行目のコメント コードのみを開いて、php ソケット_クライアント.php テストを再度実行します。クライアントは W を出力し、サーバーも R を出力します。その後、両方のプログラムが停止します。これはなぜでしょうか? ロジックを分析すると、クライアントがターミネータを送信する前にデータをサーバーに返したいためであり、サーバーもターミネータを受信して​​いないため、クライアントにターミネータを要求していることがわかります。 PHP ネットワーク プログラミングにおける Accept ブロッキング モデルの概要デッドロックの原因。 W と R が 1 つだけ入力されているのは、fread がデフォルトでブロックしているためです。このデッドロックを解決するには、socket_client.php の 17 行目のコメント コードを開き、ソケットの
timeout

を 0.1 秒に設定する必要があります。再度実行すると、W と R が表示されることがわかります。 0.1秒ごとに正常終了し、サーバから返される受信結果データも正常に記録されます。 fread がデフォルトでブロックしていることがわかりますが、タイムアウトを設定しないとデッドロックが発生しやすいため、プログラミング時には特に注意が必要です。

C> 14 行のコメントだけを開き、スクリプトをノンブロッキングに設定し、php ソケット_クライアント.php テストを実行します。結果は基本的にケース A と同じです。唯一の違いは、socket.log が記録されていることです。は戻りデータを記録しません。これは、ノンブロッキング モードの場合、クライアントはサーバーからの応答結果を待たずに実行を続行できるためです。デバッグが実行されるとき、読み取られたデータはまだ空であるため、socket.log はも空です。ここでは、ブロッキング モードと非ブロッキング モードで実行されているクライアントの違いがわかります。もちろん、クライアントが結果の受け入れを気にしない場合は、非ブロッキング モードを使用して最大の効率を得ることができます。 D> phpsocket_client.php の実行は、上記のロジックを 10 回連続して実行することです。これに問題はありません。しかし、非常に奇妙なのは、39 ~ 45 行のコードを使用する場合、popen を使用して同時に 10 個のプロセスを開くと、サーバー側で無限ループが発生しますが、これは非常に奇妙です。その後、調査の結果、PHP のソースコードを確認したところ、popen で開いたプロセスによって作成された接続がある限り、fread または Socket_read がエラーとなり、直接空の文字列を返し、無限ループに陥ることが判明しました。 PHP の Popen 関数と fread 関数が C にまったくネイティブではないことが判明し、大量の php_stream_* 実装ロジックが挿入されていますが、当初はそのバグによる Socket 接続の中断が原因であると推定されています。解決策は、socket_server.php の 33 行のコードを開くことです。接続が中断された場合はループから抜け出します。ただし、この方法では大量のデータが失われるため、この問題には特別な注意が必要です。

関連する推奨事項:

PHP と Apache の基本的なアプリケーション


PHP 正規表現とは何ですか? PHP 正規表現の使用方法 (コード付き)

以上がPHP ネットワーク プログラミングにおける Accept ブロッキング モデルの概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。