什么是socket?
网络上两个程序通过一个双向通信连接实现数据交互,这种双向通信的连接叫做Socket.
本质上,Socket 是一组对TCP/UDP协议封装的api接口,处于应用层与传输层之间.
连接过程
建立Socket连接至少需要一对套接字,分别运行于服务端和客户端。套接字直接的连接需要三个步骤:
1、服务器监听
服务端Socket始终处于等待连接状态,实时监听是否有客户端请求连接。
2、客户端请求
客户端Socket提出连接请求,指定服务端Socket的ip地址和端口号,这时就可以向对应的服务端提出Socket连接请求。
3、连接确认
当服务端Socket监听到客户端Socket提出的连接请求时作出响应,建立一个新的进程,把服务端Socket的描述发送给客户端,该描述得到客户端确认后就可建立起Socket连接。而服务端Socket则继续处于监听状态,继续接收其他客户端Socket的请求。
CocoaAsyncSocket
iOS 开发中socket有一个第三方库CocoaAsyncSocket
很好用.
使用的时候传递host和端口即可进行连接,
- (void)connectToServerWithCommand:(NSString *)command { _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; [_socket setUserData:command]; NSError *error = nil; [_socket connectToHost:host onPort:端口号 error:&error]; if (error) { NSLog(@"连接 error:%@",error.userInfo); } [_socket writeData:[command dataUsingEncoding:NSUTF8StringEncoding] withTimeout:10.0f tag:6]; }
|
然后实现代理
#pragma mark - Socket Delegate
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { NSLog(@"Socket连接成功:%s",__func__); }
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{ if (err) { NSLog(@"连接失败"); }else{ NSLog(@"正常断开"); } }
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag { NSLog(@"数据发送成功:%s",__func__); [sock readDataWithTimeout:-1 tag:tag]; }
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { NSString *receiverStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"读取数据:%s %@",__func__,receiverStr); }
|
TCP粘包、半包
什么是粘包?
由于TCP默认会使用优化方法(Nagle算法)。
将多次间隔较小且数据量小的数据,合并成一个大的数据块,进行封包,然后依次发送。
这么做优点就是为了减少广域网的小分组数目,可以减小网络拥塞的出现,同时也可以节省宽带。
什么是半包?
当发送一条很大的数据包,类似音视频,大图等,一次发送或者读取数据的缓冲区大小是有限的,所以会分段去发送或者读取数据。当多个包发送的时候,有些包可能就发送失败了,接收方接收数据不全就出现了半包的问题。
怎么处理?
如果我们要正确解析数据,那么必须要使用一种合理的机制去解包。这个机制的思路其实很简单:
在设计数据结构时记录包长和包的起始位置,一般就是每个包都设计一个包头和包体,包头包含包长等信息,包体就是具体传递的数据。
基于 CocoaAsyncSocket 处理
首先需要了解几个方法:
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
|
包读取:
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { ... [_socket readDataToLength:LENGTH_HEAD withTimeout:-1 tag:TAG_HEAD]; }
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { if (tag == TAG_HEAD) { self.dataHead = data; UInt16 lengthTotal; [data getBytes:&lengthTotal range:RANGE_PACKET_LENGTH]; lengthTotal = CFSwapInt16BigToHost(lengthTotal); UInt16 lengthBody = lengthTotal - LENGTH_HEAD;
[sock readDataToLength:lengthBody withTimeout:-1 tag:TAG_BODY]; } else if (tag == TAG_READ_BODY) { NSMutableData *mPacketData = [NSMutableData dataWithData:self.dataHead]; [mPacketData appendData:data]; self.dataHead = nil; SocketResponse *response = [SocketResponse responseFromData:[mPacketData copy]]
[sock readDataToLength:LENGTH_HEAD withTimeout:-1 tag:TAG_HEAD]; } else { NSLog(@"Socket: DidReadData: withTag: 没有相应的 TAG"); } }
|
解析:
SocketResponse.m
+ (instancetype)responseFromData:(NSData *)respData { SocketPacketHead *currentHead = [[SocketPacketHead alloc] initWithData:respData]; NSData *bodyData = [respData subdataWithRange:NSMakeRange(LENGTH_HEAD, respData.length - LENGTH_HEAD)]; SocketResponse *resp = [self finalBodyDataWithHead:currentHead bodyData:bodyData]; return resp; }
|