在iOS中用socket做即时通讯时,经常会遇到粘包的问题。
什么是粘包?
粘包:socket传输数据是由多个连续的数据包组成,他们被连续的存储在缓存中,在读取数据包时可能由于某些原因导致获取到了错误的发送边界,这时后就会出现后一个数据包的头连接在了前一个数据包的尾部。
怎么解决粘包?
因为TCP协议是基于字节流的,所以在应用层就要自己解决数据的边界问题。
一般来说,定义数据有两种方式,定义长度 或者 定义一个终结符。
下面通过项目中的代码来解释怎么解决粘包。
func didReadData(_ data:Data){
//接收的数据先写入缓存
cache += [UInt8](data)
while cache.count>8 {
print("读取到数据,开始解析,剩余数据长度:\(cache.count)")
//0-4位存储消息类型
let typeBytes = cache[0..<4]
//4-8位存储数据包长度
let lengthBytes = cache[4..<8]
let typeData = Data(typeBytes)
let lengthData = Data(lengthBytes)
//注意大小端转换问题
let type = Int32(bigEndian: typeData.withUnsafeBytes { $0.baseAddress!.bindMemory(to: Int32.self, capacity: 4).pointee })
let length = Int32(bigEndian: lengthData.withUnsafeBytes { $0.baseAddress!.bindMemory(to: Int32.self, capacity: 4).pointee })
//数据包长度不够,跳出循环,继续读取笑一个包
if cache.count < 8+Int(length){
break
}
//获取到完整的数据包
let resultBytes = cache[8..<8+Int(length)]
let resultData = Data(resultBytes)
//将数据传给delegate进行处理
self.delegate?.socketDidRead(type: type, data: resultData,retry:true)
//沾包循环读取
let begin = 8+Int(length)
let end = cache.count
cache = Array(cache[begin..<end])
}
sendSocket?.readData(withTimeout: -1, tag: 0)
}
主要流程如下
1、将接收的数据保存到缓存数据中。
2、while循环去读数据,直到判断缓存是否小于8。(0-4字节为消息类型,4-8为消息长度)
3、循环中去判断缓存数据是否大于内容的长度,如果大于,则解析掉这条数据,并从缓存中删除,如果缓存数据还有大于8字节长度的数据,则循环读取数据。
如果数据长度小于获取到的内容长度,则说明出现粘包,则继续readData,继续拼接缓存数据。
网友评论