Heim  >  Artikel  >  PHP-Framework  >  Teilen Sie das benutzerdefinierte Protokoll von Workerman, um das Sticky- und Unpacking-Problem zu lösen

Teilen Sie das benutzerdefinierte Protokoll von Workerman, um das Sticky- und Unpacking-Problem zu lösen

青灯夜游
青灯夜游nach vorne
2022-12-12 20:09:511720Durchsuche

workermanWie löst das angepasste Protokoll Probleme mit dem Anhaften und Auspacken? Im folgenden Artikel erfahren Sie, wie das benutzerdefinierte Protokoll von Workerman das Problem des Sticky Packets und des Entpackens löst. Ich hoffe, dass es für alle hilfreich ist.

Teilen Sie das benutzerdefinierte Protokoll von Workerman, um das Sticky- und Unpacking-Problem zu lösen

Vorwort:

Da ich kürzlich Workerman verwendet habe, um die Serverseite von Unity3D-Onlinespielen zu implementieren, obwohl eine direkte Kommunikation auch über das TCP-Protokoll erreicht werden kann, wurden während des eigentlichen Tests einige kleinere Probleme entdeckt. [Verwandte Empfehlung: „workerman Tutorial“]

Liegen die Datenpakete auf beiden Seiten beispielsweise in Form von Zeichenfolgen vor? Da es sich um Zeichenfolgen handelt, müssen sie außerdem abgeschnitten werden, und manchmal werden sie beim Client angezeigt oder der Server empfängt sie. Nach dem Drucken der Protokolle wurde festgestellt, dass die von beiden Enden empfangenen Pakete ein Format hatten, auf das sie sich nicht im Voraus geeinigt hatten. Dies ist das Phänomen des TCP Sticky und Unpacking. Die Lösung hierfür ist sehr einfach und es gibt viele im Internet, aber hier möchte ich das von mir implementierte Protokoll verwenden, um es zu lösen, und ich werde es für später aufheben.

Frage und Antwort:

Ich habe im Internet einige Konventionen zum Kommunikationsdatenpaketformat von Online-Spielen gelesen. Wenn Sie für serverseitige Skripts keine schwach typisierte Sprache verwenden, verwenden andere häufig Byte-Arrays. Wenn PHP jedoch ein Byte-Array empfängt, handelt es sich tatsächlich um eine Zeichenfolge. Voraussetzung ist jedoch, dass das Byte-Array keine spezifische Konvertierung aufweist. Nehmen Sie C# als Beispiel: Bei der Lösung von Problemen wie Sticky Packets wird die Bytelänge (BitConverter.GetBytes (len)) vor dem Byte-Array hinzugefügt. Wenn dies jedoch zum Empfang an den PHP-Server übergeben wird, können die ersten 4 Bytes der Zeichenfolge nicht angezeigt und auch nach vielen Konvertierungsmethoden nicht abgerufen werden. Später habe ich auch darüber nachgedacht, die Protobuf-Datenmethode zu verwenden. Obwohl PHP Daten konvertieren kann, habe ich aufgegeben, weil ich mit dem Client-C# nicht vertraut war.

Ein weiteres Problem besteht darin, dass die meisten von anderen Online-Spieleservern verwendeten Frame-Synchronisierungen tatsächlich das UDP-Protokoll verwenden, das auch von TCP und UDP gemeinsam genutzt wird. Wenn es sich jedoch nur um ein kleines Multiplayer-Onlinespiel handelt, ist es durchaus möglich, PHP als Server und die TCP-Protokollkommunikation zu verwenden. Als nächstes kehren wir zum benutzerdefinierten Protokoll von Workerman und den Sticky- und Unpacking-Problemen zurück.

Benutzerdefiniertes Protokoll:

Workerman hat mehrere Socket-Funktionen von PHP gekapselt (in Bezug auf die Socket-Funktion kann PHP auch ein Dateiübertragungs-Gadget schreiben, wenn Sie bereit sind, damit herumzuspielen), das ebenfalls integriert ist TCP Es verfügt über mehrere Protokolle der Anwendungsschicht, wie HTTP, Websocket, Frame usw. Außerdem ist es den Benutzern vorbehalten, ihre eigenen Protokolle zu definieren. Sie müssen lediglich die ProtocolInterface-Schnittstelle implementieren. Im Folgenden finden Sie eine kurze Einführung in mehrere Methoden, die für die folgenden Schnittstellen implementiert werden müssen.

1. Eingabemethode

Bei dieser Methode kann das Datenpaket entpackt, überprüft, gefiltert usw. werden, bevor es vom Server empfangen wird. Die Rückgabe von 0 bedeutet, dass das Datenpaket in den Puffer des Empfängers gelegt und weiter gewartet wird. Die Rückgabe der angegebenen Länge bedeutet, dass die Länge aus dem Puffer entfernt wird. Im Ausnahmefall können Sie auch false zurückgeben, um die Clientverbindung direkt zu schließen.

2. Kodierungsmethode

Bei dieser Methode handelt es sich um die Verarbeitung des Datenpaketformats durch den Server, bevor das Datenpaket an den Client gesendet wird. Dies muss zwischen Front-End und Back-End vereinbart werden .

3. Dekodierungsmethode

Bei dieser Methode handelt es sich ebenfalls um ein Entpacken, bei dem die angegebene Länge vom Puffer an den Ort gebracht wird, an dem onMessage vor dem Empfang verarbeitet werden muss, z. B. bei der logischen Bereitstellung usw.

Das Phänomen des Sticky Packet Unpacking:

Da TCP auf Streams basiert und die Transportschicht ist, kennt die Anwendung der oberen Schicht, wenn sie über Sockets (als Schnittstellen verstanden) kommuniziert, die übergebenen Daten nicht über Wo beginnt und endet das Paket? Senden Sie einfach den Kleber oder entbündeln Sie ihn gemäß den Überlastungsalgorithmen von TCP. Sticky Packets sind also im wahrsten Sinne des Wortes mehrere zusammen gesendete Datenpakete. Ursprünglich gab es zwei Pakete, aber der Client empfing nur ein Paket. Beim Entpacken wird ein Datenpaket in mehrere Pakete aufgeteilt. Es hätte ein Datenpaket empfangen sollen, aber es wurde nur eines empfangen. Wenn das Problem daher nicht behoben wird, wie bereits erwähnt, und die Zeichenfolge gemäß der Vereinbarung übertragen wird, kann es beim Entpacken zu einem Fehler kommen.

Lösung zum Entpacken von Sticky-Paketen:

1. Header plus Datenpaketlänge

<?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. Spezifische Zeichensegmentierung

<?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, "#");
    }
}

Sticky-Packet-Entpackungstest:

Hier demonstrieren wir nur spezifische Die Lösung zum String-Splitting, da es immer noch ein Problem mit den 4 Bytes plus Paketlänge auf der ersten Seite oben gibt. Das heißt, wenn das Paket zum ersten Mal ohne die Paketlänge gesendet wird, bleiben nachfolgende Simulationen des Anhaftens oder Entpackens im Puffer. Sie können sich für die folgende Demonstration auf den obigen Code beziehen.

1. Service-Start und Client-Verbindung

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. 拆包测试

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

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

Das obige ist der detaillierte Inhalt vonTeilen Sie das benutzerdefinierte Protokoll von Workerman, um das Sticky- und Unpacking-Problem zu lösen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:csdn.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen