您好,登錄后才能下訂單哦!
這篇“workerman怎么自定義協議解決粘包拆包問題”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“workerman怎么自定義協議解決粘包拆包問題”文章吧。
關于網游的通信數據包格式的約定,我在網上也看過一些。如果不是用弱類型語言做服務端腳本,其實別人常用的是字節數組。但是 PHP 在接收到字節數組時,其實就是字符串,但前提時該字節數組沒有一些特定轉換的。就拿 C# 來說,在解決粘包等問題會在字節數組前加入字節長度 (BitConverter.GetBytes (len))。但是這個傳遞到 PHP 服務端接收時,字符串前 4 個字節就是顯示不出來,用過很多方法進行轉換都取不出來。 后來也想過用 Protobuf 數據方式,雖然 PHP 可以對數據可以轉換,但是客戶端 C# 我還不太熟就放棄了。
還一個問題是,其實別人做網游服務端實現幀同步大部分都是 UDP 協議,同時也有 TCP 和 UDP 共用。但是如果只是小型多人在線游戲,用 PHP 做服務端,TCP 協議通信也完全可以的。接下來就回到 workerman 的自定義協議和粘包拆包問題吧。
workerman 對 PHP 的幾個 socket 函數進行了封裝 (關于 socket 函數,如果愿意折騰,php 也可以寫一個文件傳輸的小工具的),基于 TCP 之上也自帶了幾個應用層協議,比如 Http, Websocket, Frame 等。也預留了用戶自行定義協議的路口,只需要實現他的 ProtocolInterface 接口,以下就簡單介紹以下接口需要實現的幾個方法。
1. Input 方法
在這個方法里,可以在服務端接收前對數據包進行解包,檢查包長度,過濾等。返回 0 就將數據包放入接收端的緩沖內繼續等待,返回指定長度則表示取出緩沖區內長度。如果異常也可以返回 false 直接關閉該客戶端連接。
2. encode 方法
該方法是服務端在發送數據包到客戶端前,對數據包格式的處理,也就是封包,這個就要前后端約定好了。
3. decode 方法
這個方法也就是解包,就是從緩沖區里取出指定長度到 onMessage 接收前要進行處理的地方,比如進行邏輯調配等等。
由于 TCP 是基于流的,且因為是傳輸層,在上層的應用通過 socket 套接字 (理解為接口) 通信時,他不知道傳遞過來的數據包開頭結尾在哪。只是根據 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__ . '/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 "游戲協議服務啟動……";
};
// 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怎么自定義協議解決粘包拆包問題”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。