
本文深入探讨了php websocket在高速传输数据时出现乱码的常见问题。核心原因在于客户端为提高效率,可能在一个tcp包中发送多个websocket帧,而服务器端的`unseal`函数未能正确解析和截断这些帧。文章详细分析了现有`unseal`函数的缺陷,并提供了一个优化后的递归解析方案,确保每个websocket帧都能被准确解码,从而解决乱码问题。
在构建基于WebSocket的实时通信应用时,开发者可能会遇到一个令人困惑的问题:当客户端以极高的频率发送数据时,服务器端接收到的消息却出现乱码。这通常发生在客户端为了网络传输效率,将多个WebSocket帧打包到一个单一的TCP数据包中发送时。
WebSocket协议在TCP之上运行,它定义了自己的数据帧格式。每个WebSocket消息都封装在一个或多个帧中,每个帧包含头部信息(如FIN位、操作码、掩码位和载荷长度指示器)以及实际的载荷数据。当客户端(例如,浏览器J*aScript)通过websocket.send()方法快速连续发送多条消息时,底层TCP/IP协议栈可能会将这些独立的WebSocket帧合并成一个更大的TCP数据包,一次性发送给服务器。
服务器端接收到这个包含多个WebSocket帧的TCP数据包后,需要一个机制来正确地识别和解析其中的每一个帧。如果服务器的帧解析逻辑未能考虑到一个TCP包中可能包含多个帧的情况,它就会尝试将整个TCP包的内容作为一个单一的WebSocket帧来处理,从而导致后续帧的头部信息被误认为是前一个帧的载荷数据,最终表现为乱码。
问题中提供的unseal函数是一个常见的WebSocket帧解码实现,但它存在一个关键缺陷,导致无法正确处理包含多帧的TCP数据包:
立即学习“PHP免费学习笔记(深入)”;
function unseal($socketData) {
$length = ord($socketData[1]) & 127; // 获取载荷长度指示器
if($length == 126) {
$masks = substr($socketData, 4, 4);
$data = substr($socketData, 8); // 从固定位置开始截取数据,直到字符串末尾
}
elseif($length == 127) {
$masks = substr($socketData, 10, 4);
$data = substr($socketData, 14); // 从固定位置开始截取数据,直到字符串末尾
}
else {
$masks = substr($socketData, 2, 4);
$data = substr($socketData, 6); // 从固定位置开始截取数据,直到字符串末尾
}
$socketData = ""; // 变量名易混淆,应为$unmaskedData
for ($i = 0; $i < strlen($data); ++$i) {
$socketData .= $data[$i] ^ $masks[$i%4];
}
return $socketData;
}该函数的缺陷在于:
Inworld.ai
InWorldAI是一个AI角色开发平台,开发者可以创建具有自然语言、上下文意识和多模态的AI角色,并可以继承到游戏和实时媒体中
178
查看详情
要解决此问题,关键在于精确地计算出当前帧的实际载荷长度,并根据这个长度准确地截取载荷数据。处理完一个帧后,如果原始$socketData中仍有剩余数据,则应将其视为下一个WebSocket帧的开始,并递归或迭代地进行解析。
为了正确解析包含多帧的TCP数据包,我们需要对unseal函数进行优化。核心思想是:精确计算当前帧的实际载荷长度,提取并解掩码当前帧,然后检查是否有剩余数据,并递归地处理剩余数据。
以下是优化后的unseal函数实现:
<?php
/**
* 解码WebSocket帧数据,支持单个TCP包中包含多个帧的情况。
*
* @param string $socketData 原始的WebSocket帧数据(可能包含多个帧)。
* @return array 返回一个包含所有已解码消息字符串的数组。
*/
function unseal(string $socketData): array
{
$messages = []; // 用于存储所有解码后的消息
$offset = 0; // 当前处理的帧在$socketData中的起始偏移量
// 循环处理,直到所有数据都被处理完毕
while ($offset < strlen($socketData)) {
// 确保至少有足够的字节来读取帧头的前两个字节
if ($offset + 1 >= strlen($socketData)) {
// 数据不完整,无法解析帧头,通常需要等待更多数据
error_log("WebSocket frame incomplete: Not enough data for initial bytes.");
break;
}
// 读取第二个字节,包含掩码位和载荷长度指示器
$byte1 = ord($socketData[$offset + 1]);
$isMasked = ($byte1 >> 7) & 0x1; // 掩码位
$payloadLengthIndicator = $byte1 & 0x7F; // 载荷长度指示器 (0-125, 126, 127)
$actualPayloadLength = 0; // 实际载荷长度
$maskingKeyStart = 0; // 掩码键的起始偏移量
$payloadDataStart = 0; // 载荷数据的起始偏移量
// 根据载荷长度指示器确定实际载荷长度和各部分的偏移量
if ($payloadLengthIndicator == 126) {
// 载荷长度由接下来的2个字节表示 (16位无符号整数)
if ($offset + 3 >= strlen($socketData)) {
error_log("WebSocket frame incomplete: Not enough data for 16-bit length.");
break;
}
$actualPayloadLength = unpack('n', substr($socketData, $offset + 2, 2))[1];
$maskingKeyStart = $offset + 4; // FIN/Opcode (1) + Mask/Length (1) + Extended Length (2) = 4
$payloadDataStart = $offset + 8; // 掩码键 (4) 之后
} elseif ($payloadLengthIndicator == 127) {
// 载荷长度由接下来的8个字节表示 (64位无符号整数)
// 注意:PHP的unpack 'J' (64位无符号) 在某些系统或PHP版本上可能存在兼容性问题
// 对于WebSockets,超过65535字节的消息通常不常见,如果需要处理超大消息,请确保环境支持或使用自定义逻辑
if ($offset + 9 >= strlen($socketData)) {
error_log("WebSocket frame incomplete: Not enough data
for 64-bit length.");
break;
}
// 假设 'J' 适用于64位无符号整数
$actualPayloadLength = unpack('J', substr($socketData, $offset + 2, 8))[1];
$maskingKeyStart = $offset + 10; // FIN/Opcode (1) + Mask/Length (1) + Extended Length (8) = 10
$payloadDataStart = $offset + 14; // 掩码键 (4) 之后
} else {
// 载荷长度直接由载荷长度指示器表示 (0-125)
$actualPayloadLength = $payloadLengthIndicator;
$maskingKeyStart = $offset + 2; // FIN/Opcode (1) + Mask/Length (1) = 2
$payloadDataStart = $offset + 6; // 掩码键 (4) 之后
}
// 验证掩码键和载荷数据是否完整
if (!$isMasked || ($maskingKeyStart + 4 > strlen($socketData)) || ($payloadDataStart + $actualPayloadLength > strlen($socketData))) {
error_log("WebSocket frame malformed or incomplete: Masking key or payload data missing. isMasked: " . (int)$isMasked . " actualPayloadLength: " . $actualPayloadLength . " dataLen: " . strlen($socketData));
break; // 帧不完整或格式错误,无法继续解析
}
// 提取掩码键和载荷数据
$masks = substr($socketData, $maskingKeyStart, 4);
$payload = substr($socketData, $payloadDataStart, $actualPayloadLength);
$unmaskedPayload = '';
// 解掩码
for ($i = 0; $i < $actualPayloadLength; ++$i) {
$unmaskedPayload .= $payload[$i] ^ $masks[$i % 4];
}
$messages[] = $unmaskedPayload; // 将解码后的消息添加到结果数组
// 更新偏移量,指向下一个可能的帧的起始位置
$offset = $payloadDataStart + $actualPayloadLength;
}
return $messages;
}代码解析与改进点:
以上就是PHP WebSocket高频数据传输乱码问题解析与解决方案的详细内容,更多请关注php中文网其它相关文章!
# php
# 第一个
# 丹东seo公司推荐10火星
# 原阳抖音营销推广
# 购物网站优化推荐怎么写
# 大渡口区网络seo优化
# 昆明网站建设网页制作
# 下沙app营销推广
# 寻乌网站推广公司
# 哪些网站可免费推广
# 齐齐哈尔网站优化地址
# 睢宁推广网站报价
# 迭代
# 是一个
# 客户端
# 包中
# 数据包
# 偏移量
# 掩码
# 多个
# 递归
# 常见问题
# 栈
# websocket
# 字节
# 浏览器
# java
# javascript
相关栏目:
【
Google疑问12 】
【
Facebook疑问10 】
【
优化推广96088 】
【
技术知识133117 】
【
IDC资讯59369 】
【
网络运营7196 】
【
IT资讯61894 】
相关推荐:
酷狗音乐多音轨设置教程
国际经济与贸易就业方向解析
Vue 3中独立响应式实例的创建与应用
荣耀盒子应用管理技巧
飞飞漫画漫画阅读官网_飞飞漫画漫画阅读官网进入阅读
解决C#跨线程访问XML对象的异常 安全的并发XML处理模式
申通快件单号查询平台 申通包裹物流动态跟踪
从HTML表单获取逗号分隔值并转换为NumPy数组进行预测
Mac怎么关闭按键声音_Mac键盘打字音效设置
鲁班大师乓乓皮肤获取方法
MacBook Pro词典使用指南
实现二叉树的层序插入:基于树大小的路径导航
搜狗浏览器如何查找页面中的文字 搜狗浏览器Ctrl+F页面搜索功能
漫蛙官网(首页入口)_漫蛙漫画稳定访问教程分享
招商淘客入门指南
多多买菜门店端app订单查看方法
mysql数据库索引类型有哪些_mysql索引类型解析
TikTok私信无法发送表情怎么办 TikTok消息表情发送修复方法
吃完饭就犯困是什么原因 餐后嗜睡如何缓解
Dash应用中自定义HTML页面标题与网站图标(F*icon)的实用指南
使用jQuery精确检测除指定元素外任意位置的点击事件
Word如何将文字快速转成表格 Word文本转换成表格功能使用技巧【效率】
猫眼电影app如何设置电影上映提醒_猫眼电影上映提醒设置教程
c++类和对象到底是什么_c++面向对象编程基础
c++20的指定初始化(Designated Initializers)怎么用_c++ C风格结构体初始化
《真我》申请退款方法
《广发易淘金》国债逆回购操作教程
构建可配置的J*aScript加权点击计数器与共享总计功能
微信注销后银行卡解绑了吗_微信注销后银行卡解绑状态
PHP安全加载非公开目录图片与动态内容类型处理指南
如何用mysql实现客户反馈管理_mysql客户反馈数据库方法
composer licenses 命令:如何检查项目依赖的许可证?
126手机126邮箱登录_126邮箱手机登录入口官网
解决Go encoding/json 将JSON大数字解析为浮点数的问题
使用VS Code作为你的个人知识管理系统
在Flask应用中安全高效地更新SQLAlchemy用户数据
谷歌浏览器官方镜像获取方法_谷歌浏览器网页版入口极速直达
mysql如何限制远程访问_mysql远程访问限制方法
macosmonterey系统外接显示器驱动怎么安装_macosmonterey外接显示器驱动与分辨率调整
向往的生活小游戏启动处_向往的生活小游戏立即启动
电脑开不了机怎么办 电脑无法开机的解决方法
使用逻辑应用(Logic Apps)自动处理邮件附件中的XML到Excel
漫蛙manwa漫画官网链接_漫蛙manwa最新可用网址推荐
J*aScript模块加载器_RequireJS原理分析
iPhone14开启Apple TV遥控设置
《单词速记宝》设置学习计划方法
sublime如何处理超大文件不卡顿 _sublime打开大日志文件技巧
第五人格PC版怎么避免被封号_第五人格PC版防封号注意事项
微信客户端怎么查看二维码_微信客户端个人二维码查看方法
英雄联盟争者留名活动介绍
2025-12-13
运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。