iOS TCP的使用及粘包断包处理
2017-07-25 10:09
274 查看
概要:有关TCP的与服务器的三次握手此处就不介绍了,网上有很多基础知识,此篇主要是介绍使用TCP与服务器通信的实战项目。
一、TCP的基本使用
使用TCP与服务器通讯,我是使用GCDAsyncSocket三方库,首先在github中下载类库,加到项目工程,或者直接使用cocopods导入封装自己在项目中使用的TCP类库,在OGTcpClient.h中定义常用属性,socket连接和发送消息的方法,如下
#import "GCDAsyncSocket.h" #import <Foundation/Foundation.h> @class OGTcpClient; enum{ SocketOfflineByServer,// 服务器掉线,默认为0 SocketOfflineByUser, // 用户主动cut }; typedef enum { OGResultTypeUserLoginSuccess, OGResultTypeUserLoginFailed, OGResultTypeActorLoginSuccess, OGResultTypeActorLoginFailure }OGResultType; typedef void(^OGResultBlock)(OGResultType type); @interface OGTcpClient : NSObject<GCDAsyncSocketDelegate, UIAlertViewDelegate> singleton_interface(OGTcpClient) @property (nonatomic, strong) GCDAsyncSocket *clientSocket; @property (nonatomic, assign) NSTimer *connectTimer;// 计时器 @property (nonatomic, assign) BOOL hasHeatBeat; @property (nonatomic, assign) OGResultType _block;
@property (nonatomic, strong) NSMutableData *_readBuf;// 缓冲区
- (void)socketConnectHost;// socket连接 - (void)cutOffSocket; // 断开socket连接 - (void)writeData:(NSData *)data; // 发送消息 - (void)hasReadData:(NSData *)data; // 接收数据 /**向服务器发送登录确认消息,消息格式使用Protobuf,此篇未实现此方法,有关protobuf消息的序列化见下一篇*/ - (void)sendUserLoginConfirmMsgWithUserId:(int)userid session:(NSString *)session andCompletion:(OGResultBlock)completion; @end
连接到服务器
- (void)socketConnectHost { // 连接之前需要手动断开 // 确保断开后再连,如果对一个正处于连接状态的socket进行连接,会出现崩溃 [self cutOffSocket]; self.hasHeatBeat = NO; self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_queue_create(kCLIENT_QUEUE, NULL)]; NSError *error = nil; [self.clientSocket connectToHost:kSERVER_ADDRESS onPort:kSERVER_PORT error:&error]; } // 主动断开 - (void)cutOffSocket { id userData = @(SocketOfflineByUser); self.clientSocket.userData = userData; [self.clientSocket disconnect]; }
如果连接服务器成功会调用GCDAsyncSocket中的didConnectToHost代理方法,如下
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { NSLog(@"连接到服务器: [%@:%d]", host, port); // 存储接收数据的缓存区,处理数据的粘包和断包 _readBuf = [[NSMutableData alloc] init]; // 连接成功之后可以增加定时器检查心跳包,检测心跳包的时间可以根据自己项目实际情况来定,一般z [self addConnctTimer]; [self.clientSocket readDataWithTimeout:kREAD_TIMEOUT tag:0]; } // 登录成功之后在检测心跳包 - (void)addConnctTimer { //把定时器放在子线程中 每隔30秒检测是否接收过心跳包 if (self.connectTimer != nil) { [self.connectTimer invalidate]; self.connectTimer = nil; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_MSEC), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{ self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(checkForHeartBeat) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; }); }
检测心跳包方法
-(void)checkForHeartBeat { NSLog(@"======检测过心跳包"); if (self.hasHeatBeat == NO) { [self cutOffSocket]; AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate; [app loginAndSelectRole]; } self.hasHeatBeat = NO; }
如果未成功连接或者又断开连接,会调用socketDidDisconnect代理方法
/**断开连接:1、服务器断开2、用户主动断开(用户退出或程序退出)*/ - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err { if (self.connectTimer != nil) { [self.connectTimer invalidate]; self.connectTimer = nil; } NSLog(@"失去连接:[%@:%d]==%@", [sock connectedHost], [sock connectedPort], err); if (sock.userData == SocketOfflineByServer) { [[NSNotificationCenter defaultCenter] postNotificationName:@"isSocketDisconnect" object:nil]; [[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"isDisconnect"]; [[NSUserDefaults standardUserDefaults] synchronize]; // 服务器掉线,重连 // 这里可以给用户以提示,写一个重连发送,并调用 AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate; [app loginAndSelectRole]; } }
连接服务器成功后会调用GCDAscySocket提供的代理方法接收服务器数据,或者向服务器发送数据
// 接收到服务器数据时调用 - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { // 此方法是处理数据的粘包或者断包,见后面 [self disposeBufferData:data]; [sock readDataWithTimeout:kREAD_TIMEOUT tag:0]; } // 已经向服务器发送数据后调用此代理方法 - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag { NSString *ip = [sock connectedHost]; uint16_t port = [sock connectedPort]; NSLog(@" 客户端已发送数据: [%@:%d]", ip, port); [sock readDataWithTimeout:kREAD_TIMEOUT tag:0]; } - (void)writeData:(NSData *)data { [self.clientSocket writeData:data withTimeout:kWRITE_TIMEOUT tag:0]; }
客户端与服务器的消息格式一般由服务器定义好,按照规则读取数据,下面举个例子
根据上面定义的数据协议进行消息的断包和粘包处理
- (void)disposeBufferData:(NSData *)data { // 粘包处理方式 保存缓存数据 [_readBuf appendData:data]; while (_readBuf.length >= 5) {// 头数据为5个字节 // int16FromBytes是自己写的一个方法,将截取的data数据转换成int // 得到数据的ID 和 整个数据的长度 4000 NSInteger ID = [OGSocketUtils int16FromBytes:[_readBuf subdataWithRange:NSMakeRange(2, 2)]]; NSInteger length = [OGSocketUtils int16FromBytes:[_readBuf subdataWithRange:NSMakeRange(0, 2)]]; NSLog(@"已读取数据:缓冲区长度:%ld, 接收长度:%lu iD:%ld 数据长度:%ld ", (long)_readBuf.length, (unsigned long)[data length], (long)ID, (long)length); NSInteger dataLength = length + 2; if (_readBuf.length >= dataLength) {//如果缓存中的数据 够 一个整包的长度 NSData *msgData = [_readBuf subdataWithRange:NSMakeRange(0, dataLength)]; // 处理消息数据 [self hasReadData:msgData]; // 从缓存中截掉处理完的数据,继续循环 if (_readBuf.length > 0) { _readBuf = [NSMutableData dataWithData:[_readBuf subdataWithRange:NSMakeRange(completeLength, _readBuf.length - completeLength)]]; } } else { // 断包情况,继续读取 [self.clientSocket readDataWithTimeout:kREAD_TIMEOUT buffer:_readBuf bufferOffset:_readBuf.length tag:0]; return; } } }
- (void)hasReadData:(NSData *)data{ // 处理接收到服务器的数据data }以上是我在使用TCP与服务器通信时写的类,可以完成TCP的一个基本开发使用,下一篇会分享一下如何使用Protobuf的消息格式与TCP的联合使用。
相关文章推荐
- tcp粘包,断包问题及处理
- java netty使用DelimiterBasedFrameDecoder处理tcp粘包问题
- (转) 使用epoll的ET模式下的tcp服务处理特点
- c/c++网络编程 对象的传输 以及 TCP粘包处理 解析
- 使用OpenCV开发iOS图像处理应用(To be continued..)
- 使用钩子参与到TCP拥塞事件的处理中
- TCP粘包处理-RingBuf方法
- GCDAsyncSocket类库,IOS下TCP通讯使用心得
- Win32 TCP粘包处理,源代码。
- IOS 构造和使用TableView(基于storyboard) (二)接收和处理Table View事件
- 【iOS界面处理】使用storyboard实现页面跳转,简单的数据传递
- IOS开发之--异常处理--使用try 和 catch 来捕获错误。
- NIO框架之MINA源码解析(四):粘包与断包处理及编码与解码
- 【iOS界面处理】使用storyboard实现页面跳转,简单的数据传递
- 使用钩子参与到TCP拥塞事件的处理中
- TCP通讯处理粘包详解
- TCP粘包问题的处理
- 使用Select I/O模型来实现一个并发处理多个客户端的TCP服务器
- GCDAsyncSocket类库,IOS下TCP通讯使用心得
- 异步Windows Socket包装,包括TCP与UDP,可处理粘包