GCDAsyncSocket是CocoaAsyncSocket第三方库中的其中一个类,本文介绍的就是基于这一个类来做快速的socket通信开发,而且该库已经支持IPv4和IPv6
首先,介绍一下CocoaAsyncSocket第三方库的用途
CocoaAsyncSocket为Mac和iOS提供了易于使用且强大的异步通信库
在Podfile文件中,只要加上这句话就可以使用了
pod 'CocoaAsyncSocket'
下面,我就按照这个四个基本功能来讲一下,怎么来使用CocoaAsyncSocket中GCDAsyncSocket这个类来开发Socket通信
简单的Socket通信包括了建连、断开连接、发送socket业务请求、重连这四个基本功能
首先,Socket在第一步时,需要建连才能开始通信
在GCDAsyncSocket中提供了四种初始化的方法
/**
* GCDAsyncSocket uses the standard delegate paradigm,
* but executes all delegate callbacks on a given delegate dispatch queue.
* This allows for maximum concurrency, while at the same time providing easy thread safety.
*
* You MUST set a delegate AND delegate dispatch queue before attempting to
* use the socket, or you will get an error.
*
* The socket queue is optional.
* If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue.
* If you choose to provide a socket queue, the socket queue must not be a concurrent queue.
* If you choose to provide a socket queue, and the socket queue has a configured target queue,
* then please see the discussion for the method markSocketQueueTargetQueue.
*
* The delegate queue and socket queue can optionally be the same.
**/
- (instancetype)init;
- (instancetype)initWithSocketQueue:(nullabledispatch_queue_t)sq;
- (instancetype)initWithDelegate:(nullableid<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullabledispatch_queue_t)dq;
- (instancetype)initWithDelegate:(nullableid<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullabledispatch_queue_t)dq socketQueue:(nullabledispatch_queue_t)sq;
#pragma mark Configuration
@property (atomic,weak, readwrite,nullable) id<GCDAsyncSocketDelegate> delegate;
#if OS_OBJECT_USE_OBJC
@property (atomic,strong, readwrite,nullable) dispatch_queue_t delegateQueue;
#else
@property (atomic,assign, readwrite,nullable) dispatch_queue_t delegateQueue;
#endif
sq是socket的线程,这个是可选的设置,如果你写null,GCDAsyncSocket内部会帮你创建一个它自己的socket线程,如果你要自己提供一个socket线程的话,千万不要提供一个并发线程,在频繁socket通信过程中,可能会阻塞掉,个人建议是不用创建
aDelegate就是socket的代理
dq是delegate的线程
必须要需要设置socket的代理以及代理的线程,否则socket的回调你压根儿就不知道了,
比如:
self.socket = [[GCDAsyncSocketalloc]initWithDelegate:selfdelegateQueue:dispatch_get_main_queue()];
接着,在设置代理之后,你需要尝试连接到相应的地址来确定你的socket是否能连通了
/**
* Connects to the given host and port with an optional timeout.
*
* This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface.
**/
- (BOOL)connectToHost:(NSString *)host
onPort:(uint16_t)port
withTimeout:(NSTimeInterval)timeout
error:(NSError **)errPtr;
host是主机地址,port是端口号
如果建连成功之后,会收到socket成功的回调,在成功里面你可以做你需要做的一些事情,我这边的话,是做了心跳的处理
/**
* Called when a socket connects and is ready for reading and writing.
* The host parameter will be an IP address, not a DNS name.
**/
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
如果建连失败了,会收到失败的回调,我这边在失败里面做了重连的操作
/**
* Called when a socket disconnects with or without error.
*
* If you call the disconnect method, and the socket wasn't already disconnected,
* then an invocation of this delegate method will be enqueued on the delegateQueue
* before the disconnect method returns.
*
* Note: If the GCDAsyncSocket instance is deallocated while it is still connected,
* and the delegate is not also deallocated, then this method will be invoked,
* but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.)
* This is a generally rare, but is possible if one writes code like this:
*
* asyncSocket = nil; // I'm implicitly disconnecting the socket
*
* In this case it may preferrable to nil the delegate beforehand, like this:
*
* asyncSocket.delegate = nil; // Don't invoke my delegate method
* asyncSocket = nil; // I'm implicitly disconnecting the socket
*
* Of course, this depends on how your state machine is configured.
**/
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullableNSError *)err;
重连操作其实比较简单,只需要再调用一次建连请求,我这边设置的重连规则是重连次数为5次,每次的时间间隔为2的n次方,超过次数之后,就不再去重连了
(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err {
self.status= -1;
if(self.reconnection_time>=0 && self.reconnection_time <= kMaxReconnection_time) {
[self.timer invalidate];
self.timer=nil;
int time =pow(2,self.reconnection_time);
self.timer= [NSTimer scheduledTimerWithTimeInterval:time target:selfselector:@selector(reconnection) userInfo:nil repeats:NO];
self.reconnection_time++;
NSLog(@"socket did reconnection,after %ds try again",time);
} else {
self.reconnection_time=0;
NSLog(@"socketDidDisconnect:%p withError: %@", sock, err);
}
}
那么socket已经建连了,该怎么发起socket通信呢? 这里我用status来标记socket的连接状态
你需要和后端开发人员商定好socket协议格式,比如:
[NSString stringWithFormat:@"{\"version\":%d,\"reqType\":%d,\"body\":\"%@\"}\r\n",PROTOCOL_VERSION,reqType,reqBody];
中间应该大家都能看得懂,那为什么后面需要加上\r\n呢?
这个\r\n是socket消息的边界符,是为了防止发生消息黏连,没有\r\n的话,可能由于某种原因,后端会收到两条socket请求,但是后端不知道怎么拆分这两个请求
同理,在收到socket请求回调时,也会根据这个边界符去拆分
那为什么要用\r\n呢?
而且GCDAsyncSocket不支持自定义边界符,它提供了四种边界符供你使用\r\n、\r、\n、空字符串
在拼装好socket请求之后,你需要调用GCDAsyncSocket的写方法,来发送请求,然后在写完成之后你会收到写的回调
[self.socket writeData:requestData withTimeout:-1 tag:0];
这个是写的回调Timeout是超时时间,这个根据实际的需要去设置
/**
* Called when a socket has completed writing the requested data. Not called if there is an error.
**/
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag;
在写之后,需要再调用读方法,这样才能收到你发出请求后从服务器那边收到的数据
[self.socket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:10 maxLength:50000 tag:0];
在读回调里面,你可以根据不同业务来执行不同的操作[GCDAsyncSocket CRLFData]这里是设置边界符,maxLength是设置你收到的请求数据内容的最大值
/**
* Called when a socket has completed reading the requested data into memory.
* Not called if there is an error.
**/
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;
最后一个则是断开连接,这个只需要调用
[self.socket disconnect];
最简单基础的Socket通信,已经大致能完成了~
转自:http://zeeyang.com/2016/01/17/GCDAsyncSocket-socket/