GCDAsynSocket之TCP簡析

GCDAsynSocket是一個開源的基于GCD的異步的socket庫。它支持IPV4和IPV6地址,TLS/SSL協議。同時它支持iOS端和Mac端。本篇主要介紹一下GCDAsynSocket中的TCP用法和實現。

首先通過下面這個方法初始化一個GCDAsynSocket對象。

- (id)initWithDelegate:(id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq;

這里面需要傳入代理的對象,代理隊列以及socket隊列。其中socket隊列不能是一個并發的隊列,不然讀寫就亂了。同時為了防止socket隊列死鎖,通過dispatch_queue_set_specific來為這個隊列添加key值。

dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);

同時這里面初始化了readQueue、writeQueue數組,和一個4K數據緩沖區,后面讀寫的數據都會先經過這個緩沖區。

readQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentRead = nil;
    
writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
currentWrite = nil;
    
preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];

接著通過下面這個方法建立一個tcp連接:

- (BOOL)connectToHost:(NSString *)host
           onPort:(uint16_t)port
     viaInterface:(nullable NSString *)interface
      withTimeout:(NSTimeInterval)timeout
            error:(NSError **)errPtr;

你需要傳入host,port,timeout等信息。其中interface是一個備用的port,絕大多數情況下只需傳nil。它會把里面的操作都放入上面的socketQueue中。
在這方法里面,先做了一個地址檢測。

NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];

同時在里面會做一個超時計時器,超時時間為一開始傳入的時間。

- (void)startConnectTimeout:(NSTimeInterval)timeout
{
    if (timeout >= 0.0)
    {
        connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
    
        __weak GCDAsyncSocket *weakSelf = self;
    
        dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool {
        #pragma clang diagnostic push
        #pragma clang diagnostic warning "-Wimplicit-retain-self"
    
            __strong GCDAsyncSocket *strongSelf = weakSelf;
            if (strongSelf == nil) return_from_block;
        
            [strongSelf doConnectTimeout];
        
        #pragma clang diagnostic pop
        }});
        dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
        dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0);
    
        dispatch_resume(connectTimer);
    }
}

然后嘗試去連接這個地址,中間先會做一些ipv4地址和ipv6地址的轉換,接著會并發的發送connect()連接。一旦連接成功,就會在didConnect方法中開啟讀寫流連接。一旦進入didConnect方法,就會關閉前面的超時計時器,因為已經建立tcp握手連接。另外通過CFStreamCreatePairWithSocket的讀寫流連接也都是放在socketQueue中執行的。接著通過registerForStreamCallbacksIncludingReadWrite注冊讀寫的回調。注冊完之后會把讀寫放在一個cfstreamThread線程中進行執行,并且在cfstreamThread加入了通過計時器激活的runloop,用來不停的循環檢測。

[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
--[self connectSocket:socketFD address:address stateIndex:aStateIndex];
----connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
------[strongSelf didConnect:aStateIndex];
--------createReadAndWriteStream
----------CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream);
------------registerForStreamCallbacksIncludingReadWrite
--------------CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext)
--------------CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext)
----------------startCFStreamThreadIfNeeded
------------------CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
------------------CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);

這樣一個連接就建立了。如果連接建立就會回調到這個代理方法中。你可以在里面讀寫數據。

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;

一旦收到服務端返回的數據,就會回調到這個方法。

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;

參考:CocoaAsyncSocket

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容