Socket TCP一种合包思路的分享

250 阅读3分钟

「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战

Socket TCP一种合包思路的分享

上篇文章讲过TCP是流的形式,并不是发送什么就能收到什么,而是要在应用层去做逻辑处理,才能接收到正确的数据。

合包的解决方法

回顾一下合包的解决方法。

  1. 发送时数据末尾拼接换行符,使用readLine方法接收(只能接收字符串)

  2. 约定分割符,每个字节进行判断(效率低)

  3. 使用webSocket基于包协议的技术(局限)

  4. 约定每一包的长度,每一包前加上包的长度,比如[5][12345]这样的结构

这篇文章是为了详细讲解方法四的思路。

可变数组长度

在讲解之前先了解可变数组长度怎么实现,字节数据不同于字符串,没有很方便的StringBuffer、StringBuilder方法,那么怎么实现可变长度的数组,方便起见,可以直接使用ByteArrayOutputStream

ByteArrayOutputStream如何创建使用,有两种方法可以创建它。

  • 不指定大小的方式,默认开辟空间是32字节

OutputStream bOut = new ByteArrayOutputStream();

  • 指定大小的方式,比如开辟大小为a的空间

OutputStream bOut = new ByteArrayOutputStream(int a)

它的大小空间是可以变的,不像新创建字节数组时,必须要指定长度。在添加字节数组时,使用write方法就可以写入了,转成字节数组时,再使用toByteArray方法。

思路分析

在发送端,我们给每包发送的数据增加一个整形的长度。比如

原来字节数组是: [1,2,3]

加上整形长度后是: 整型3转字节数组(4位) 拼接上 [1,2,3]

由于TCP是流,在大量数据传输时,并不能一次性的接收全。在之前的文章中讲过,不再细说。

在接收端,就需要每次判断内容是否接收全,提取真实包的长度,进而提取出真实的数据包。

笔者用递归写了一个简单的处理,含义是,先将接收的数据包保存到可变数组中,然后判断是否超过4个字节,超过4个字节就提取约定的包的长度,没有超过就继续进行接收。

可变数组的长度 > 约定的包长度+4 时,开始提取真实的包数据。

看代码吧,以后看了开源框架之后,再优化这部分代码。

注意在每次链接成功时,重置 可变数组和包长度的变量。


/*可变数组*/

ByteArrayOutputStream stream = new ByteArrayOutputStream();

/*包长度的约定*/

int frameLen = 0;

private synchronized void handleReceiveTcpByte(byte[] buffer) throws IOException {

stream.write(buffer);

deal();

}

private void deal() throws IOException {

if (frameLen == 0) {

if (stream.size() > 4) {

// 取出实际包的长度

byte[] tempArray = stream.toByteArray();

byte[] intLengthArray = new byte[4];

System.arraycopy(tempArray, 0, intLengthArray, 0, 4);

frameLen = ByteUtils.bytes2IntLittle(intLengthArray);

// 进行递归

deal();

} else {

// 无需操作,等待下一次的包

}

} else {

if (stream.size() >= frameLen + 4) {

byte[] tempArray = stream.toByteArray();

byte[] framePacket = new byte[frameLen];

System.arraycopy(tempArray, 4, framePacket, 0, frameLen);

if (mTcpSocketCallback != null)

mTcpSocketCallback.onReceived(framePacket);

// 取出之后,剩下的空间继续放入 stream

byte[] remainPacket = new byte[stream.size() - frameLen + 4];

System.arraycopy(tempArray,frameLen + 4 , remainPacket, 0, stream.size() - frameLen + 4);

stream.reset();

stream.write(remainPacket);

} else {

// 无需操作,等待下一次的包

}

}

}