CocoaAsyncSocket部分功能解析
想要全部了解CocoaAsyncSocket的代码实在是一件太过于繁重的工作=。=毕竟代码量放在那里,尝试着看一下其源码,了解底层socket API的调用,从某个具体的功能开始一个个来看吧
首先,GCDAsyncSocket中定义了几个类,用来包装数据以及缓存等功能
Property
这个类定义了很多属性,先大致看看,后面用到的时候会详细说到。
初始化
下面是初始化一个socket的方法,如果初始化的时候不赋予需要的delegate和delegateQueue,那么在最终调用的方法中也会默认为其赋值。
基本作用都写在注释里了,大部分也就是一些参数初始化,并分配delegate和delegateQueue。需要注意的一点是,socket初始化中有三个整型变量分别是socket4FD,socket6FD和socketUN,它们初始化的时候都设置为SOCKET_NULL。前两者代表socket对IPV4,IPV6类型的地址的解析,后面的UnixSocket是一种从socket框架下发展出来的进程间通信机制,也就是说在同一个主机上进程间通信,使用UNIX Domain Socket是更有效率的。
因为它不需要经过网络协议栈,本质是默认可靠通信的基础下设计的,那么自然在本机上就会比其他方法快很多。
初始化的时候会给socket分配一个socketQueue(如果没有赋值则使用默认生成方法),这个queue是串行队列,由于它负责所有对socket和其内部数据的操作,所以必须让它是串行的,才能保证整个实例运行时的安全性。
后面的比较好理解,一个是read/write的数组,每次有新的读写任务就追加到数组后面,等待何时的时间执行;而preBuffer用来保存操作的数据,比如从服务器端接收来的数据,都是先缓存在这里,然后到一定的时机才取出来和先前读取的数据拼凑在一起。
以上是初始化的部分
Connect
真的不会有人拿iOS来做服务器吧=。=所以这里反正只考虑从客户端的角度来解析各个部分的方法,那么客户端连接的最开始当然就是connect指令了:
上面是所有和connect功能的方法,我们从调用的角度来一步步看
连接服务器的方法是:
|
|
最后实际上是调用这个方法:
基本上就是定义了一个block,然后交给socketQueue中串行执行,具体的关键方法的作用已经注释在代码中,但是我们还是看一看一些方法细节上的实现:
接口检查:
|
|
代码上都有作者的注释,每一段的功能其实很清晰,先判断delegate和delegateQueue的合法性,然后判断是不是在非连接状态(已经connect就没有调用前置判断的意义了),是否支持IPV4,IPV6的位运算,如果interface存在,得到本机的IPV4,IPV6地址并且赋值目标变量,最后清空queue中的读写请求。
这个interface参数并不是一定会有的,也可能传入的是nil,那么后面的一些操作就直接跳过。inrerface的含义是本机的IP+端口号,本来是默认的localhost并且由系统自取一个空闲的端口,但是也可以用户自己设置,不过=。=这样反倒是有可能导致被另歪线程占用的端口被调用,容易出事。一般情况下都是直接选择让系统自己决定调用的端口号的。
不过万一需要自己设定的情况下,后面会调用什么操作呢?会调用一个方法通过这个interface的描述来得到IPV4,IPV6地址,并且以oc能够识别的方式包装起来。注意这里interfaceAddre4Ptr和interfaceAddr6Ptr其实算是返回值,作用和引用参数一样。
|
|
这个方法里主要都是C语言的函数调用,毕竟socket的API这么底层,不能用OC直接操控了。这些结构体重要的成员变量基本也就看名字就明白是啥意思了,这里就不再赘述。简单藐视这个方法就是在各种情况下顺利获取IPV4,IPV6地址。
有一些网络知识可能需要提及一下:
客户端不会出现interface为空的情况,一般是在服务器端将本机地址绑定为0,用来侦听自己各个端口上是否在接受数据;loopback是一个虚拟地址,是路由器的标志;极端情况下如果interface的地址是其他地址,需要和本机地址或者IP进行对比,如果都不相同,其实是啥都不干的。
回过头来接着看connectToHost方法,里面还有一个重要操作:
如果host为localhost或者loopback,则和先前本机地址的方法一样生成IPV4/IPV6地址,并保存在数组中,如果不是,则根据host和port来创建地址。调用的是C的方法
这个很通俗易懂,反正就是对地址进行判断,如果合适就直接连接。唯一需要说明的是
结果我们可以看到,这个也不是建立连接最主要的部分,也只是起到了从IPV4,IPV6地址获取到socketFD的过渡功能。这里创建的socket就是本地客户端自己创建的socket,靠这个进程来发起连接到远程服务器端。那么再看看具体是如何创建本机socket的方法吧:
|
|
可以看到创建socket也是调用的iOS底层的socket接口,很简单易懂,有一点要提一下的就是这个方法最后的SIGPIPE:
简单描述一下就是SIGPIPE就是在一个socket通信中,如果一端向另一个已经关闭的端口发送两次write,第二次的write将会生成SIGPIPE信号,直接结束进程。一般不想直接遇到这种情况就结束进程,那么就得想办法不生成SIGPIPE信号或者忽视SIGPIPE信号。
iOS中可以很方便地处理这个SIGPIPE问题,就是如这个方法最后调用的setsockpot函数,可以直接更改socket的设置,我们看到这里设定了SO_NOSIGPIPE,意味着不会触发SIGPIPE信号量,这样就不会中途结束进程了。具体的socket通信中信号的问题请参考更详细的资料。
总结一下这个方法做了三件事:1.创建socket 2.将socket和本地地址绑定 3.禁用SIGPIPE信号量防止进程中断
到了这里,socket是创建好了,又多个一个新操作:将socket和本机地址进行绑定
|
|
可以看到这个方法也挺简单易懂,就是调用socket自己的bind函数,三个参数分别为socket,绑定地址以及地址大小。有多的一点就是设置了socket的复用。
上面进行了这么多的准备工作,我们拿到了本机地址,通过IPV4/IPV6地址建立了socket,然后把socket和本机地址进行绑定,现在终于能够开始正式的连接了:
|
|
其实也就是调用了socket的connect函数,这个函数式阻塞的,会让其执行的线程一直等待到有结果,所以肯定要使用全局并发队列,然后再新的线程中进行操作,得到结果后回到socketQueue进行后续的操作。
后面其实还有一些关于连接成功后source,stream的初始化操作,但是限于篇幅就不在这里展开说明了。后面说到I/O的内容的时候再来细致讨论吧。
总体而言CocoaAsyncSocket这个框架是在iOS自带的socket API的基础上做了数据包装和通信错误验证的各种操作,其主要的操作基本上都在socketQueue中执行,根据不同情况选择是同步还是异步线程执行。
后续能说的还很多,这里说了这么多只是包装过的socket中connect的部分操作,解读起来已经非常耗时了,不得不佩服大神的技术水平。所以说学习大神的代码真是让人受益匪浅。
参考
1.涂耀辉 iOS即时通讯进阶 - CocoaAsyncSocket源码解析(Connect篇)
2.CocoaAsyncSocket源码