iOS用GCDAsyncSocket開發Socket通訊
GCDAsyncSocket是CocoaAsyncSocket第三方庫中的其中一個類,本文介紹的就是基於這一個類來做快速的socket通訊開發,而且該庫已經支援IPv4和IPv6
首先,介紹一下CocoaAsyncSocket第三方庫的用途
CocoaAsyncSocket為Mac和iOS提供了易於使用且強大的非同步通訊庫
在Podfile檔案中,只要加上這句話就可以使用了
pod 'CocoaAsyncSocket', '7.4.1' |
簡單的Socket通訊包括了建連、斷開連線、傳送socket業務請求、重連這四個基本功能
下面,我就按照這個四個基本功能來講一下,怎麼來使用CocoaAsyncSocket中GCDAsyncSocket這個類來開發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:(nullableNSError *)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); } } |
這裡我用status來標記socket的連線狀態
那麼socket已經建連了,該怎麼發起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/