ホームページ  >  記事  >  PHPフレームワーク  >  Workerman のカスタム プロトコルを共有して、粘着性と解凍の問題を解決します

Workerman のカスタム プロトコルを共有して、粘着性と解凍の問題を解決します

青灯夜游
青灯夜游転載
2022-12-12 20:09:511721ブラウズ

workermanカスタマイズされたプロトコルのスティッキーとアンパックを解決するにはどうすればよいですか?次の記事では、Workerman のカスタム プロトコルがスティッキー パケットとアンパックの問題をどのように解決するかを紹介します。

Workerman のカスタム プロトコルを共有して、粘着性と解凍の問題を解決します

前書き:

私は最近、workerman を使用して Unity3D オンライン ゲームのサーバー側を実装しています。 TCP プロトコルを通じて直接通信することもできますが、実際のテスト中にいくつかの小さな問題が発見されました。 [関連する推奨事項: "workermanTutorial"]

たとえば、両側のデータ パケットは文字列の形式ですか? また、文字列であるため、カットする必要があります。クライアントまたはサーバーがそれを受信したときにエラーが発生する場合があります。ログを出力したところ、両端で受信したパケットの形式が事前に合意されていないことが判明しました (TCP スティッキーおよびアンパック現象)。これに対する解決策は非常に簡単で、インターネット上にたくさんありますが、ここでは、私が実装したプロトコルを使用して解決したいと思います。それは後で残しておきます。

質問への答え:

インターネット上のオンライン ゲームの通信データ パケットの形式に関するいくつかの規則も見たことがあります。サーバーサイド スクリプトを実行するために弱い型指定の言語を使用しない場合、他の言語ではバイト配列が使用されることがよくあります。ただし、PHP がバイト配列を受け取るとき、それは実際には文字列ですが、バイト配列には特定の変換が行われないことが前提となります。 C# を例に挙げると、スティッキー パケットなどの問題を解決する場合、バイト配列の前にバイト長 (BitConverter.GetBytes (len)) が追加されます。しかし、これを PHP サーバーに渡して受信すると、最初の 4 バイトが表示されず、いくら変換しても取得できません。その後、Protobuf データメソッドを使用することも考えましたが、PHP ではデータ変換はできますが、クライアント C# に慣れていないため断念しました。

もう 1 つの問題は、実際、他のオンライン ゲーム サーバーで使用されるフレーム同期のほとんどは、TCP と UDP でも共有される UDP プロトコルを使用していることです。ただし、小規模なマルチプレイヤー オンライン ゲームであれば、サーバーとして PHP を使用し、TCP プロトコル通信を使用することは完全に可能です。次に、workerman のカスタム プロトコルとスティッキーおよびアンパックの問題に戻りましょう。

カスタム プロトコル:

Workerman は、PHP のいくつかのソケット関数をカプセル化しました (ソケット関数に関しては、手間をかけても PHP でファイル転送関数を作成することもできます)ガジェット)は、TCP に基づいており、Http、Websocket、Frame などのいくつかのアプリケーション層プロトコルも付属しています。また、ユーザーが独自のプロトコルを定義するための交差部分も予約されています。ユーザーが必要とするのは、その ProtocolInterface インターフェイスを実装することだけです。以下は、次のインターフェイスに実装する必要があるいくつかのメソッドの簡単な紹介です。

1. 入力メソッド

このメソッドでは、サーバーが受信する前に、データ パケットを解凍、パケット長のチェック、フィルタリングなどを行うことができます。 0を返す場合はデータパケットを受信側のバッファに入れて待ち続けることを意味し、指定した長さを返す場合はバッファ内の長さを取り出すことを意味します。例外が発生した場合は、false を返してクライアント接続を直接閉じることもできます。

2. エンコード方式

この方式は、データ パケットをクライアントに送信する前に、サーバーによってデータ パケットの形式を処理する、つまりパケットのカプセル化です。この方法は、フロントエンドとバックエンドが合意されています。

3. decode メソッド

このメソッドはアンパックでもあり、onMessage が実行される前にバッファから処理が必要な場所まで指定された長さを取得します。ロジック調整などを受け付けております。

スティッキー パケットのアンパックによって引き起こされる現象:

TCP はストリームに基づいており、トランスポート層であるため、上位層のアプリケーションはソケットを使用します (次のように理解されます)。インターフェイス)通信では、渡されたデータパケットの始まりと終わりがどこにあるのかわかりません。 TCP の一連の輻輳アルゴリズムに従って、グルーを送信するかバンドルを解除するだけです。文字通り、スティッキー パケットは一緒に送信される複数のデータ パケットです。元々は 2 つのパケットがありましたが、クライアントは 1 つのパケットしか受信しませんでした。アンパッキングとは、データ パケットを複数のパケットに分割することで、本来は 1 つのデータ パケットを受信するはずですが、1 つのデータ パケットしか受信しませんでした。したがって、これが解決されていない場合、前述したように、取り決めに従って文字列が送信されると、解凍時にエラーが報告される可能性があります。

スティッキー パケット アンパックの解決策:

1. ヘッダーにパケット長を追加します

<?php
/**
 * This file is part of game.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author    beiqiaosu
 * @link      http://www.zerofc.cn
 */
namespace Workerman\Protocols;

use Workerman\Connection\TcpConnection;

/**
 * Frame Protocol.
 */
class Game
{
    /**
     * Check the integrity of the package.
     *
     * @param string        $buffer
     * @param TcpConnection $connection
     * @return int
     */
    public static function input($buffer, TcpConnection $connection)
    {
        // 数据包前4个字节
        $bodyLen = intval(substr($buffer, 0 , 4));
        $totalLen = strlen($buffer);

        if ($totalLen < 4) {
            return 0;
        }

        if ($bodyLen <= 0) {
            return 0;
        }

        if ($bodyLen > strlen(substr($buffer, 4))) {
            return 0;
        }

        return $bodyLen + 4;
    }

    /**
     * Decode.
     *
     * @param string $buffer
     * @return string
     */
    public static function decode($buffer)
    {
        return substr($buffer, 4);
    }

    /**
     * Encode.
     *
     * @param string $buffer
     * @return string
     */
    public static function encode($buffer)
    {
        // 对数据包长度向左补零
        $bodyLen = strlen($buffer);
        $headerStr = str_pad($bodyLen, 4, 0, STR_PAD_LEFT);

        return $headerStr . $buffer;
    }
}

2特定の文字のセグメンテーション

<?php

namespace Workerman\Protocols;

use Workerman\Connection\ConnectionInterface;

/**
 * Text Protocol.
 */
class Tank
{
    /**
     * Check the integrity of the package.
     *
     * @param string        $buffer
     * @param ConnectionInterface $connection
     * @return int
     */
    public static function input($buffer, ConnectionInterface $connection)
    {
        
        if (isset($connection->maxPackageSize) && \strlen($buffer) >= $connection->maxPackageSize) {
            $connection->close();
            return 0;
        }
        
        $pos = \strpos($buffer, "#");
        
        if ($pos === false) {
            return 0;
        }
        
        // 返回当前包长
        return $pos + 1;
    }

    /**
     * Encode.
     *
     * @param string $buffer
     * @return string
     */
    public static function encode($buffer)
    {
        return $buffer . "#";
    }

    /**
     * Decode.
     *
     * @param string $buffer
     * @return string
     */
    public static function decode($buffer)
    {
        return \rtrim($buffer, "#");
    }
}

スティッキー パケットのアンパッキング テスト:

ここでは、特定の文字列のセグメンテーションに対する解決策のみを示します。上記のホームページ 長いパッケージにはまだ問題があります。つまり、最初にパケット長を指定せずにパケットが送信されると、その後のスティッキングまたはアンパッキングのシミュレーションはバッファーに残ります。次のデモンストレーションについては、上記のコードを参照してください。

1. サービスの起動とクライアント接続

2. 服务业务端代码

        数据包格式说明一下,字符串以逗号分割,数据包以 #分割,逗号分割第一组是业务方法,如 Login 表示登陆传递,Pos 表示坐标传递,后面带的就是对应方法需要的参数了。

<?php

use Workerman\Worker;

require_once __DIR__ . &#39;/vendor/autoload.php&#39;;

// #### create socket and listen 1234 port ####
$worker = new Worker(&#39;tank://0.0.0.0:1234&#39;);

// 4 processes
//$worker->count = 4;

$worker->onWorkerStart = function ($connection) {
    echo "游戏协议服务启动……";
};

// Emitted when new connection come
$worker->onConnect = function ($connection) {
    echo "New Connection\n";
    $connection->send("address: " . $connection->getRemoteIp() . " " . $connection->getRemotePort());
};

// Emitted when data received
$worker->onMessage = function ($connection, $data) use ($worker, $stream) {

    echo "接收的数据:" . $data . "\n";

    // 简单实现接口分发
    $arr = explode(",", $data);

    if (!is_array($arr) || !count($arr)) {
        $connection->close("数据格式错误", true);
    }

    $func = strtoupper($arr[0]);
    $client = $connection->getRemoteAddress();

    switch($func) {
        case "LOGIN":
            $sendData = "Login1";
            break;
        case "POS":
            $positionX = $arr[1] ?? 0;
            $positionY = $arr[2] ?? 0;
            $positionZ = $arr[3] ?? 0;

            $sendData = "POS,$client,$positionX,$positionY,$positionZ";
            break;
    }

    $connection->send($sendData);
};

// Emitted when connection is closed
$worker->onClose = function ($connection) {
    echo "Connection closed\n";
};


// 接收缓冲区溢出回调
$worker->onBufferFull = function ($connection) {
    echo "清理缓冲区吧";
};

Worker::runAll();

?>

3. 粘包测试

    只需要在客户端模拟两个数据包连在一起,但是要以 #分隔,看看服务端接收的时候是一几个包进行处理的。

4. 拆包测试

        拆包模拟只需要将一个数据包分成两次发送,看看服务端接收的时候能不能显示或者说能不能按约定好的格式正确显示。

更多编程相关知识,请访问:编程教学!!

以上がWorkerman のカスタム プロトコルを共有して、粘着性と解凍の問題を解決しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。