iOS TCP的使用及粘包断包处理
来源:互联网 发布:mysql base64 解码 编辑:程序博客网 时间:2024/06/16 21:53
概要:有关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 和 整个数据的长度 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的联合使用。
阅读全文
2 0
- iOS TCP的使用及粘包断包处理
- ios GCDAsyncSocket(Tcp)的使用
- WinSock TCP keepalive的机理及使用
- WinSock TCP keepalive的机理及使用
- WinSock TCP keepalive的机理及使用
- boost asio处理tcp和udp的不同之处及要点
- TCP的TSO处理
- 使用钩子参与到TCP拥塞事件的处理中
- 使用钩子参与到TCP拥塞事件的处理中
- TCP/UDP简单介绍及JavaSocket的使用
- TCP组包问题及处理方法
- Filter的使用及处理特殊请求
- GridLayout的使用及问题处理
- iOS 设备 检测声音输出设备及耳机麦克风的处理
- ios中处理时间的类及方法详解
- 基于iOS平台的消息处理方法及系统
- iOS中文件下载监控及处理的小结
- ios UILabel 的详细使用及特殊效果
- 什么是事务、事务特性、事务隔离级别、spring事务传播特性
- Android Studio中的自动分包和方法数查看
- Android Studio 项目相关配置杂记
- Centos7.3 安装编译nbd模块
- 使用 TexturePacker 打包图片
- iOS TCP的使用及粘包断包处理
- servlet--第十七天
- MySQL命名规范
- CRM的dev(二)--货币金额的大小限制以及货币格式化
- android中常见单位 dp,sp,px,pt,in,mm,dpi,dip详解
- mac 大型垃圾手动清理 无需安装软件
- Python selenium —— 用chrome的Mobile emulation模拟手机浏览器测试手机网页
- 数据结构之BITMAP
- Oracle分页思想