经常在网上看到很多类似于粘包的问题以及处理,但是因为这个跟自己本身的工作内容关系不大,所以也就没怎么了解。结果今天就被坑了一大把。
起因
最近由于有个投屏的软件需要模拟多个客户端进行连接投屏,所以用java写了个客户端模拟多个同时进行并发同屏的情况。而他的协议过程是这样子的。当你发送开始投屏的命令一样,接收端也就是我们所说的服务端会回复几条socket的信息(这个是重点),但是因为我们是模拟客户端的,所以并不是所有的数据都是我关心的。只有其中一条关于回复的端口的信息是需要接收处理。所以就有了坑的问题了。
问题
在测试的过程中发现,经常是有概率的出现模拟了4个客户端进行并发时服务端只回复了3条关于端口的数据,总会有概率的少了一两条。那问题总得定位到底是代码处理的问题,还是说是机器本身就没有收到对应的包呢。
以下是wireshark抓包的结果
实际证明是有收到这个回复包的, 那么就证明确实是代码的问题了。那代码问题在哪 我们看看出错的代码
byte srcArray[] = new byte[1024];
int count = socket.getInputStream().read(srcArray);
byte[] msgLenArray = new byte[4];
System.arraycopy(srcArray, 0, msgLenArray, 0, 4);
int msgLen = (MsgUtils.bytesToInt2(msgLenArray));
...
if (temp[9] == 1) {
switch (temp[10]) {
case 101:
Msg.CMsgConnectResp cMsgConnectResp = Msg.CMsgConnectResp.parseFrom(msg);
log.append("收到指令类型:" + (temp[9]) + ", 指令号:" + temp[10] + "连接结果:" + cMsgConnectResp.getResultType() + ", 接收端版本:" + cMsgConnectResp.getReceiverVersion() + ", 支持最大窗口数量:" + cMsgConnectResp.getMaxWindowCount() + "\n");
...
代码实际很简单就是声明一个byte的数组,每次读1024个字节。再来将读取到的数据进行解析,表面上看起来没什么问题。
可是细想一个问题。就是刚才我们提到的,当我们进行投屏的时候,服务端是会连续回复几个socket的数据的,然而我们一下子读了那么1024个字节的数据,就会把几个数据包都读到一起,那如果幸运的话,回复端口的数据先到了,嗯 没问题,那如果是其他数据类型先到了,会发生什么事情呢。。很悲剧。数据包会被丢弃掉。
解决
既然遇到这个问题,就肯定有解决的方法。下来看看解决的方式
byte srcArray[] = new byte[11];
socket.getInputStream().read(srcArray);
byte[] msgLenArray = new byte[4];
System.arraycopy(srcArray, 0, msgLenArray, 0, 4);
int msgLen = (MsgUtils.bytesToInt2(msgLenArray));
if (msgLen > 0) {
byte contentByte[] = new byte[msgLen];
socket.getInputStream().read(contentByte);
System.out.println(len[9]);
System.out.println(len[10]);
if (len[9] == 1) {
}
以上就能够完美的解决问题了,我们每次读取都读11个字节。因为协议头的长度就是11个字节。再来根据协议头里面的body的长度我们再去读对应的长度。这样子就避免了读取的时候盲目的读取1024个长度的问题了。