YINSocket-基於GCDAsyncSocket的封裝
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)NSIntegerautoConnect; //連線服務端 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工具:warning:一般來說我們使用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 地址
ofollow,noindex">https://github.com/wangyin1/YINSocket