Socket编程中,基于TCP协议的通信有时候会发生粘包问题,原因大家自行百度,已经搜到这种问题应该了解粘包产生的原因哈
TCP协议是可靠的字节流式协议,字节流可以理解为是水流,数据在网络中像水流一样传输,所以纯粹发送字符串一旦TCP底层发生粘包情况,数据表意将产生错误,比如你想发送,我今天发现一特有意思的事
这句话TCP可能给你拆成我今
,天发
,现一特有意
,思的
,事儿
,只是举个简单例子哈,
我们怎么处理这种问题呢,有多种方案
- 一个是根据
EOF
拆分,这个EOF
就是自定义的一个特殊符号,比如我们现在就约定\r\n
为EOF
,碰到这个我就认为你当前发送的消息结束掉了,这个是没问题的哈,只不过有的应用场景,比如我们聊QQ跟人的时候,想一次性打好多话给对面,然后换下行,比如下图这种情况,这种情况让根据换行截断就不合适了吧,因为毕竟是一个消息,如果你多个换行,本来一句话,根据\r\n
的约定,会被切成N句话,所以这个要看应用场景
- 第二种就是我们约定一个数据格式,比如我们自定个规矩(逼格说的高一点叫协议),我们的协议规定,消息一定要有个包头+包体,包头(两个字节)存放消息长度,包体存放字符串的消息,好了,我们现在建立Socket通信以后把我们的数据传递给对端,对端按照我们约定的协议,先把头两个字节给摘出来,看看消息长度有多少,然后按长度向后截取,这样就是一个完整的消息包了,即使我们发送的包几十个都粘在一起,对端依旧可以按照我们的约定把数据包解出来,下面进行代码示范
<?php
// 这里我引用了 symfony/var-dumper 的一个包哈,写Laravel写惯了,里面的dump函数和dd函数特别好用
// 不想引用的同学下面的dump函数自行替换成var_dump哈
require_once __DIR__ . '/vendor/autoload.php';
$user = "Vencenty";
$time = "2020-09-30 21:40:00";
$userMessageLength = strlen($user);
$timeLength = strlen($time);
$userMessage = pack('n', $userMessageLength) . $user;
$timeMessage = pack('n', $timeLength) . $time;
// 假装目前发生了粘包情况,Socket另一端收到的消息是这样似的
$mixedMessage = $userMessage . $timeMessage;
// ---我们协议约定消息包分为包头+包体,包头两字节保存消息长度,包体就是具体消息内容,下面我们依照我们的协议进行拆包
$binaryPackageLength = substr($mixedMessage, 0, 2);
// 获取包长度
$packageLength = unpack('n', $binaryPackageLength);
dump($packageLength); // 我们发现他是一个数组,下标从1开始,那么我们直接获取
$packageLength = $packageLength[1]; // 直接获取包长度
$message = substr($mixedMessage, 2, $packageLength);
// 好,我们目前拆出一条消息
dump($message); // 结果是 "Vencenty"
// 拆第二条消息同理,我们要获取第二条消息的包长,应该是 2(二字节的包长) + 包体长度
$start = 2 + $packageLength;
// 第二个消息包的长度
$secondBinaryPackageLength = substr($mixedMessage, $start, 2);
// 第二个包的包头,我们把内容解出来
$secondPackageLength = unpack('n', $secondBinaryPackageLength);
dump($secondPackageLength); // 19个字节
$secondPackageLength = $secondPackageLength[1];
// 成功截取出字符串来
$secondMessage = substr($mixedMessage, 2 + $start, $secondPackageLength);
dump($secondMessage); // 2020-09-30 21:40:00
当然上面只是一个简单地代码示例哈,其实还存在问题,比如外人万一知道了你的协议,或者猜到了你的协议,他伪造数据包对你进行攻击,包头里面携带的数据长度是100,然后包体里面的字符串消息有1000,这样你拿到消息解码也会出现解析错误的问题,还有一个情况是合包的情况,TCP发送的消息过来以后,发现本来包长100,结果你只收到80个字符串,那么你需要把数据缓存下来,然后把后面发来的数据合并,这个做到具体业务的时候请大家根据自身业务进行处理
好了,最近一直在加班没空写东西,有错误希望大家指正