说来惭愧,搞了两年ios居然木有用过socket...初学ios的时候倒是了解过,但是两年不用,之前学的内容已经完全忘光光.于是又开始网上各种查.
用cf的socket貌似显得很拽的样子,但是实在不适合我这种领导紧逼着出项目的情况.搜了下发现目前最常用的socket库应该就是AsyncSocket了.嗯,看起来很简单,搞it~
这个库有基于runloop和GCD两种,据我一哥们说runloop版本是基于timer机制实现异步处理,会跟scroller的滚动动画冲突.我暂时还没有验证他的说法,不过保险起见还是用了GCD.因为服务端那里用的是tcp,所以最终我只导入了GCDAsyncSocket.h/.m两个文件.
socket的数据处理放在一个JLYSocketManager类进行.因为我们是程序在就一直保持长链,所以直接搞成单例.
以下正题:
首先要进行连接:
1 if (!_asynSocket) 2 { 3 _asynSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; 4 } 5 6 NSError *err = nil; 7 if(![_asynSocket connectToHost:kSocketHost onPort:kSocketPort error:&err]) 8 { 9 //handle error 10 NSLog(@"Error: %@", err); 11 _retryTime++; 12 [self connentServer]; 13 } 14 else 15 { 16 //do sth after connect 17 }
成功连接之后就可以推数据或者读服务器的数据了.
1 //get data from server 2 [_asynSocket readDataWithTimeout:-1 tag:0]; 3 4 //send data to server 5 [_asynSocket writeData:data withTimeout:-1 tag:0];
然后要设置一些相应的委托方法,我主要用了下面几个:
1 #pragma mark - 2 #pragma mark -----socket delegate----- 3 - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port; 4 5 - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag; 6 7 - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; 8 9 - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err;
不用多说,看名字就能明白这几个方法是干嘛的.
值得注意的是,你需要在"适当的地方"设置数据读取的监听,其实就是调用上面说的readDataWithTimeout方法.比如在didConnectToHost里,接收建立信道后服务端传来的链接状态信息.或者接受了一次数据,然后在didReadData里面设置继续监听.总之就是你需要保持一致向服务端索取数据.
so easy吧~
tcp通信必然伴随着粘包或者拆包的情况.而每次通过didReadData这个委托方法收到的是数据都是以包为单位.拆包粘包说白了就是服务器一次给你发了很多条信息,但是这些信息不是一条信息一个包,而是一条信息分了几个包发来,或者几条信息的数据合到一个包发来了.
java貌似自己有框架,闭着眼就能完成拆包合包的工作.但是oc上貌似没找到.只好手动.那首先要跟服务器有个协议,说明一下以什么作为一条信息的节点.比如我们,每条信息的头四个字节是用来保存这条信息的应有长度的.接到信息首先要读取长度,然后再跟收到的数据包长度对比.如果收到的长度刚刚好,说明这是一条完整的信息,直接封起来就行.如果收到的长度比预计长度短,说明信息被拆包发送,那就要继续跟下一个包进行拼装,再检验长度.如果收到的长度比预计的长,说明发生了粘包,那就要根据预计长度对数据包的数据进行拆分,对拆出的两部分数据继续进行处理.
说起来很啰嗦,其实简单,大概就是这么个方法:
1 /** 2 * 处理_bufferData 3 * 主要处理拆包粘包问题.获得每段数据的长度之后,和当前buffer比较,如果刚好说明接收完全;如果buffer过大说明服务端粘包,需要拆包;如果buffer小说明服务端拆包,需要合包 4 * 5 * @param newData 获取的新数据,如果只是本地拆包则传nil 6 */ 7 - (void)handleBufferData:(NSData *)newData 8 { 9 if (newData) 10 { 11 //这是一个缓冲区,因为是全局的,并且是单例中,所以在数据取走的时候一定要清空 12 [_bufferData appendData:newData]; 13 } 14 15 //首先取前四位 16 int i = 0; 17 [_bufferData getBytes:&i range:NSMakeRange(0, 4)]; 18 //因为oc和java字节数组的高地位顺序是反的,所以要翻转一下顺序 19 Byte *fix = [self overturnIntByteArr:i]; 20 memcpy(&i, fix, 4); 21 free(fix); 22 23 //刚好 24 if (i == [_bufferData length]-4) 25 { 26 NSData *contentData = [_bufferData subdataWithRange:NSMakeRange(4, i)]; 27 //完整接收了一条信息的处理 28 [self finishGetWholeData:contentData]; 29 30 _bufferData = [NSMutableData data]; 31 } 32 //需要拆包 33 else if (i < [_bufferData length]-4) 34 { 35 NSData *contentData = [_bufferData subdataWithRange:NSMakeRange(4, i)]; 36 //这是完成了第一个包的数据的接收 37 [self finishGetWholeData:contentData]; 38 39 NSData *buffer = [_bufferData subdataWithRange:NSMakeRange(i+4, _bufferData.length-i-4)]; 40 _bufferData = [NSMutableData data]; 41 [_bufferData appendData:buffer]; 42 43 //继续进行判断处理 44 [self handleBufferData:nil]; 45 } 46 } 47
大概就是酱紫.思路是这样,但是估摸着是会有一些问题的,以后使用中再慢慢完善吧~
总结一下前两天刚尝试的socket编程-使用AsyncSocket