安卓拆包导致问题
1.socket读取到ByteArray前面部分正确,后面剩余部分全部为0;
2.由于socket读取长度不正确,进而出现连锁反应,导致后面获取到的包头不正确,于是出现以下闪退信息。
java.lang.OutOfMemoryError: Failed to allocate a 2021226312 byte
allocation with 9337485 free bytes and 183MB until OOM,
target footprint 18674973, growth limit 201326592
分析
socket服务器协议大致如下:
- 1.固定包头长度11byte,其中包含包体长度信息;
- 2.根据包头信息读取包体。
于是socket读取消息代码如下:
val inputStream = clientSocket!!.getInputStream()
val message = HlllSocketMessage() //自定义消息实体
var buffer = ByteArray(headerLength) //读取二进制数据
var len = -1
while (clientSocket!!.isConnected && inputStream.read(buffer).also { len = it } != -1) {
if (len == headerLength) {
message.parseHeader(buffer) //解析包头信息
buffer = ByteArray(message.bodyBytesLength)
} else {
message.parseBodyData(buffer) //解析包体信息
buffer = ByteArray(headerLength)
onReceivedMessage(message)
}
}
这么写,在一般情况下没什么问题,但是在高并发时就出现上边所说的问题。
你会发现实际上读取到的长度len和所需要读取的长度message.bodyBytesLength不一致!
原因是inputStream读取长度有限制,所以自动拆包了!
inputStream.available()方法能够获取到剩余可用长度,但是inputStream.read(buffer)也能够获取到当前实际获取到的信息长度,用后者即可。
解决方案
val inputStream = clientSocket!!.getInputStream();
val message = HlllSocketMessage();
var buffer = ByteArray(headerLength);
var readLen = headerLength; //实际读取长度
var len = headerLength; //需要读取的长度
var leftLen = headerLength; //剩余未读长度
while (clientSocket!!.isConnected && readLen != -1) {
synchronized(buffer) Runnable@ {
val offset = len - leftLen;
readLen = inputStream.read(buffer, offset, leftLen); //读取长度
if (readLen == -1) {
return@Runnable;
}
if (readLen < leftLen) { //处理拆包
leftLen = leftLen - readLen;
} else {
leftLen = 0;
}
if (leftLen == 0) { //需要读取长度跟实际长度一致才处理
if (len == headerLength) { //包头
message.parseHeader(buffer);
len = message.bodyBytesLength;
leftLen = len;
buffer = ByteArray(len);
} else { //包体
message.parseBodyData(buffer);
len = headerLength;
leftLen = len;
buffer = ByteArray(len);
onReceivedMessage(message);
}
}
}
}