一. CocoaAsyncSocket API 基本使用介绍
提供支持 TCP 和 UDP 接口,乍一看以为很简单分别对应 GCDAsyncSocket 和 GCDAsyncUdpSocket 两个类,但是一个类中几千行代码。。。。
1. GCDAsyncSocket - TCP 主要 API
public - method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
NSError *error; [self.socket connectToHost:@"127.0.0.1" onPort:8029 error:&error];
NSData *data = [@"zerocc发送消息" dataUsingEncoding:NSUTF8StringEncoding];
[self.socket writeData:data withTimeout:-1 tag:10000];
[self.socket disconnect]; self.socket = nil;
|
GCDAsyncSocketDelegate - method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { NSLog(@"连接成功 : %@---%d",host,port); [self.socket readDataWithTimeout:-1 tag:10000]; }
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
NSLog(@"断开 socket 连接原因:%@",err); }
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(@"接收到tag = %ld : %lu 长度的数据",tag,(unsigned long)data.length); [self.socket readDataWithTimeout:-1 tag:10000]; }
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag { NSLog(@"%ld 发送数据成功",tag); }
|
二. 自定义报文通讯规约
通讯数据封装格式:
报文头 |
数据域长度 |
数据域 |
校验和 |
报文尾 |
0x86 |
长度低字节长度高字节 |
JSON 数据域 |
1字节CheckSum |
00A8 |
1. 粘包拆包
TCP 传输过程中不会流失字节,但是由于其是流式传输,传输的数据之间是没有边界的,不同于 UDP 提供传输服务,其传输的数据是有边界的,因而不能保证每次接收的包都是一个完整的包。应用层数据被分割成多次发送产生粘包问题的原因有以下几种情况:
- 应用层调用 write 方法,将应用层的缓冲区中的数据拷贝到套接字的发送缓冲区。而发送缓冲区有一个 SO_SNDBUF 的限制,如果应用层的缓冲区数据大小大于套接字发送缓冲区的大小,则数据需要进行多次的发送。
- TCP 所传输的报文段有 MSS 的限制,如果套接字缓冲区的大小大于 MSS,也会导致消息的分割发送。
- 由于链路层最大发送单元 MTU,在 IP 层会进行数据的分片。
处理方法:将 JSON 字符串转16进制表示然后转为 data 流,用0x86 和 0xA8 特殊字符分别作为报文头和报文尾 ASCII码与16进制转换对照表; 报文数据编码处理示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| NSString *str = @"zerocc我";
NSData *strData = [str dataUsingEncoding:NSUTF8StringEncoding]; const Byte *bytes = (Byte *)[strData bytes]; NSString *hexStr = @""; for (int i = 0; i < [strData length]; i++) { NSString *newHexStr = [NSString stringWithFormat:@"%x",bytes[i]&0xff]; if([newHexStr length]==1) { hexStr = [NSString stringWithFormat:@"%@0%@",hexStr,newHexStr]; }else { hexStr = [NSString stringWithFormat:@"%@%@",hexStr,newHexStr]; } } NSLog(@"%@",hexStr);
unsigned long lens = [hexStr length] / 2; unsigned char *buf = malloc(lens); unsigned char *whole_byte = buf; char byte_chars[3] = {'\0','\0','\0'}; for (int i=0; i < [hexStr length] / 2; i++) { byte_chars[0] = [hexStr characterAtIndex:i*2]; byte_chars[1] = [hexStr characterAtIndex:i*2+1]; *whole_byte = strtol(byte_chars, NULL, 16); whole_byte++; } NSData *messageData = [NSData dataWithBytes:buf length:lens]; free(buf);
NSMutableData *sourceData = [[NSMutableData alloc] init]; Byte head = 0x86; NSData *headData = [NSData dataWithBytes:&head length:sizeof(head)]; Byte end = 0xA8; NSData *endData = [NSData dataWithBytes:&end length:sizeof(end)]; [sourceData appendData:headData]; [sourceData appendData:messageData]; [sourceData appendData:endData]; NSLog(@"%@",sourceData);
|
1 2 3 4 5 6 7 8 9 10
| 运行结果对比几部如下: ...字符串:zerocc我 ...16进制字符串:7a65726f6363e68891 ...16进制字符串转data:<7a6572 6f6363e6 8891> ...拼接头尾:<867a6572 6f6363e6 8891a8>
分析: 字母 z 的 ASCII 码是 122 122 的十六进制表示就是 0x7a 依次类推但是0x86 和 0xA8 是特殊的,一般字符编码不会出现86 和 A8, 所以在进行 data 接收时可以通过头和尾去判断截取
|
2. 校验和CheckSum
主要作用用于校验数据传输的合法性;
本文标题:iOS - CocoaAsyncSocket 项目运用总结
文章作者:zerocc
发布时间:2015年10月27日
原始链接:http://www.zerocc.com.cn/20151027.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!