溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

workerman怎么自定義協(xié)議解決粘包拆包問(wèn)題

發(fā)布時(shí)間:2022-12-13 09:32:27 來(lái)源:億速云 閱讀:121 作者:iii 欄目:編程語(yǔ)言

這篇“workerman怎么自定義協(xié)議解決粘包拆包問(wèn)題”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“workerman怎么自定義協(xié)議解決粘包拆包問(wèn)題”文章吧。

問(wèn)題解答:

關(guān)于網(wǎng)游的通信數(shù)據(jù)包格式的約定,我在網(wǎng)上也看過(guò)一些。如果不是用弱類型語(yǔ)言做服務(wù)端腳本,其實(shí)別人常用的是字節(jié)數(shù)組。但是 PHP 在接收到字節(jié)數(shù)組時(shí),其實(shí)就是字符串,但前提時(shí)該字節(jié)數(shù)組沒有一些特定轉(zhuǎn)換的。就拿 C# 來(lái)說(shuō),在解決粘包等問(wèn)題會(huì)在字節(jié)數(shù)組前加入字節(jié)長(zhǎng)度 (BitConverter.GetBytes (len))。但是這個(gè)傳遞到 PHP 服務(wù)端接收時(shí),字符串前 4 個(gè)字節(jié)就是顯示不出來(lái),用過(guò)很多方法進(jìn)行轉(zhuǎn)換都取不出來(lái)。 后來(lái)也想過(guò)用 Protobuf 數(shù)據(jù)方式,雖然 PHP 可以對(duì)數(shù)據(jù)可以轉(zhuǎn)換,但是客戶端 C# 我還不太熟就放棄了。

還一個(gè)問(wèn)題是,其實(shí)別人做網(wǎng)游服務(wù)端實(shí)現(xiàn)幀同步大部分都是 UDP 協(xié)議,同時(shí)也有 TCP 和 UDP 共用。但是如果只是小型多人在線游戲,用 PHP 做服務(wù)端,TCP 協(xié)議通信也完全可以的。接下來(lái)就回到 workerman 的自定義協(xié)議和粘包拆包問(wèn)題吧。

自定義協(xié)議:

workerman 對(duì) PHP 的幾個(gè) socket 函數(shù)進(jìn)行了封裝 (關(guān)于 socket 函數(shù),如果愿意折騰,php 也可以寫一個(gè)文件傳輸?shù)男」ぞ叩?,基于 TCP 之上也自帶了幾個(gè)應(yīng)用層協(xié)議,比如 Http, Websocket, Frame 等。也預(yù)留了用戶自行定義協(xié)議的路口,只需要實(shí)現(xiàn)他的 ProtocolInterface 接口,以下就簡(jiǎn)單介紹以下接口需要實(shí)現(xiàn)的幾個(gè)方法。

1.  Input 方法

在這個(gè)方法里,可以在服務(wù)端接收前對(duì)數(shù)據(jù)包進(jìn)行解包,檢查包長(zhǎng)度,過(guò)濾等。返回 0 就將數(shù)據(jù)包放入接收端的緩沖內(nèi)繼續(xù)等待,返回指定長(zhǎng)度則表示取出緩沖區(qū)內(nèi)長(zhǎng)度。如果異常也可以返回 false 直接關(guān)閉該客戶端連接。

2. encode 方法

該方法是服務(wù)端在發(fā)送數(shù)據(jù)包到客戶端前,對(duì)數(shù)據(jù)包格式的處理,也就是封包,這個(gè)就要前后端約定好了。

3. decode 方法

這個(gè)方法也就是解包,就是從緩沖區(qū)里取出指定長(zhǎng)度到 onMessage 接收前要進(jìn)行處理的地方,比如進(jìn)行邏輯調(diào)配等等。

粘包拆包產(chǎn)生現(xiàn)象:

由于 TCP 是基于流的,且因?yàn)槭莻鬏攲?,在上層的?yīng)用通過(guò) socket 套接字 (理解為接口) 通信時(shí),他不知道傳遞過(guò)來(lái)的數(shù)據(jù)包開頭結(jié)尾在哪。只是根據(jù) TCP 的一套擁塞算法機(jī)型粘合或拆解的發(fā)送。所以從字面上看,粘包就是幾個(gè)數(shù)據(jù)包一起發(fā)送,原本應(yīng)該是兩個(gè)包,客戶端只收到了一個(gè)包。而拆包是將一個(gè)數(shù)據(jù)包拆成了幾個(gè)包,本應(yīng)該是接收一個(gè)數(shù)據(jù)包,卻只收到了一個(gè)。所以如果不解決這個(gè),前面提到了按約定字符串傳輸,就可能解包時(shí)報(bào)錯(cuò)的情況。

粘包拆包解決方法:

1. 首部加數(shù)據(jù)包長(zhǎng)度

<?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)
    {
        // 數(shù)據(jù)包前4個(gè)字節(jié)
        $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)
    {
        // 對(duì)數(shù)據(jù)包長(zhǎng)度向左補(bǔ)零
        $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;
        }
        
        // 返回當(dāng)前包長(zhǎng)
        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, "#");
    }
}

粘包拆包測(cè)試:

這里就只演示特定字符串分割的解決方法,因?yàn)樯厦媸醉?yè) 4 字節(jié)加包長(zhǎng)的還是存在問(wèn)題。就是第一次發(fā)送不帶包長(zhǎng),后面模擬粘包還是拆包都會(huì)停留在緩沖區(qū),下面演示可以參照上面代碼查看。

1. 服務(wù)開啟和客戶端連接

2. 服務(wù)業(yè)務(wù)端代碼

數(shù)據(jù)包格式說(shuō)明一下,字符串以逗號(hào)分割,數(shù)據(jù)包以 #分割,逗號(hào)分割第一組是業(yè)務(wù)方法,如 Login 表示登陸傳遞,Pos 表示坐標(biāo)傳遞,后面帶的就是對(duì)應(yīng)方法需要的參數(shù)了。

<?php

use Workerman\Worker;

require_once __DIR__ . '/vendor/autoload.php';

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

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

$worker->onWorkerStart = function ($connection) {
    echo "游戲協(xié)議服務(wù)啟動(dòng)……";
};

// 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 "接收的數(shù)據(jù):" . $data . "\n";

    // 簡(jiǎn)單實(shí)現(xiàn)接口分發(fā)
    $arr = explode(",", $data);

    if (!is_array($arr) || !count($arr)) {
        $connection->close("數(shù)據(jù)格式錯(cuò)誤", 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";
};


// 接收緩沖區(qū)溢出回調(diào)
$worker->onBufferFull = function ($connection) {
    echo "清理緩沖區(qū)吧";
};

Worker::runAll();

?>

3. 粘包測(cè)試

只需要在客戶端模擬兩個(gè)數(shù)據(jù)包連在一起,但是要以 #分隔,看看服務(wù)端接收的時(shí)候是一幾個(gè)包進(jìn)行處理的。

4. 拆包測(cè)試

拆包模擬只需要將一個(gè)數(shù)據(jù)包分成兩次發(fā)送,看看服務(wù)端接收的時(shí)候能不能顯示或者說(shuō)能不能按約定好的格式正確顯示。

以上就是關(guān)于“workerman怎么自定義協(xié)議解決粘包拆包問(wèn)題”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI