0

0

分享workerman自定义协议解决粘包拆包问题的方法

青灯夜游

青灯夜游

发布时间:2022-12-12 20:09:51

|

1940人浏览过

|

来源于csdn

转载

workerman自定义的协议如何解决粘包拆包?下面本篇文章给大家介绍一下workerman自定义协议解决粘包拆包问题的方法,希望对大家有所帮助。

分享workerman自定义协议解决粘包拆包问题的方法

前言:

        由于最近在使用 workerman 实现 Unity3D 联机游戏的服务端,虽然也可以通过 TCP 协议直接通信,但是在实际测试的过程中发现了一些小问题。【相关推荐:《workerman教程》】

        比如双方的数据包都是字符串的方式吗,还有就因为是字符串就需要切割,而有时候在客户端或服务端接收时都会出现报错。经过打印日志发现,两端接收到的包都有出现不是事先约定好的格式,这也就是 TCP 的粘包拆包现象。这个的解决方法很简单,网上也有很多,但是这里是想用自己实现的协议解决,暂且放到后面来说。

问题解答:

        关于网游的通信数据包格式的约定,我在网上也看过一些。如果不是用弱类型语言做服务端脚本,其实别人常用的是字节数组。但是 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 接收前要进行处理的地方,比如进行逻辑调配等等。

Unscreen
Unscreen

AI智能视频背景移除工具

下载

粘包拆包产生现象:

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

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

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

16

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

23

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

75

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

95

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

218

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

420

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

168

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

222

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

33

2026.03.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号