> PHP 프레임워크 > Workerman > 끈적임과 포장 풀기 문제를 해결하기 위해 Workerman의 맞춤형 프로토콜을 공유합니다.

끈적임과 포장 풀기 문제를 해결하기 위해 Workerman의 맞춤형 프로토콜을 공유합니다.

青灯夜游
풀어 주다: 2022-12-12 20:21:35
앞으로
1808명이 탐색했습니다.

workerman맞춤형 프로토콜은 끈적임과 포장 풀기 문제를 어떻게 해결하나요? 다음 기사에서는 Workerman의 사용자 정의 프로토콜이 끈적한 패킷 및 압축 풀기 문제를 어떻게 해결하는지 소개합니다. 도움이 되기를 바랍니다.

끈적임과 포장 풀기 문제를 해결하기 위해 Workerman의 맞춤형 프로토콜을 공유합니다.

서문:

최근에 Workerman을 사용하여 Unity3D 온라인 게임의 서버측을 구현했기 때문에 TCP 프로토콜을 통해서도 직접 통신이 가능하지만 실제 테스트 중에 몇 가지 사소한 문제가 발견되었습니다. [관련 추천 : "workerman Tutorial"]

예를 들어 양쪽의 데이터 패킷이 문자열 형태인가요? 그리고 문자열이기 때문에 잘라내야 하고 클라이언트에서 가끔 나타날 때도 있습니다. 또는 서버가 오류를 보고합니다. 로그를 출력한 결과, 양쪽 끝에서 수신한 패킷이 사전에 합의되지 않은 형식인 것으로 나타났습니다. 이는 TCP 끈적임 및 언패킹 현상입니다. 이에 대한 해결책은 매우 간단하고 인터넷에 많이 있지만 여기서는 이를 해결하기 위해 구현한 프로토콜을 사용하고 싶기 때문에 나중에 남겨 두겠습니다.

질문과 답변:

또한 온라인 게임의 통신 데이터 패킷 형식에 대한 몇 가지 관례를 인터넷에서 본 적이 있습니다. 서버 측 스크립트를 수행하기 위해 약한 유형의 언어를 사용하지 않는 경우 다른 사람들은 바이트 배열을 사용하는 경우가 많습니다. 그러나 PHP가 바이트 배열을 수신하면 실제로는 문자열이지만 전제는 바이트 배열에 특정 변환이 없다는 것입니다. C#을 예로 들면 고정 패킷과 같은 문제를 해결할 때 바이트 길이(BitConverter.GetBytes(len))가 바이트 배열 앞에 추가됩니다. 그러나 이것이 수신을 위해 PHP 서버로 전달되면 문자열의 처음 4바이트가 표시되지 않고 여러 가지 변환 방법을 사용해도 검색할 수 없습니다. 나중에는 Protobuf 데이터 방식을 사용해볼까도 생각했지만, PHP에서는 데이터 변환이 가능하지만, 클라이언트 C#에 익숙하지 않아서 포기했습니다.

또 다른 문제는 실제로 다른 온라인 게임 서버에서 사용하는 프레임 동기화의 대부분이 TCP와 UDP에서도 공유되는 UDP 프로토콜을 사용한다는 것입니다. 그러나 소규모 멀티플레이어 온라인 게임이라면 PHP를 서버로 사용하고 TCP 프로토콜 통신을 사용하는 것은 완전히 가능합니다. 다음으로, 워커맨의 커스텀 프로토콜과 끈끈한 문제와 압축 풀기 문제로 돌아가겠습니다.

맞춤형 프로토콜:

Workerman은 PHP의 여러 소켓 기능을 캡슐화했습니다(소켓 기능과 관련하여, 기꺼이 조작하려는 경우 PHP는 파일 전송 가젯을 작성할 수도 있음). 이 역시 TCP 기반으로 내장되어 있습니다. Http, Websocket, Frame 등과 같은 여러 애플리케이션 계층 프로토콜이 있습니다. 또한 사용자가 자신의 프로토콜을 정의할 수 있는 교차점을 예약합니다. ProtocolInterface 인터페이스를 구현하기만 하면 됩니다. 다음은 다음 인터페이스에 대해 구현해야 하는 여러 메서드에 대한 간략한 소개입니다.

1. 입력 방법

이 방법에서는 데이터 패킷을 서버에서 수신하기 전에 압축 해제, 패킷 길이 확인, 필터링 등을 수행할 수 있습니다. 0을 반환한다는 것은 데이터 패킷을 수신 측의 버퍼에 넣고 계속 기다리는 것을 의미합니다. 지정된 길이를 반환한다는 것은 버퍼에서 길이를 꺼내는 것을 의미합니다. 예외가 있는 경우 false를 반환하여 클라이언트 연결을 직접 닫을 수도 있습니다.

2. 인코딩 방법

이 방법은 데이터 패킷을 클라이언트에 보내기 전에 서버에서 데이터 패킷 형식을 처리하는 것입니다. 즉, 패킷 캡슐화는 프런트엔드와 백엔드에서 합의해야 합니다. .

3. 디코드 방법

이 방법도 unpacking인데, 논리적 배포 등 onMessage를 수신하기 전에 처리해야 하는 위치로 버퍼에서 지정된 길이를 가져오는 것입니다.

sticky packet unpacking으로 인한 현상:

TCP는 스트림 기반이고 전송 계층이므로 상위 계층 응용 프로그램이 소켓(인터페이스로 이해됨)을 통해 통신할 때 전달된 데이터를 알지 못합니다. 패키지의 시작과 끝은 어디입니까? TCP의 혼잡 알고리즘 세트에 따라 글루를 보내거나 번들을 풀면 됩니다. 즉, 끈적한 패킷은 말 그대로 여러 개의 데이터 패킷이 함께 전송되는 것입니다. 원래는 두 개의 패킷이 있었지만 클라이언트는 한 개의 패킷만 받았습니다. 언패킹은 데이터 패킷을 여러 개의 패킷으로 분할하는 것입니다. 하나의 데이터 패킷을 수신해야 하는데 하나만 수신되었습니다. 따라서, 이것이 해결되지 않고 앞서 언급한 바와 같이 약정에 따라 문자열을 전송하게 되면 언패킹 시 오류가 보고될 수 있습니다.

고정 패킷 언패킹 솔루션:

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, "#");
    }
}
로그인 후 복사

고정 패킷 언패킹 테스트:

여기서는 구체적인 솔루션만 보여줍니다. 위의 첫 번째 페이지에서 4바이트에 패킷 길이를 더한 값에 여전히 문제가 있기 때문에 문자열 분할을 수행합니다. 즉, 패킷 길이 없이 처음으로 패킷이 전송되면 이후의 고정 또는 포장 풀기 시뮬레이션이 버퍼에 유지됩니다. 다음 데모에서는 위의 코드를 참조할 수 있습니다.

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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:csdn.net
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿