前言
服務(wù)器傳輸協(xié)議協(xié)議:
基于netty LengthFieldBasedFrameDecoder(100000000,0,4,0,4)
總長度 = 4byte + 包體內(nèi)容
接收二進制的json字符串
正文
思路:
1.在發(fā)送數(shù)據(jù)包前, 拼接4個字節(jié)帶有包體內(nèi)容長度的數(shù)據(jù).
2.在接受數(shù)時, 需要考慮 粘包和半包的情況.根據(jù)包頭長度,判斷包
的完整性.將完整的包解析出去,不完整的等待下一次的數(shù)據(jù)拼接.
設(shè)計
1.采用面向協(xié)議的方式編碼與解碼
2.自定義編碼器和解碼器, 遵守協(xié)議, 實現(xiàn)協(xié)議中的方法
3.基于GCDAsyncSocket, 封裝需要用到的方法
文件結(jié)構(gòu)
52D4F31C-68DA-4E9E-A667-70EC29032BF0.png
分塊講解
協(xié)議 - LCSocketCoderProtocol
1.編碼協(xié)議:LCSocketEncoderProtocol
2.解碼協(xié)議:LCSocketDecoderProtocol
3.編碼完成后的輸出協(xié)議:LCSocketEncoderOutputProtocol
4.解碼完成后的輸出協(xié)議:LCSocketDecoderOutputProtocol
編碼器 - LCSocketEncoder
1. 遵守編碼協(xié)議
2.實現(xiàn)協(xié)議中的方法:
- (void)encode:(id)object output (id<LCSocketEncoderOutputProtocol>)output
其中, 輸出output遵守 編碼輸出協(xié)議
編碼器 - LCSocketDecoder
1. 遵守解碼協(xié)議
2.實現(xiàn)協(xié)議中的方法:
- (void)decode:(id)object output:(id<LCSocketDecoderOutputProtocol>)output
其中, 輸出output遵守 解碼輸出協(xié)議
套接字 - LCBaseSocket
1. 基于GCDAsyncSocket
2.單例化, 提供需要給外界訪問的接口,如:"重連", "斷開連接". "是否正在連接"等
3.設(shè)置代理屬性
編碼器代碼詳解 - LCSocketEncoder
1.判斷數(shù)據(jù)格式是否可解析為json
if (![NSJSONSerialization isValidJSONObject:object]) {
[output didEndEncode:nil error:[NSError errorWithDomain:@"數(shù)據(jù)不能解析為json" code:-1 userInfo:nil]];
return;
}
2.將json數(shù)據(jù)編碼為NSData
NSError *error = nil;
NSData *contentData = [NSJSONSerialization dataWithJSONObject:object options:NSJSONWritingPrettyPrinted error:&error];
NSString *contentStr = [[NSString alloc] initWithData:contentData encoding:NSUTF8StringEncoding];
contentData = [contentStr dataUsingEncoding:NSUTF8StringEncoding];
3.判斷編碼后的數(shù)據(jù)長度
if (contentData.length > 1000000 - countOfLengthByte) {
[output didEndEncode:nil error:[NSError errorWithDomain:@"encoder的數(shù)據(jù)太長" code:-1 userInfo:nil]];
return;
}
4.將數(shù)據(jù)長度, 拼接到包頭
NSUInteger contentDataLength = contentData.length;
NSData *headData = [self dataForLength:contentDataLength byteCount:countOfLengthByte reverse:NO];
NSMutableData *data = [NSMutableData data];
[data appendData:headData];
[data appendData:contentData];
5.分發(fā)數(shù)據(jù)
[output didEndEncode:data error:nil];
解碼器代碼詳解 - LCSocketDecoder
1.判斷數(shù)據(jù)是否為NSData類型
if (![object isKindOfClass:[NSData class]]) {
[output didEndDecode:nil error:[NSError errorWithDomain:@"當前數(shù)據(jù)類型非NSData" code:-1 userInfo:nil]];
return;
}
2.初始化, 并截取包頭數(shù)據(jù)
self.needAppend = NO;
NSData *packetData = object;
[self.tempData appendData:packetData];
packetData = [self.tempData copy];
NSData *headerData = [packetData subdataWithRange:NSMakeRange(0, countOfLengthByte)];
NSUInteger contentLength = [self lengthForData:headerData reverse:YES];
3.判斷是否粘包
while (packetData.length >= contentLength) { }
3.1 判斷包的長度
if (contentLength > 1000000 - countOfLengthByte) { // 服務(wù)器定的...
[output didEndDecode:nil error:[NSError errorWithDomain:@"數(shù)據(jù)太長" code:-1 userInfo:nil]];
return;
}
3.2截取包體內(nèi)容
NSData *contentData = [packetData subdataWithRange:NSMakeRange(countOfLengthByte, contentLength)];
3.3 輸出一個完整的包, 截取剩余的包
[output didEndDecode:contentData error:nil];
packetData = [packetData subdataWithRange:NSMakeRange(contentLength + countOfLengthByte , packetData.length - contentLength - countOfLengthByte)];
3.4 判斷包頭長度, 如果小于規(guī)定的, 則跳出循環(huán), 等待拼接
if (packetData.length < countOfLengthByte) {
if (self.tempData.length != 0) {
[self.tempData resetBytesInRange:NSMakeRange(0, self.tempData.length)];
[self.tempData setLength:0];
self.needAppend = YES;
}
break;
}
3.5 截取包頭的數(shù)據(jù),計算長度
headerData = [packetData subdataWithRange:NSMakeRange(0, countOfLengthByte)];
contentLength = [self lengthForData:headerData reverse:YES];
3.6.只要執(zhí)行了while, 將tempData的數(shù)據(jù)清空, 拼接新的數(shù)據(jù)
if (self.tempData.length != 0) {
[self.tempData resetBytesInRange:NSMakeRange(0, self.tempData.length)];
[self.tempData setLength:0];
}
self.needAppend = YES;
一些小細節(jié)
1.計算根據(jù)NSData包頭的長度- lengthForData
掃盲
NSData 中的length為NSUInteger類型
接下來我們看看 NSUInteger為何物
#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
目前一般都為64位操作系統(tǒng), 所以 NSUInteger 就是 unsigned long 占了8bits
1字節(jié)(byte) = 8bits,兩者換算是1:8的關(guān)系篮绰。
略帶:一個漢字占了兩個字節(jié)
- (NSUInteger)lengthForData:(NSData *)data
{
NSUInteger dataLen = data.length;
NSUInteger length = 0;
int offset = 0;
while (offset < dataLen) {
NSUInteger tempVal = 0;
[data getBytes:&tempVal range:NSMakeRange(offset, 1)];
length += (tempVal << (8 * offset));
offset++;
}
return length;
}
1.將數(shù)據(jù)的高低位, 更改為低位在前,高位在后,如:
<00000045> --> <45000000>數(shù)據(jù)以16進制的形式展示,
驗證:利用計算器轉(zhuǎn)換為十進制 等于 69.后附圖.
2.利用NSData 中的:
- (void)getBytes:(void *)buffer range:(NSRange)range
方法獲取每一個字節(jié)
3.(注意當前為:低位在前,高位在后)
將每一個字節(jié), 根據(jù)字節(jié)所屬的范圍,統(tǒng)一往左位移相應(yīng)的 8 (1字節(jié)(byte) = 8bits)的倍數(shù),
也就是說,將后面高位的字節(jié),統(tǒng)一處理為低位的字節(jié), 將統(tǒng)一格式的字節(jié), 相加.
利用%zd 輸出每一個字節(jié)的 十進制的大小.進行檢驗.
4AE2B600-2ACB-40F7-958C-35D9B8A9EEA1.png
CFD0FD1D-FD26-474F-831C-C3902E18DCBC.png
ACF58783-249F-4996-B6F5-ADD3514175AF.png
2.高低位互換-dataWithReverse
- (NSData *)dataWithReverse:(NSData *)data
{
NSMutableData *dstData = [[NSMutableData alloc] initWithData:data];
NSUInteger count = data.length / 2;
for (NSUInteger i = 0; i < count; i++) {
NSRange head = NSMakeRange(i, 1);
NSRange end = NSMakeRange(data.length - i - 1, 1);
NSData *headData = [data subdataWithRange:head];
NSData *endData = [data subdataWithRange:end];
[dstData replaceBytesInRange:head withBytes:endData.bytes];
[dstData replaceBytesInRange:end withBytes:headData.bytes];
}
return dstData;
}
1.例用NSMutableData 字節(jié)互換 的方法
- (void)replaceBytesInRange:(NSRange)range withBytes:(const void *)bytes;
2.遍歷數(shù)據(jù)
從收尾開始,依次將字節(jié)互換, 一般包頭所占字節(jié)都不會太多, 所以只需遍歷幾次就可以完成,遍歷的次數(shù)為字節(jié)總數(shù)減半
3.將長度轉(zhuǎn)為占固定字節(jié)數(shù)的NSData - dataForLength
NSMutableData *valData = [NSMutableData data];
NSUInteger templen = length;
int offset = 0;
while (offset < byteCount) {
unsigned char valChar = 0xff & templen;
[valData appendBytes:&valChar length:1];
templen = templen >> 8;
offset++;
}
return valData;
1.利用:NSMutableData 的方法
- (void)appendBytes:(const void *)bytes length:(NSUInteger)length;
2.將數(shù)據(jù) & 0xff
0xff是十六進制FF的表示方法棚放,因為一個十六進制數(shù)字轉(zhuǎn)換成二進制是四位,即F=1111塞茅,所以0xff占用一個字節(jié), 8bits
&符表示的是按位數(shù)進行與(同為1的時候返回1歼冰,否則返回0)
保留后7位吞彤,高位清零,避免符號位擴展:
3.while循環(huán)的說明:
首先, 0xff & templen, 只保留最低的字節(jié), 獲取到最低位的字節(jié), 利用appendBytes, 將此字節(jié)添加進去,
進而將templen往右移8位, 也就是剔除剛才計算過的字節(jié), 獲取第二個字節(jié)
,將第二個字節(jié)添加進去.依次往后直到將所有的包頭字節(jié)添加完畢