在iOS中用socket做即時(shí)通訊時(shí)蛇券,經(jīng)常會(huì)遇到粘包的問題缀壤。
什么是粘包?
粘包:socket傳輸數(shù)據(jù)是由多個(gè)連續(xù)的數(shù)據(jù)包組成纠亚,他們被連續(xù)的存儲(chǔ)在緩存中塘慕,在讀取數(shù)據(jù)包時(shí)可能由于某些原因?qū)е芦@取到了錯(cuò)誤的發(fā)送邊界,這時(shí)后就會(huì)出現(xiàn)后一個(gè)數(shù)據(jù)包的頭連接在了前一個(gè)數(shù)據(jù)包的尾部蒂胞。
怎么解決粘包图呢?
因?yàn)門CP協(xié)議是基于字節(jié)流的,所以在應(yīng)用層就要自己解決數(shù)據(jù)的邊界問題骗随。
一般來說岳瞭,定義數(shù)據(jù)有兩種方式,定義長度 或者 定義一個(gè)終結(jié)符蚊锹。
下面通過項(xiàng)目中的代碼來解釋怎么解決粘包瞳筏。
func didReadData(_ data:Data){
//接收的數(shù)據(jù)先寫入緩存
cache += [UInt8](data)
while cache.count>8 {
print("讀取到數(shù)據(jù),開始解析牡昆,剩余數(shù)據(jù)長度:\(cache.count)")
//0-4位存儲(chǔ)消息類型
let typeBytes = cache[0..<4]
//4-8位存儲(chǔ)數(shù)據(jù)包長度
let lengthBytes = cache[4..<8]
let typeData = Data(typeBytes)
let lengthData = Data(lengthBytes)
//注意大小端轉(zhuǎn)換問題
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 })
//數(shù)據(jù)包長度不夠姚炕,跳出循環(huán)摊欠,繼續(xù)讀取笑一個(gè)包
if cache.count < 8+Int(length){
break
}
//獲取到完整的數(shù)據(jù)包
let resultBytes = cache[8..<8+Int(length)]
let resultData = Data(resultBytes)
//將數(shù)據(jù)傳給delegate進(jìn)行處理
self.delegate?.socketDidRead(type: type, data: resultData,retry:true)
//沾包循環(huán)讀取
let begin = 8+Int(length)
let end = cache.count
cache = Array(cache[begin..<end])
}
sendSocket?.readData(withTimeout: -1, tag: 0)
}
主要流程如下
1、將接收的數(shù)據(jù)保存到緩存數(shù)據(jù)中柱宦。
2些椒、while循環(huán)去讀數(shù)據(jù),直到判斷緩存是否小于8掸刊。(0-4字節(jié)為消息類型免糕,4-8為消息長度)
3、循環(huán)中去判斷緩存數(shù)據(jù)是否大于內(nèi)容的長度忧侧,如果大于石窑,則解析掉這條數(shù)據(jù),并從緩存中刪除蚓炬,如果緩存數(shù)據(jù)還有大于8字節(jié)長度的數(shù)據(jù)松逊,則循環(huán)讀取數(shù)據(jù)。
如果數(shù)據(jù)長度小于獲取到的內(nèi)容長度肯夏,則說明出現(xiàn)粘包经宏,則繼續(xù)readData,繼續(xù)拼接緩存數(shù)據(jù)驯击。