CocoaAsyncSocket 实现时用到的技术

作者: ampire_dan | 来源:发表于2018-03-29 14:28 被阅读324次

    前言

    最近在阅读 CocoaAsyncSocket 的源码,整理里一下其中用到的一些技术点。

    GCD 相关

    目标队列(Target Queue)

    概念

    目标队列的基本概念是:你创建的所有队列,如果没有指定其目标队列,那么它的目标队列是优先级为 DISPATCH_QUEUE_PRIORITY_DEFAULT 的全局并发队列。每次在你队列中的一个 block 开始执行的时候,GCD 会重新将其放入目标队列执行。详细介绍可以查看引用1。

    目标队列可能引起死锁

    这里说一下使用目标队列可能导致的死锁问题:

    比如说有两个队列:

    dispatch_queue_t queueOne;
    dispatch_queue_t targetQueueOne;
    queueOne.targetQueue = targetQueueOne;
    

    那么所有在 queue 上的操作最终会在 targetQueue 上执行。一切看起来都很好,现在有设想下面这样的函数:

     - (BOOL)doSomething
      {
          __block BOOL result = NO;
          dispatch_block_t block = ^{
              result = [self someInternalMethodToBeRunOnlyOnQueueOne];
          }
          if (is_executing_on_queue(queueOne))
              block();
          else
              dispatch_sync(queueOne, block);
          
          return result;
      }
    

    如果你在 targetQueueOne 上调用这个方法会怎样?答案是死锁。这是因为 GCD 的 API 没有提供一个机制去发现队列的目标队列,所以我们不知道 queueOne 的目标队列。(死锁的原因是在当前串型队列上又同步派发了一个操作,导致两者相互等待)

    目标队列死锁问题解决

    一句话解释:使用 dispatch_queue_set_specific()dispatch_get_specific()

    先给 queueOne 设置 sepcific

    IsOnQueueOneOrTargetQueueKey = &IsOnQueueOneOrTargetQueueKey;
    
    void *nonNullUnusedPointer = (__bridge void *)self;
    dispatch_queue_set_specific(queue, IsOnQueueOneOrTargetQueueKey, nonNullUnusedPointer, NULL);
    

    每次要判断是否是在当前队列或者目标队列的时候如下判断:

    if (dispatch_get_specific(IsOnQueueOneOrTargetQueueKey)) {
        block();
    } else {
        dispatch_sync(queueOne, block);
    }
    
    

    GCD Dispatch 源

    概念

    当要处理系统底层相关任务的时候,我们必须准备好要等待一段时间。当你的应用程序陷入系统内核或者其他系统层面的时候,比起在进程内部的函数调用,我们要面临切换上下文导致的巨大时间开销。结果就是,许多系统库会提供异步接口使你的代码提交一个请求给系统,然后继续自己的工作,当系统完成你的请求,在回调你的请求结果处理 block。GCD 的 dispatch source 就是这样一个基本数据类型。具体介绍可以查看官方文档,引用2。

    使用 Dispatch 源来做一个定时器

    在 iOS 中想要实现一个定时器可以使用系统原生的类 NSTimerNSTimer 的问题是它的设计模式是 target-action 模式。也就是要在当前类中定义一个方法给定时器使用。比起多定义一个方法我们更喜欢用 block。但是 NSTimer 的 API 在 iOS 11 才支持回调的方式。有很多方式来自定义 NSTimer 使用回调的方式。这里不做介绍,可以查看引用3。

    实现一个回调的定时器的一种方式是使用 Dispatch 源,代码如下所示:

    
    dispatch_source_t CreateDispatchTimer(uint64_t interval,
                  uint64_t leeway,
                  dispatch_queue_t queue,
                  dispatch_block_t block)
    {
       dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
                                                         0, 0, queue);
       if (timer)
       {
          dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
          dispatch_source_set_event_handler(timer, block);
          dispatch_resume(timer);
       }
       return timer;
    }
     
    void MyCreateTimer()
    {
       dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC,
                                   1ull * NSEC_PER_SEC,
                                   dispatch_get_main_queue(),
                                   ^{ MyPeriodicTask(); });
     
       // Store it somewhere for later use.
        if (aTimer)
        {
            MyStoreTimer(aTimer);
        }
    }
    

    使用 Dispatch 源来处理 Socket 相关操作

    无论是服务器端还是客户端socket 都需要某种机制使得其能够和对方进行持续的通信。通常的解决办法是进行一个永久循环,遇到某种条件时再终止循环。这种方式缺陷较多,一是不够“优雅”,二是无限循环非常浪费 CPU 时间。使用 Dispatch 源来做处理会更好。

    CocoaAsyncSocket 这个框架的核心思想之一就是使用源来处理 Socket 的读和写。下面代码来自 CocoaAsyncSocket:

    accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
    int socketFD = socket4FD;
    dispatch_source_t acceptSource = accept4Source;
    __weak GCDAsyncSocket *weakSelf = self;
    dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool {
        __strong GCDAsyncSocket *strongSelf = weakSelf;
        if (strongSelf == nil) return_from_block;
        unsigned long i = 0;
        unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
        while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
    }});
    dispatch_source_set_cancel_handler(accept4Source, ^{
        close(socketFD);
    });
    
    dispatch_resume(accept4Source);
    

    使用 dispatch_source_create 来创建源,参数 DISPATCH_SOURCE_TYPE_READ 指的是这是一个读类型的源,读事件来自与 socket4FD,事件回调发生在 socketQueue 上。

    dispatch_source_set_event_handler 指定了每次事件发生时的回调,
    dispatch_source_set_cancel_handler 指定了取消一个源的操作,取消一个源可以使用 dispatch_source_cancel 完成。默认情况下源是不会开始执行的,所以要用 dispatch_resume 显示的启动一个源。

    dispatch 源还有很多作用,比如监视一个文件夹中的文件变化等。详细介绍可以看官方文档或者引用4

    SSL/TLS 握手

    我们知道每一个 TCP 连接都会进行3次握手,然后才会开始通信,具体原理可以查看引用5。HTTP 连接是建立在 TCP 连接的基础之上的。HTTPS 则更进一步,其连接是建立在 SSL/TLS 连接之上的。而 SSL/TLS 连接又是建立在 TCP 3次握手之后。这里不描述 SSL/TLS 的握手过程,详细可以查看参考资料6。

    如何在 iOS 或者 macOS 系统上实现 SSL/TLS 握手呢?先说一下为什么要自己实现 SSL/TLS 握手,毕竟我们使用 HTTPS 的时候好像并没有处理这个?

    了解客户端证书绑定的会知道,要做客户端证书绑定我们需要实现 NSURLSessionDelegate 的 URLSession:didReceiveChallenge:completionHandler 协议。其实在这一步发生的时候,NSURLSession 就已经正在为我们进行 SSL/TLS 握手,只是由于我们实现了这个协议,所以 NSURLSession 需要我们告诉它是否要信任这次握手,默认情况下 NSURLSession 有自己的逻辑来决定是否信任。NSURLSession 为我们隐藏了很多其他握手细节。

    当我们想自定义 SSL/TLS 的握手细节的时候就需要自定义了。(SSL/TLS 握手中每一个步骤请看资料6)。

    iOS 这边实现 SSL/TLS 握手 有两种方式

    1. 使用苹果提供的 SecureTransport 框架
    2. 使用 CFStream 相关接口

    使用 SecureTransport 的优势是可以结合 dispatch 源,并且性能高,可控性强,但是 SecureTransport 没有开源。

    使用 CFStream 的优势是使用 dispatch source 苹果没有一种机制告诉我们当前是否要进入后台或者正在后台。CFStream 的 API 就可以,通过设置 steam 的 kCFStreamNetworkServiceType 为 kCFStreamNetworkServiceTypeVoIP 即可。

    注意 CocoaAsyncSocket 中貌似无法用 CFSteam 来自定义证书认证。

    具体如何使用 CFStream 请看资料 CFSocketStream

    具体如何使用 Secure Transport 请看资料 Secure Transport

    SOCKET

    CocoaAsyncSocket 本来就是对原生的 Socket 的封装,所以要看懂源码需要了解原生的 Socket 的写法。主要分成两个部分:

    1. 服务器端 Socket
    2. 客户端 Socket

    同时 Socket 本身还有很多细节内容需要了解。建议在阅读或者使用 CocoaAsyncSocket 之前了解这方面的知识。

    英文资料可以看: Introduction to Sockets Programming in C using TCP/IP

    中文资料可以看: Socket

    引用

    1. 【翻译】GCD Target Queues
    2. Dispatch Sources 官方文档
    3. NSTimer-Blocks
    4. 细说GCD(Grand Central Dispatch)如何用
    5. 通俗大白话来理解TCP协议的三次握手和四次分手
    6. SSL/TLS协议运行机制的概述

    相关文章

      网友评论

      • 严青_:能详细的展开就更好了,赞一个:heart_eyes:

      本文标题:CocoaAsyncSocket 实现时用到的技术

      本文链接:https://www.haomeiwen.com/subject/zemmcftx.html