iOS開發之Socket通信實戰--Request請求數據包編碼模塊



實際上在iOS很多應用開發中,大部分用的網絡通信都是http/https協議,除非有特殊的需求會用到Socket網絡協議進行網絡數 據傳輸,這時候在iOS客戶端就需要很好的第三方CocoaAsyncSocket來進行長連接連接和傳輸數據,該第三方地 址:https://github.com/robbiehanson/CocoaAsyncSocket,讀者可以自行google或者baidu搜索 這個庫的用法,網上有很多資料,而且用法不難。

在一些對Socket通信使用需求不是很高的應用中,比如需要多個iOS設備之間進行聊天 即時通訊,這時候只要用這個CocoaAsyncSocket就基本能滿足這個需求,但是在本人從事的直播項目中對Socket通信協議的需求就比較高, 這個需求模式和游戲開發模式類似,因為游戲應用上就有很多數據變化是需要實時更新數據的,比如游戲的玩家生命值,如果在多人聯網游戲平台上,你的設備上需 要實時更新其他玩家的生命值數據,而其他玩家設備上也會實時更新你的生命值數據,這個需求通過http主動請求是不能滿足需求,所以游戲開發一般都會有 Socket通信。而在本人從事的直播項目后台所用的語言是erlang語言,后台對這Socket協議傳輸的數據有一個自定義的協議規則,例如下圖:

                    圖 1

這協議就是一段可以在Socket傳輸的二進制流,后面第三部分協議數據流就是具體要傳輸的數據字段,

而這個協議數據流內容就是如下通過erlang的一個協議文檔的示例:

// ========== 切換到新場景 ==========
message Cs_20001{   
    uint8 SceneId = 1;          // 進入場景ID
    uint32 Line = 2;             // 分線,公共場景發0,多人副本后端會指定

 String message = 3;     // 消息
}
message Sc_20001{
    uint8 Res = 1;               // 1成功 0異常 2不能進入該關卡 3已經處於關卡當是 4沒這個地圖或關卡 5今日進入BOSS關或普通關次數上限 6體力不足 7進入條件不足 8地圖類型不存在 10 沒參加BOSS活動,無法進入 11 BOSS戰房間狀態已經結束 12 次數不足 13 冷卻時間不足 14無權進入這個BOSS房間 15進BOSS房間條件不足
    uint32 SceneId = 2;          // 現在所在的場景
    uint8 Pos = 3;               // 坐標點
    uint8 Status = 4;            // 人物狀態 0 正常 10隊長狀態
    uint8 Type = 5;              // 前端標識
    uint32 DefaultCombat = 6;    // 推薦戰斗力

  String message = 7;     // 消息
}
先解釋一下這個協議的一些定義:
  Cs就是Client --> Server(客戶端向服務器發送的數據協議),而Sc就是Server-->Client(服務器向客戶端返回的數據協議)

本篇主要講解請求模塊,所以就講這個Cs_20001請求協議封裝數據包,這個20001就是這個協議的號,也叫協議ID(后面在代碼中會用到)。

在 Cs_20001中,有兩個需要Socket發送給服務器的字段,都是uint32格式的,也是C語言的基本數據類型,但是在Socket傳輸中傳輸的是 二進制流,也就是說,我們需要將這兩個uint32格式的數據字段轉為二進制數據,然后拼接成一條數據流,然后讓這個數據流在Socket中傳遞到服務 器,說到流如果學過Java的同學會對流的概念比較容易理解,如果沒接觸過流也沒關系,就好比是一截水流從客戶端流向服務器。其實這個數據流從客戶端傳遞 到服務器,這個過程也不是想象的那么簡單,涉及到很多底層的Socket傳輸邏輯邏輯,但是CocoaAsyncSocket已經做好了這部分的封裝,而 且是OC面向對象的封裝,我們只需要將需要傳遞的數據轉為NSData通過CocoaAsyncSocket的代理方法傳遞過去就好了。

對 於簡單的需求,比如我僅僅只需要兩台iPhone設備傳遞NSString字符串,只要將NSString轉為NSData傳遞就好,但是對於我上面說的 erlang服務器需要自定義的協議,就需要客戶端更多的封包解包的邏輯了,這個封包,比如拿上面字段協議為例子,就是將Cs_20001的數據包按照圖 1中自定義的格式進行拼接數據,那么這時候在圖 1中的需要兩個字節的協議號就是20001了,也就是說需要將20001用兩個字節的存儲空間存儲,然后在圖 1中, 協議數據流的內容就是uint32 SceneId和uint32 Line這兩個字段拼接成的數據,而且協議規定了順序拼接就是怎么樣的順序,這里uint32 SceneId當然是在前而uint32 Line在后,拼接好后,可以計算得出,這個協議數據流的字節數?bytes,然后+2(協議號的字節長度),再+4(消息總長度需要的四個字節),就得 到整個協議流的長度,然后把這個總長度存儲在四個字節的消息總長度中,當然整個協議流的從左到右的拼接順序還是如圖 1中所示,然后通過CocoaAsyncSocket傳遞給服務器就好。

下面就通過代碼來講解這個業務邏輯:

一、首先對后台提供的協議進行模型對象化,但凡有MVC基礎就應該秒懂,其實就是MVC中的Model。

                                                                       圖 2

二、使用這個模型

                           圖 3

三、 因為在Socket通信協議中,是通過二進制字節碼傳輸的,所以需要將模型中的屬性,比如上面的sceneId、line和message分別轉為 byte類型,然后轉為NSData(OC端需要NSData),然后通過Socket傳輸。這個過程就叫做"編碼(Encoded)",編碼的同時還要 按順序拼接,不要亂來哦。

                            圖 4

通過遍歷並用運行時對模型對象的屬性逐一取出類型和值,根據類型,來將這個值通過對應的編碼方式來轉為byte字節碼,然后轉為NSData這個OC的二進制對象類型。

在這里屬性的類型其實OC有規定,不了解可以通過上面的運行時進行打印出所有類型的結果。

這里就上面那個Cs_20001的數據包模型對象編碼的同時,也進行NSLog打印查看看是什么值和類型:

                  圖 5

看看這個結果,我們可以看到uint8_t類型是TC、uint32_t類型是TI、NSString類型是T@"NSString",當然還有很多其他的,可以自行去打印查看,或者Google搜索。

那么接着就解釋圖 4中的70、73、76、79和82行的TYPE_...是什么了,其實就是常量定義:

                               圖 6

而編碼所用到的工具類的接口:

                               圖 7

具體編碼和解碼的實現:

  1 #import "YMSocketUtils.h"  2  3 @implementation YMSocketUtils  4  5 /**  6  * 反轉字節序列  7  *  8  * @param srcData 原始字節NSData  9  *  10  * @return 反轉序列后字節NSData  11 */  12 + (NSData *)dataWithReverse:(NSData *)srcData  13 {  14 // NSMutableData *dstData = [[NSMutableData alloc] init];  15 // for (NSUInteger i=0; i<srcData.length; i++) {  16 // [dstData appendData:[srcData subdataWithRange:NSMakeRange(srcData.length-1-i, 1)]];  17 // }//for  18  19 NSUInteger byteCount = srcData.length;  20 NSMutableData *dstData = [[NSMutableData alloc] initWithData:srcData];  21 NSUInteger halfLength = byteCount / 2;  22 for (NSUInteger i=0; i<halfLength; i++) {  23 NSRange begin = NSMakeRange(i, 1);  24 NSRange end = NSMakeRange(byteCount - i - 1, 1);  25 NSData *beginData = [srcData subdataWithRange:begin];  26 NSData *endData = [srcData subdataWithRange:end];  27  [dstData replaceBytesInRange:begin withBytes:endData.bytes];  28  [dstData replaceBytesInRange:end withBytes:beginData.bytes];  29 }//for  30  31 return dstData;  32 }  33  34 + (NSData *)byteFromUInt8:(uint8_t)val  35 {  36 NSMutableData *valData = [[NSMutableData alloc] init];  37  38 unsigned char valChar[1];  39 valChar[0] = 0xff & val;  40 [valData appendBytes:valChar length:1];  41  42 return [self dataWithReverse:valData];  43 }  44  45 + (NSData *)bytesFromUInt16:(uint16_t)val  46 {  47 NSMutableData *valData = [[NSMutableData alloc] init];  48  49 unsigned char valChar[2];  50 valChar[0] = 0xff & val;  51 valChar[1] = (0xff00 & val) >> 8;  52 [valData appendBytes:valChar length:2];  53  54 return [self dataWithReverse:valData];  55 }  56  57 + (NSData *)bytesFromUInt32:(uint32_t)val  58 {  59 NSMutableData *valData = [[NSMutableData alloc] init];  60  61 unsigned char valChar[4];  62 valChar[0] = 0xff & val;  63 valChar[1] = (0xff00 & val) >> 8;  64 valChar[2] = (0xff0000 & val) >> 16;  65 valChar[3] = (0xff000000 & val) >> 24;  66 [valData appendBytes:valChar length:4];  67  68 return [self dataWithReverse:valData];  69 }  70  71 + (NSData *)bytesFromUInt64:(uint64_t)val  72 {  73 NSMutableData *valData = [[NSMutableData alloc] init];  74  75 unsigned char valChar[8];  76 valChar[0] = 0xff & val;  77 valChar[1] = (0xff00 & val) >> 8;  78 valChar[2] = (0xff0000 & val) >> 16; 79 valChar[3] = (0xff000000 & val) >> 24; 80 valChar[4] = (0xff00000000 & val) >> 32; 81 valChar[5] = (0xff0000000000 & val) >> 40; 82 valChar[6] = (0xff000000000000 & val) >> 48; 83 valChar[7] = (0xff00000000000000 & val) >> 56; 84 [valData appendBytes:valChar length:8]; 85 86 return [self dataWithReverse:valData]; 87 } 88 89 + (NSData *)bytesFromValue:(NSInteger)value byteCount:(int)byteCount 90 { 91 NSAssert(value <= 4294967295, @"bytesFromValue: (max value is 4294967295)"); 92 NSAssert(byteCount <= 4, @"bytesFromValue: (byte count is too long)"); 93 94 NSMutableData *valData = [[NSMutableData alloc] init]; 95 NSUInteger tempVal = value; 96 int offset = 0; 97 98 while (offset < byteCount) { 99 unsigned char valChar = 0xff & tempVal; 100 [valData appendBytes:&valChar length:1]; 101 tempVal = tempVal >> 8; 102 offset++; 103 }//while 104 105 return valData; 106 } 107 108 + (NSData *)bytesFromValue:(NSInteger)value byteCount:(int)byteCount reverse:(BOOL)reverse 109 { 110 NSData *tempData = [self bytesFromValue:value byteCount:byteCount]; 111 if (reverse) { 112 return tempData; 113 } 114 115 return [self dataWithReverse:tempData]; 116 } 117 118 + (uint8_t)uint8FromBytes:(NSData *)fData 119 { 120 NSAssert(fData.length == 1, @"uint8FromBytes: (data length != 1)"); 121 NSData *data = fData; 122 uint8_t val = 0; 123 [data getBytes:&val length:1]; 124 return val; 125 } 126 127 + (uint16_t)uint16FromBytes:(NSData *)fData 128 { 129 NSAssert(fData.length == 2, @"uint16FromBytes: (data length != 2)"); 130 NSData *data = [self dataWithReverse:fData];; 131 uint16_t val0 = 0; 132 uint16_t val1 = 0; 133 [data getBytes:&val0 range:NSMakeRange(0, 1)]; 134 [data getBytes:&val1 range:NSMakeRange(1, 1)]; 135 136 uint16_t dstVal = (val0 & 0xff) + ((val1 << 8) & 0xff00); 137 return dstVal; 138 } 139 140 + (uint32_t)uint32FromBytes:(NSData *)fData 141 { 142 NSAssert(fData.length == 4, @"uint16FromBytes: (data length != 4)"); 143 NSData *data = [self dataWithReverse:fData]; 144 145 uint32_t val0 = 0; 146 uint32_t val1 = 0; 147 uint32_t val2 = 0; 148 uint32_t val3 = 0; 149 [data getBytes:&val0 range:NSMakeRange(0, 1)]; 150 [data getBytes:&val1 range:NSMakeRange(1, 1)]; 151 [data getBytes:&val2 range:NSMakeRange(2, 1)]; 152 [data getBytes:&val3 range:NSMakeRange(3, 1)]; 153 154 uint32_t dstVal = (val0 & 0xff) + ((val1 << 8) & 0xff00) + ((val1 << 16) & 0xff0000) + ((val1 << 24) & 0xff000000); 155 return dstVal; 156 } 157 158 + (NSInteger)valueFromBytes:(NSData *)data 159 { 160 NSAssert(data.length <= 4, @"valueFromBytes: (data is too long)"); 161 162 NSUInteger dataLen = data.length; 163 NSUInteger value = 0; 164 int offset = 0; 165 166 while (offset < dataLen) { 167 uint32_t tempVal = 0; 168 [data getBytes:&tempVal range:NSMakeRange(offset, 1)]; 169 value += (tempVal << (8 * offset)); 170 offset++; 171 }//while 172 173 return value; 174 } 175 176 + (NSInteger)valueFromBytes:(NSData *)data reverse:(BOOL)reverse 177 { 178 NSData *tempData = data; 179 if (reverse) { 180 tempData = [self dataWithReverse:tempData]; 181 } 182 return [self valueFromBytes:tempData]; 183 } 184 185 + (NSData *)dataFromHexString:(NSString *)hexString 186 { 187 NSAssert((hexString.length > 0) && (hexString.length % 2 == 0), @"hexString.length mod 2 != 0"); 188 NSMutableData *data = [[NSMutableData alloc] init]; 189 for (NSUInteger i=0; i<hexString.length; i+=2) { 190 NSRange tempRange = NSMakeRange(i, 2); 191 NSString *tempStr = [hexString substringWithRange:tempRange]; 192 NSScanner *scanner = [NSScanner scannerWithString:tempStr]; 193 unsigned int tempIntValue; 194 [scanner scanHexInt:&tempIntValue]; 195 [data appendBytes:&tempIntValue length:1]; 196 } 197 return data; 198 } 199 200 + (NSString *)hexStringFromData:(NSData *)data 201 { 202 NSAssert(data.length > 0, @"data.length <= 0"); 203 NSMutableString *hexString = [[NSMutableString alloc] init]; 204 const Byte *bytes = data.bytes; 205 for (NSUInteger i=0; i<data.length; i++) { 206 Byte value = bytes[i]; 207 Byte high = (value & 0xf0) >> 4; 208 Byte low = value & 0xf; 209 [hexString appendFormat:@"%x%x", high, low]; 210 }//for 211 return hexString; 212 } 213 214 + (NSString *)asciiStringFromHexString:(NSString *)hexString 215 { 216 NSMutableString *asciiString = [[NSMutableString alloc] init]; 217 const char *bytes = [hexString UTF8String]; 218 for (NSUInteger i=0; i<hexString.length; i++) { 219 [asciiString appendFormat:@"%0.2X", bytes[i]]; 220 } 221 return asciiString; 222 } 223 224 + (NSString *)hexStringFromASCIIString:(NSString *)asciiString 225 { 226 NSMutableString *hexString = [[NSMutableString alloc] init]; 227 const char *asciiChars = [asciiString UTF8String]; 228 for (NSUInteger i=0; i<asciiString.length; i+=2) { 229 char hexChar = '\0'; 230 231 //high 232 if (asciiChars[i] >= '0' && asciiChars[i] <= '9') { 233 hexChar = (asciiChars[i] - '0') << 4; 234 } else if (asciiChars[i] >= 'a' && asciiChars[i] <= 'z') { 235 hexChar = (asciiChars[i] - 'a' + 10) << 4; 236 } else if (asciiChars[i] >= 'A' && asciiChars[i] <= 'Z') { 237 hexChar = (asciiChars[i] - 'A' + 10) << 4; 238 }//if 239 240 //low 241 if (asciiChars[i+1] >= '0' && asciiChars[i+1] <= '9') { 242 hexChar += asciiChars[i+1] - '0'; 243 } else if (asciiChars[i+1] >= 'a' && asciiChars[i+1] <= 'z') { 244 hexChar += asciiChars[i+1] - 'a' + 10; 245 } else if (asciiChars[i+1] >= 'A' && asciiChars[i+1] <= 'Z') { 246 hexChar += asciiChars[i+1] - 'A' + 10; 247 }//if 248 249 [hexString appendFormat:@"%c", hexChar]; 250 } 251 return hexString; 252 } 253 254 @end

剩下的留給讀者自行下載這個Demo看代碼吧,鏈接: http://pan.baidu.com/s/1hsi7tNQ 密碼: byiy

 

尊重勞動成果,轉載請注明出處;iOS開發之Socket通信實戰--Request請求數據包編碼模塊

 

另外補充一個發現到的博客:初用 CocoaAsyncSocket

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM