iOS 基于GCDAsyncSocket 封装 TCP(重连,断包等)

1.    .h文件

#import  <Foundation/Foundation.h>

#import <GCDAsyncSocket.h>

@interfaceSingleTcpCase :NSObject

@property(strong,nonatomic)GCDAsyncSocket *asyncTcpSocket;

/**  单利初始化 */

+(instancetype)sharedTcpManager;

/**  建立连接 */

-(void)connectServer;

/**  关闭连接 */

-(void)disConnectServer;

/**  发送数据给服务器 */

-(void)sendDataToServer:(id)data;


2. .m文件

#import "SingleTcpCase.h"

#define WSELF              __weak typeof(self) wself = self;

/** 主线程异步队列 */

#define Dispatch_main_async_safe(block)\

if ([NSThread isMainThread]) {\

block();\

} else {\

dispatch_async(dispatch_get_main_queue(), block);\

}

@interface SingleTcpCase() <GCDAsyncSocketDelegate>

/** 心跳计时器 */

@property (nonatomic, strong) NSTimer *heartBeatTimer;

/** 没有网络的时候检测网络定时器 */

@property(nonatomic,strong)NSTimer*netWorkTestingTimer;

/** 存储要发送给服务端的数据 */

@property (nonatomic, strong) NSMutableArray *sendDataArray;

/** 数据请求队列(串行队列) */

@property (nonatomic, strong) dispatch_queue_t queue;

/** 重连时间 */

@property (nonatomic, assign) NSTimeInterval reConnectTime;

/** 用于判断是否主动关闭长连接,如果是主动断开连接,连接失败的代理中,就不用执行 重新连接方法 */

@property (nonatomic, assign) BOOL isActivelyClose;

/** 缓存区域 */

@property (nonatomic, strong) NSMutableData *readBuf;

@end

@implementation SingleTcpCase

#pragma mark - 🔥🔥🔥🔥🔥🔥🔥🔥

#pragma mark ----单利------

/** 单利 */

+ (instancetype)sharedTcpManager

{

    staticSingleTcpCase*_instace =nil;

    staticdispatch_once_tonceToken;

    dispatch_once(&onceToken,^{

        _instace = [[selfalloc]init];

        DLogInfo(@"TCP 单利实例化!");

    });

    return_instace;

}

#pragma mark ----初始化------

/** 初始化 */

- (instancetype)init

{

    self= [superinit];

    if(self)

    {

        self.reConnectTime = 0;

        self.isActivelyClose = NO;

        self.queue = dispatch_queue_create("BF",NULL);

        self.sendDataArray = [[NSMutableArray alloc] init];

        self.mRecvPacket = [JHreceivePacket sharedManager];

        self.deviceListArr = [[NSMutableArray alloc] init];

        DLogInfo(@"初始化!");

    }

    return self;

}

#pragma mark - 🔥🔥🔥🔥🔥🔥🔥🔥

#pragma mark --- 心跳计时器处理 -----

/** 心跳计时器初始化 */

- (void)initHeartBeat

{

    //心跳没有被关闭

    if(self.heartBeatTimer)

    {

        return;

    }

    [self destoryHeartBeat];

    //连接成功  先发一次 心跳数据 在打开计时器

    [[JHSocketData sharedManager] heartbeat];

    [self.asyncTcpSocket writeData:[[JHSocketData sharedManager]getWillSendFrame] withTimeout:30 tag:0];


    WSELF

    Dispatch_main_async_safe(^{

        wself.heartBeatTimer  = [NSTimer timerWithTimeInterval:30target:wself selector:@selector(senderheartBeat) userInfo:nilrepeats:true];

        [[NSRunLoop currentRunLoop]addTimer:wself.heartBeatTimer forMode:NSRunLoopCommonModes];

    });

}

#pragma mark --- 取消心跳 -----

/** 取消心跳 */

- (void)destoryHeartBeat

{

    WSELF

    Dispatch_main_async_safe(^{

        if(wself.heartBeatTimer)

        {

            [wself.heartBeatTimer invalidate];

            wself.heartBeatTimer =nil;

        }

    });

}

#pragma mark --- 没有网络的时候开始定时 -- 用于网络检测 -----

/** 没有网络,进行网络监测 */

- (void)noNetWorkStartTestingTimer

{

    WSELF

    Dispatch_main_async_safe(^{

        wself.netWorkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0target:wself selector:@selector(noNetWorkStartTesting) userInfo:nilrepeats:YES];

        [[NSRunLoop currentRunLoop] addTimer:wself.netWorkTestingTimer forMode:NSDefaultRunLoopMode];

    });

}

#pragma mark --- 取消网络监测 ----

/** 取消网络监测 */

- (void)destoryNetWorkStartTesting

{

    WSELF

    Dispatch_main_async_safe(^{

        if(wself.netWorkTestingTimer)

        {

            [wself.netWorkTestingTimer invalidate];

            wself.netWorkTestingTimer =nil;

        }

    });

}

#pragma mark - 🔥🔥🔥🔥🔥🔥🔥🔥

#pragma mark --- 发送心跳 ---

/** 发送心跳 */

- (void)senderheartBeat

{

  //  和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小


     [self.asyncTcpSocket writeData:你的数据 withTimeout:30 tag:0];


//    WSELF;

//    Dispatch_main_async_safe(^{

//

//    });

}

#pragma mark --- 定时检查网络 ---

/** 定时检查网络 */

- (void)noNetWorkStartTesting

{

    //有网络

    if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable)

    {

        //关闭网络检测定时器

        [self destoryNetWorkStartTesting];

        //开始重连

        [self reConnectServer];

    }

}


#pragma mark - 🔥🔥🔥🔥🔥🔥🔥🔥

#pragma mark ----建立连接------

/**  建立连接 */

-(void)connectServer{


    self.isActivelyClose = NO;

    NSError*error =nil;

    _asyncTcpSocket= [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

//你的ip地址和端口号

    [_asyncTcpSocket connectToHost:@"255.255.253.108" onPort:8989 withTimeout:60 error:&error];

    if(error) {


        DLogInfo(@"TCP连接错误:%@", error);


    }

    [self.asyncTcpSocket readDataWithTimeout:-1 tag:0];

}

#pragma mark --- 重连服务器 ---

/** 重连服务器 */

- (void)reConnectServer{


    if (self.asyncTcpSocket.isConnected) {


        return;

    }


    if (self.reConnectTime > 1024) {

        self.reConnectTime = 0;

        return;

    }


    WSELF;

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{


        if(wself.asyncTcpSocket.isConnected)

        {

            return;

        }


        [wselfconnectServer];

        DLogInfo(@"正在重连");


        if(wself.reConnectTime==0)  //重连时间2的指数级增长

        {

            wself.reConnectTime=2;

        }

        else

        {

            wself.reConnectTime*=2;

        }

    });

    DLogInfo(@"重连服务器!");

}

#pragma mark ----关闭连接------

/** 关闭连接 */

- (void)disConnectServer {

    NSLog(@"dealloc");

    // 关闭套接字

    self.isActivelyClose = YES;

    [self.asyncTcpSocket disconnect];

    //关闭心跳定时器

    [self destoryHeartBeat];

    //关闭网络检测定时器

    [self destoryNetWorkStartTesting];

    self.asyncTcpSocket = nil;

}

#pragma mark ---代理 连接成功 ---

//连接成功

-(void)socket:(GCDAsyncSocket*)sock didConnectToHost:(NSString*)host port:(uint16_t)port

{

    DLogInfo(@"TCP连接成功!");

//    [self.asyncTcpSocket readDataWithTimeout:-1 tag:index];

//      [self.asyncTcpSocket readDataWithTimeout:-1 tag:0];


    // 存储接收数据的缓存区,处理数据的粘包和断包

    self.readBuf =[[NSMutableData alloc]init];

    [self initHeartBeat]; //开启心跳

    //如果有尚未发送的数据,继续向服务端发送数据

    if([self.sendDataArraycount] >0){

        [self sendeDataToServer];

    }


}

#pragma mark ---代理 连接失败 ---

//连接失败 进行重连操作

-(void)socketDidDisconnect:(GCDAsyncSocket*)sock withError:(NSError*)err

{

    //用户主动断开连接,就不去进行重连

    if(self.isActivelyClose)

    {

        return;

    }

    [self destoryHeartBeat]; //断开连接时销毁心跳

    //判断网络环境

    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) //没有网络

    {

        [self noNetWorkStartTestingTimer];//开启网络检测定时器

    }

    else//有网络

    {

        [self reConnectServer];//连接失败就重连

    }


    DLogInfo(@"TCP连接失败!");

}

#pragma mark --- 消息发送成功 ---

-(void)socket:(GCDAsyncSocket*)sock didWriteDataWithTag:(long)tag

{

    NSLog(@"TCP发送成功");

}

#pragma mark - 🔥🔥🔥🔥🔥🔥🔥🔥

#pragma mark --- 发送数据 ---

-(void)sendDataToServer:(id)data{



    [self.sendDataArrayaddObject:data];

    [self sendeDataToServer];

//      [self.asyncTcpSocket  writeData:data withTimeout:60 tag:0];


}

#pragma mark --- 发送数据给服务器 详细处理 ---

/** 发送数据的详细处理 */

-(void)sendeDataToServer{

    WSELF

    //把数据放到一个请求队列中

    dispatch_async(self.queue, ^{

    //网络判断  没有网络的情况

    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {


        //开启网络检测定时器

        [wselfnoNetWorkStartTestingTimer];

    }else//有网络情况

    {

        //判断对象是否存在

        if(wself.asyncTcpSocket!=nil) {

            //判断TCP是否处于连接状态

            if(wself.asyncTcpSocket.isConnected) {

                //判断数据  是否存在

                if(wself.sendDataArray.count>0)

                {


                    idsendData = wself.sendDataArray[0];

                    DLogWarn(@"TCP发送出去的数据: %@",sendData);

                    [self.asyncTcpSocket  writeData:sendDatawithTimeout:30tag:0];

                    [wself.sendDataArrayremoveObjectAtIndex:0];


                    if([wself.sendDataArraycount] >0)

                    {

                        [wselfsendeDataToServer];

                    }

                }


            }else{

                //TCP 处于断开状态

                //主动去重连服务器  连接成功后 继续发送数据

                  [wselfreConnectServer];

            }

        }else{

              [wselfreConnectServer];

        }

    }

    });

}

#pragma mark - 🔥🔥🔥🔥🔥🔥🔥🔥

#pragma mark --- 接收的数据 ---

-(void)socket:(GCDAsyncSocket*)sock didReadData:(NSData*)data withTag:(long)tag

{

    //      sleep(1);

    //此方法处理数据的黏包或者断包

    myLOG(@"原始数据%@",data);

    //断包处理

    [self didReadData:data];

//    [self disposeBufferData:data];

    [self.asyncTcpSocket readDataWithTimeout:-1 tag:0];

}

#pragma mark - 🔥🔥🔥🔥🔥🔥🔥🔥

#pragma mark --- 黏包 断包处理 ---

-(void) didReadData:(NSData*)data {

    //断包处理 要根据 你的 数据的 长度标识位的数据 来判断 读到什么地方 才是你完整的数据。根据协议去走

    _readBuf = [NSMutableData dataWithData:data];


    // 取出4-8位保存的数据长度,计算数据包长度

    while(_readBuf.length>=5) {// 头数据为5个字节

        // 得到数据的ID 和 整个数据的长度

        NSData *dataLength = [_readBuf subdataWithRange:NSMakeRange(3, 2)];

        Byte*ByteLength = (Byte*)[dataLengthbytes];


        intheadLen = (ByteLength[0] &0x00ff) + ((ByteLength[1] &0x00ff) <<8);

        NSIntegerlengthInteger =0;

        lengthInteger = (NSInteger)headLen;

        NSIntegercomplateDataLength = lengthInteger +6;//算出一个包完整的长度(内容长度+头长度)

        myLOG(@"已读取数据:缓冲区长度:%ld, 接收长度:%lu  数据长度:%ld ", (long)_readBuf.length, (unsignedlong)[data length], complateDataLength);

//        NSInteger dataLength = length + 2;

        if(_readBuf.length>= complateDataLength) {//如果缓存中的数据 够  一个整包的长度


            NSData*msgData = [_readBufsubdataWithRange:NSMakeRange(0, complateDataLength)];

            // 处理消息数据

//            myLOG(@"得到完整包数据:%@",msgData);

            // 从缓存中截掉处理完的数据,继续循环

            _readBuf= [NSMutableDatadataWithData:[_readBufsubdataWithRange:NSMakeRange(complateDataLength,_readBuf.length- complateDataLength)]];

//            [self.asyncTcpSocket readDataWithTimeout:-1 tag:0];


        }else{// 断包情况,继续读取

//              [self.asyncTcpSocket readDataWithTimeout:-1 tag:0];

//            [sock readDataWithTimeout:-1 buffer:_readBuf bufferOffset:_readBuf.length tag:0];//继续读取数据

            [self.asyncTcpSocket readDataWithTimeout:-1 buffer:_readBuf bufferOffset:_readBuf.length tag:0];

            return;

        }

        [self.asyncTcpSocket readDataWithTimeout:-1 buffer:_readBuf bufferOffset:_readBuf.length tag:0];//继续读取数据

    }





}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容