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