在iOS中用socket做即時通訊時,經(jīng)常會遇到粘包的問題。
什么是粘包?
粘包:socket傳輸數(shù)據(jù)是由多個連續(xù)的數(shù)據(jù)包組成,他們被連續(xù)的存儲在緩存中,在讀取數(shù)據(jù)包時可能由于某些原因?qū)е芦@取到了錯誤的發(fā)送邊界,這時后就會出現(xiàn)后一個數(shù)據(jù)包的頭連接在了前一個數(shù)據(jù)包的尾部。
怎么解決粘包?
因為TCP協(xié)議是基于字節(jié)流的,所以在應(yīng)用層就要自己解決數(shù)據(jù)的邊界問題。
一般來說,定義數(shù)據(jù)有兩種方式,定義長度 或者 定義一個終結(jié)符。
下面通過項目中的代碼來解釋怎么解決粘包。
func didReadData(_ data:Data){
//接收的數(shù)據(jù)先寫入緩存
cache += [UInt8](data)
while cache.count>8 {
print("讀取到數(shù)據(jù),開始解析,剩余數(shù)據(jù)長度:\(cache.count)")
//0-4位存儲消息類型
let typeBytes = cache[0..<4]
//4-8位存儲數(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ù)讀取笑一個包
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ù)。