iOS程序猿iOS学习专题ios实用开发技巧

YINSocket-基于GCDAsyncSocket的封装

2018-10-19  本文已影响92人  _maomao

GCDAsyncSocket是iOS开发主流的socket封装库,YINSocket是自己基于此封装的一个方便使用的类。

我们知道Socket一般用到tcp 和 udp两种 即GCDAsyncSocket和GCDAsyncUdpSocket

tcp面向连接 更加稳定,但是占用资源保持连接

udp面向非连接 稳定性相对弱 优点是通信的时候直接点对点 不需要保持连接

typedef enum : NSUInteger {
    YINSocketEventConnectSucceed,//连接成功 / 有新的客户机连接
    YINSocketEventConnectError,//断开连接 / 有客户机断开连接
    YINSocketEventRecive,//收到消息
} YINSocketEventStatus;


typedef void(^YINSocketEventBlock)(YINSocketEventStatus status,id socket,NSString *message);



@interface YINSocket : NSObject

//客户机主动断开 tcp udp通用
- (void)close;

//---------------tcp 面向连接的socket---------------

//初始化一个客户端 app开发一般只需要socket客户端
+ (instancetype)tcpSocket;

//设置断开自动重连次数 默认为5 如果五次连接都失败,则触发YINSocketEventConnectError
@property(nonatomic,assign)NSInteger   autoConnect;

//连接服务端 host可以是网络解析地址,不需要传port。如果是ip需要传host和port
- (BOOL)tcpConnectToHost:(NSString *)host onPort:(NSString *)port eventBlock:(YINSocketEventBlock)block;

//向服务端发送消息
- (void)tcpSendToService:(NSString *)str;


//初始化一个服务端 一些特殊的p2p需求 要用app作为服务端
+ (instancetype)tcpSocketService;

//已连接自己的所有客户端
@property(nonatomic,strong,readonly)NSMutableArray<GCDAsyncSocket *>   *tcpClients;

//服务端开启一个端口用于监听事件
- (BOOL)tcpAcceptOnPort:(NSString *)port eventBlock:(YINSocketEventBlock)block;

//服务端向客户机发送消息,clinet为nil为广播
- (void)tcpSendDataString:(NSString *)str toClient:(GCDAsyncSocket *)client;

//服务端主动断开客户机 nil为断开所有连接
- (void)tcpCloseTcpClient:(GCDAsyncSocket *)client;






//---------------udp 非连接的socket 也可以连接---------------
+ (instancetype)udpSocket;

//开启端口 可以接收udp消息
- (BOOL)udpBindOnPort:(NSString *)port eventBlock:(YINSocketEventBlock)block;

//向ip发送消息 ip要加端口号
- (void)udpSendDataStr:(NSString *)str toIP:(NSString *)ip;

//连接一个地址。如果连接,则只能发送和接收此地址的消息 一般情况使用udp都是非连接
- (BOOL)udpConnectToHost:(NSString *)host onPort:(NSString *)port;


//
////加入一个组 发送消息时组内所有成员都能收到消息 需要传一个ip不需要端口
//- (BOOL)udpJoinGroup:(NSString *)ip;
@end

下面我们封装tcpSocket工具

作为客户机:

1.初始化一个tcp客户机管理工具

- (instancetype)init
{
    self = [super init];
    if (self) {
        //作为服务机时被链接的客户机数组
        self.tcpClients = @[].mutableCopy;
        //断开自动重连次数
        self.autoConnect = 5;
    }
    return self;
}
//初始化一个tcp客户机管理工具
+ (instancetype)tcpSocket{
    YINSocket *tool = [[YINSocket alloc] init];
    tool.tcpClient = [[GCDAsyncSocket alloc] initWithDelegate:tool delegateQueue:dispatch_get_main_queue()];
    return tool;
}

2.提供连接服务端的方法

//连接
//返回是否链接成功  host可以是网络解析地址,不需要传port。如果是ip需要传host和port YINSocketEventBlock 是监听事件的回调  比如收到服务端下发的消息 链接被断开等
/*
typedef enum : NSUInteger {
    YINSocketEventConnectSucceed,//连接成功 / 有新的客户机连接
    YINSocketEventConnectError,//断开连接 / 有客户机断开连接
    YINSocketEventRecive,//收到消息
} YINSocketEventStatus;
*/

- (BOOL)tcpConnectToHost:(NSString *)host onPort:(NSString *)port eventBlock:(YINSocketEventBlock)block{
    _connectCount = _autoConnect;
    self.ipStr = @"";
    self.portStr = @"";
    if (self.tcpClient) {
        self.ipStr = host;
        self.portStr = port;
        if (block) {
            self.eventBlock = block;
        }
        if ([host containsString:@"http"]) {
            
            return [self.tcpClient connectToUrl:[NSURL URLWithString:host] withTimeout:-1 error:nil];
        }else if (host.length>0&&port.length>0){
            return [self.tcpClient connectToHost:host onPort:port.integerValue error:nil];
        }else{
            return NO;
        }
    }
    return NO;
}

客户机监听事件

#pragma mark - GCDAsyncSocketDelegate
// 链接服务器端成功, 客户端获取地址和端口号
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
    dispatch_source_cancel(_beatTimer);
    if (self.eventBlock) {
        self.eventBlock(YINSocketEventConnectSucceed,sock,@"连接服务端成功");
    }
    [sock readDataWithTimeout:-1 tag:0];
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url{
    dispatch_source_cancel(_beatTimer);
    if (self.eventBlock) {
        self.eventBlock(YINSocketEventConnectSucceed,sock,@"连接服务端成功");
    }
    [sock readDataWithTimeout:-1 tag:0];
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
    
    if (_connectCount>0&&self.tcpClient) {
        [self doAdutoConnect];
    }
    if (self.tcpService) {
        if (self.eventBlock) {
            self.eventBlock(YINSocketEventConnectError, sock, @"有客户机断开连接");
        }
        [self.tcpClients removeObject:sock];
    }
}

// 已经获取到内容
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    if (self.eventBlock) {
        self.eventBlock(YINSocketEventRecive, sock, content);
    }
    if (sock==self.tcpClient) {
        //作为客户端收到消息
    }else{
        //作为服务端收到消息
    }
    [sock readDataWithTimeout:-1 tag:0];
}

3.提供向服务端写入数据的方法 传入string

//向服务端发送消息
- (void)tcpSendToService:(NSString *)str{
  if (self.tcpClient) {
      NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
      [self.tcpClient writeData:data withTimeout:-1 tag:0];
  }
}

4.客户机控制心跳重连

- (dispatch_source_t)beatTimer
{
 if (!_beatTimer) {
     weakify(self)
     _beatTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
     dispatch_source_set_timer(_beatTimer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
     dispatch_source_set_event_handler(_beatTimer, ^{
         _count+=1;
         if ( ![weak_self tcpConnectToHost:weak_self.ipStr onPort:weak_self.portStr eventBlock:weak_self.eventBlock]&&_count==5) {
             if (weak_self.eventBlock) {
                 weak_self.eventBlock(YINSocketEventConnectError, _tcpClient, @"断开与服务器的连接");
             }
         }
     });
 }
 return _beatTimer;
}
- (void)doAdutoConnect{
 _count=0;
 dispatch_resume(self.beatTimer);
}
#pragma mark - GCDAsyncSocketDelegate
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
//如果是作为客户机 并且还有重连次数
 if (_connectCount>0&&self.tcpClient) {
     [self doAdutoConnect];
 }
//如果是作为服务端
 if (self.tcpService) {
     if (self.eventBlock) {
         self.eventBlock(YINSocketEventConnectError, sock, @"有客户机断开连接");
     }
     [self.tcpClients removeObject:sock];
 }
}

5.主动断开连接

- (void)close{
    
    if (self.tcpClient) {
        _connectCount = 0;
        [self.tcpClient disconnect];
    }
  //  if (self.tcpService) {
     //   [self.tcpService disconnect];
    //}
    //if (self.udp) {
      //  [self.udp close];
    //}
}

作为tcp服务机:
1.初始化一个tcp服务机管理工具 服务机可以被多个客户机链接

//初始化一个服务机管理工具
+ (instancetype)tcpSocketService{
    YINSocket *tool = [[YINSocket alloc] init];
    tool.tcpService = [[GCDAsyncSocket alloc] initWithDelegate:tool delegateQueue:dispatch_get_main_queue()];
    return tool;
}

2.服务机开放一个端口用于通信,并且监听事件

- (BOOL)tcpAcceptOnPort:(NSString *)port eventBlock:(YINSocketEventBlock)block{

    if (self.tcpService) {
        if (block) {
            self.eventBlock = block;
        }
        if (port.length>0) {
            return  [self.tcpService acceptOnPort:port.integerValue error:nil];
        }else{
            return NO;
        }
    }
    return NO;
}

//有新的客户端连接自己 其他的监听事件和客户机一样 
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{
    if (self.tcpService) {
        [self.tcpClients addObject:newSocket];
    }
    [newSocket readDataWithTimeout:-1 tag:0];
}

监听到的事件有
YINSocketEventConnectSucceed有新的客户机连接
YINSocketEventConnectError有客户机断开连接
YINSocketEventRecive收到消息

3.保存已连接的客户机 因为针对性发送消息的时候需要用到具体对象 所以这里也体现了tcp面向连接的特点 必须保持连接

//有新的客户端连接自己
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{
    if (self.tcpService) {
        [self.tcpClients addObject:newSocket];
    }
    [newSocket readDataWithTimeout:-1 tag:0];
}

4.向客户机发送消息

//服务端向客户机发送消息,clinet为nil为广播
- (void)tcpSendDataString:(NSString *)str toClient:(GCDAsyncSocket *)client{
    if (self.tcpService) {
        if (client) {
            [client writeData:[str dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
        }else{
            
            [self.tcpClients enumerateObjectsUsingBlock:^(GCDAsyncSocket * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                [obj writeData:[str dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:0];
            }];
        }
    }
}

5.tcp服务机断开连接

//服务端主动断开客户机 nil为断开所有连接
- (void)tcpCloseTcpClient:(GCDAsyncSocket *)client{
    if (client) {
        [client disconnect];
    }else{
        [self.tcpService disconnect];
    }
}

下面我们封装udpSocket工具 ⚠️一般来说我们使用udp是非连接的,socket 但是它也可以连接
udpSocket 不存在tcp服务机和客户机这种一对多的关系。
你可以这样理解,一个对象可以同时作为客户机和服务机
1.初始化一个管理者

//初始化一个udpSocket管理工具
+ (instancetype)udpSocket{
    YINSocket *tool = [[YINSocket alloc] init];
    tool.udp = [[GCDAsyncUdpSocket alloc] initWithDelegate:tool delegateQueue:dispatch_get_main_queue()];
    return tool;
}

2.开放端口用于监听消息接受

//开启端口 可以接收udp消息 
- (BOOL)udpBindOnPort:(NSString *)port eventBlock:(YINSocketEventBlock)block{
    if (block) {
        self.eventBlock = block;
    }
    if (self.udp) {
        return  [self.udp bindToPort:port.integerValue error:nil];
    }else{
        return NO;
    }
}

3.提供发送消息的方法

//向ip发送消息  ip要加端口号
- (void)udpSendDataStr:(NSString *)str toIP:(NSString *)ip{
    if (self.udp) {
        [self.udp sendData:[str dataUsingEncoding:NSUTF8StringEncoding] toHost:[ip componentsSeparatedByString:@":"].firstObject port:[ip componentsSeparatedByString:@":"].lastObject.integerValue withTimeout:-1 tag:0];
    }
}

4.udp也可以连接

//连接一个地址。如果连接,则只能发送和接收此地址的消息 一般情况使用udp都是非连接
- (BOOL)udpConnectToHost:(NSString *)host onPort:(NSString *)port{
    if (self.udp) {
        return NO;
    }
    return  [self.udp connectToHost:host onPort:port.integerValue error:nil];
}

5.关闭端口

- (void)close{
    if (self.udp) {
        [self.udp close];
    }
}

使用方法

pod 'YINSocket'

githud 地址
https://github.com/wangyin1/YINSocket

上一篇 下一篇

猜你喜欢

热点阅读