Socket学习总结系列(二) -- CocoaAsyncSocket

这是系列的第二篇

这是这个系列文章的第二篇,要是没有看第一篇的还是建议看看第一篇,以为这个是接着第一篇梳理的

先大概的总结一下在上篇的文章中说的些内容:

1、 整理了一下做IM我们有那些途径,以及我们怎样选择最适合自己的

2、在做IM的时候协议你又该怎样选择,以及这些协议之间一些的对比等等

3、接下来梳理了一下Socket的我们该怎样理解,它的心跳,pingpong,重连机制等等

4、利用demo整理出来了原生Socket的简单的连接以及接收/发送消息。

这篇我们梳理那些

1、 对CocoaAsyncSocke这个三方的理解以及一些自己的看法 

2、分析CocoaAsyncSocket的集成,源码的一些解析

3、利用CocoaAsyncSocket实现Socket的连接,接收/发送 消息,以及总结一下这整个过程

认识一下CocoaAsyncSocke

这里我们先认识一下CocoaAsyncSocke:

CocoaAsyncSocke是谷歌的开发者,基于BSD-Socket写的一个IM框架,它给Mac和iOS提供了易于使用的、强大的异步套接字库,向上封装出简单易用OC接口。省去了我们面向Socket以及数据流Stream等繁琐复杂的编程,下面是我们导入的整个框架的

       这里大概说一下:     GCDAsyncSocket         是基于TCP协议写的

                                  GCDAsyncUdpSocket    是基于UDP协议写的

       以前框架还有一个runloop版,不过因为功能相似等其他的原因,后续版本就废弃了,现在仅有这个GCD版本。

CocoaAsyncSocket集成

认识一下CocoaAsyncSocket的源码 (建议先文章最后下载Demo)

     下面我们开始整理分析CocoaAsyncSocket的GCDAsyncSocket部分的源码,这部分代码量在九千多行,我们这一次按照它.h的可以给外面调用的方法开始认识它的源码,具体的每一行的注释你可以下载Demo去看Demo中GCDAsyncSocket部分的源码,上面给了很详细的注释,然后我们这里就是按照.h的方法去总结,Demo中的很多的注释我也是看着大神学的,大家要有什么疑问或者不明白的地方可以找我QQ联系我:

一:初始化

      我把初始化这一部分的源码又划分成了三部分,这三部分我们分开来说:

      第一部分: 第一部部分的三个方法你在.h文件当中也能看得到,三个逐层调用的初始化方法,注意这种逐层调用写法的好处就是灵活的进行初始化,这个你要看过AFNetworking的源码的话你也可以看到同样的写法,我们重点不放在这一块,我相信这一块的东西你要仔细点看起来是没有什么问题的。

      第二部分:这一部分的内容就是代理和线程的设置,说实话也没什么好说的,重点还是下面的连接部分,这个你也在Demo中配合注释去理解理解。

      第三部分:这一部分比起前面的两小部分稍微就需要我们注意点了,这部分的内容在下面的重点的连接部分用的比较多,你仔细看这部分方法的名字也可以理解,都是一些设置、判断IPV4和IPV6是否可用的方法。至于最下面userData的复制方法,这点我觉得似乎可以暂时忽略。

二:Accept  Socket      

      这一部分的代码主要是在服务端用得到:

      但按照我自己的理解,很少用OC来写服务端的代码吧!当然这也许也只是我自己见的少而已吧,我是真的不怎么知道用OC来写服务端,不过这部分的代码能能帮助我们理解在整个过程中服务端的Accept到底是怎么一个流程:

三:Connect  

      连接这部分的代码可以说是这整个三方的核心内容,先看看我们划分的它的方法架构:

      接下来把这五部分我们说说:

      第一部分: 前置判断,这一部分的内容是在调用了连接方法之后在连接的方法里面调用的,我们在这里先不说它的调用时具体在连接方法哪里调用,怎么调用的我们先看看这个前置检查到底检查了什么,看看里面的内容,等到我们看到调用它的地方的时候我们再谈。

      注意:下面的代码不是完整的,完整版本看Demo,具体判断之后返回YES还是NO看具体的情况而定,我们这里是为了不让无用代码占篇幅,我们的注意点放在它是通过哪些条件作了前置的判断,可以看代码中的注释:

{
        // 先断言,如果当前的queue不是初始化quueue,直接报错
        // dispatch_get_specific 这个和dispatch_set_specific的用法具体的可以百度
    NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
        //无代理
    if (delegate == nil) // Must have delegate set{
    }
    //没有代理queue
    if (delegateQueue == NULL) // Must have delegate queue set{
    }
        //当前不是非连接状态
    if (![self isDisconnected]) // Must be disconnected{
    }
        // 判断是否支持IPV4 IPV6  &按位“与”运算,因为枚举是用  左位移<<运算定义的,所以可以用来判断 config包不包含某个枚举。因为一个值可能包含好几个枚举值,所以这时候不能用==来判断,只能用&来判断
        // 注意这个解释:kIPv4DisabledIf set, IPv4 is disabled,要是包含就说明IPV4是不能使用的,也就是要是返回YES,说明IPV4不能使用
         
    BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
    BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
     
        //是否都不支持
    if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled{
    }
        //  如果有interface,本机地址
        //  interface这个参数这个就是我们设置的本机IP+端口号
        /*
            一般情况不需要去设置这个参数,默认的为localhost(127.0.0.1)本机地址。而端口号会在本机中取一个空闲可用的端口。
            而我们一旦设置了这个参数,就会强制本地IP和端口为我们指定的
            这里端口号如果我们写死,万一被其他进程给占用,讲导致无法连接成功
         */
    if (interface)
    {
        NSMutableData * interface4 = nil;
        NSMutableData * interface6 = nil;
         
                //得到本机的IPV4 IPV6地址
        [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0];
         
                //如果两者都为nil
        if ((interface4 == nil) && (interface6 == nil)){
        }
        //IPV4不能正常使用且本机的IPV6为nil
        if (isIPv4Disabled && (interface6 == nil)){
        }
                //IPV6不能正常使用且本机的IPV4为nil
        if (isIPv6Disabled && (interface4 == nil)){
        }
        //如果都没问题,则赋值
        connectInterface4 = interface4;
        connectInterface6 = interface6;
    }
     
    // Clear queues (spurious read/write requests post disconnect)
        // 读写Queue清除
        // 走到这里则前面的全没有返回值,在这里就返回YES,
    [readQueue  removeAllObjects];
    [writeQueue removeAllObjects];
     
        //能走到这里的条件  有delegate   delegateQueue 包含IPV4或者IPV6
    return YES;
}

     注意:还有一个前置检测方法,我们在这里就不粘贴代码了。你看了第一个你也能看的懂第二个的啊判断条件,至于为什么会有两个前置检测的方法,怎么调用这个我们接着看。

       第二部分:逐层调用连接方法   你在这三个逐层调用的连接方法里面可以看到下面这段代码,在这 block 中你可以看到在这里调用了我们上面说的第一个前置检测方法:

      在这里做了前置判断通过之后,再往下面就是异步执行获取得到IPV4的地址:address4 和IPV6的地址:address6 ,获取的具体方法你可以在Demo中看看,在这里获取到之后就进入我们需要理解的三部曲连接终极方法了,这三个方法先知道有三个,我们在说完下面的地址调用连接方法之后会说这三个方法,就在这个block的最后,发起了调用终极连接三部曲:

//异步去发起连接
dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
                     
    [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
}});

    这个block的调用就在这个block的下面。

      第三部分: 直接连接一个addr的data 三个逐层连接方法,这一部分的内容在我们日常的使用中使用的也不是很多,具体的在注释中也有,你也可以按照前面我们说的去理解这部分的逻辑。

      第四部分: 我们前面说的终极连接三方法都是在这一部分里面的,在这部分我们说说这三个方法,还有我们前面需要补充的问题,就是为什么有两个前置检测方法,哪里用到了呢?

      下面这三个方法是终极的连接方法,这三个也是逐层的调用连接,返回值以及里面具体的调用还有方法里面的内容注释里面都写得比较清楚,大家看Demo。

// 下面三个是终极的连接方法
- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6{}
 
- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr{}
 
- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex{}

    再说说我们遗留下来的那个问题,另一个前置方法在哪里用?看下面代码:

//连接本机的url上,IP8C,进程间通信
- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
{
    LogTrace();
     
    __block BOOL result = NO;
    __block NSError *err = nil;
     
    dispatch_block_t block = ^{ @autoreleasepool {
         
        //判断长度
        if ([url.path length] == 0)
        {
            NSString *msg = @"Invalid unix domain socket url.";
            err = [self badParamError:msg];
             
            return_from_block;
        }
         
        // Run through standard pre-connect checks
        //前置的检查
        if (![self preConnectWithUrl:url error:&err])
        {
            return_from_block;
        }
         
        // We've made it past all the checks.
        // It's time to start the connection process.
         
        flags |= kSocketStarted;
         
        // Start the normal connection process
         
        NSError *connectError = nil;
                //调用另一个方法去连接,连接Unix域服务器
        if (![self connectWithAddressUN:connectInterfaceUN error:&connectError])
        {
            [self closeWithError:connectError];
            return_from_block;
        }
 
        [self startConnectTimeout:timeout];
         
        result = YES;
    }};
     
        //在socketQueue中同步执行
    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
        block();
    else
        dispatch_sync(socketQueue, block);
     
    if (result == NO)
    {
        if (errPtr)
            *errPtr = err;
    }
     
    return result;
}

    首先这个方法是在didConnect 方法里面去调用的,这个didConnect就是已经连接成功的方法,在这个放里面调用我们上面的方法,然后再上面给的方法里面你就可以看到前置检查方法和连接Unix域服务器的方法,这里我想大家就明白了,在连接Unix域服务器的时候用到了前置检查,这个也是在服务端用到的,大家要是感兴趣可以去看看里面的具体的代码注释。

      下面Diagnostics部分的代码结构如下,这里全都是在配合我们上面连接部分的代码所用:

      上面的第五部分你看方法名称也就知道,这里我们就不在多说这部分的内容。

      接下来我们说说剩下的主要的两部分,读和写这两部分:

三:Writing

       这部分是写的内容,通过这几个方法就完成了一个写数据的操作,当然这写方法里面肯定还是会涉及到其他的一些辅助的方法,这里我们不一一的列举了,大家在Demo里面去看,再说一点,这部分的代码你根据demo看注释之前,还是先把上篇我们说的那个Socket原生的发送和接收过程理解了,这样有助于你更好的看完写部分的代码,发送完了之后接下来我们就是要看接收的代码了。我们看接收部分的代码。

四:Reading

      上面最重要的就是这个方法:   doReadData  

上面这个方法后面我们添加的几个标签(开始读取数据 CFStream , 开始读取数据 SSLRead, 开始读取数据普通的形式 等等)都是对这个方法的解释。

      当到下面的  completecurrentread  完成当前的读操作,到下面这里的时候:

      在这里就调用了我们GCDAsyncSocket中接收消息的代理方法:

      这里我们的读的操作你也就理解了,当然我说的不是看看这样一个过程你就理解了,重点还是我们Demo里面CocoaAsyncSocket的注释!!     

      剩下的方法几乎也全都是在辅助我们这几个重要的模块,也都有注释,还是那句看Demo!        

总结一下着整个过程

      上面就大概的把CocoaAsyncSocket的一个组织框架分析的一下,里面的具体的内容还是得看Demo,Demo的地址我也会在最下面给出来,这里我们把我看的简书文章中作者整理的这个连接过程图给大家,然后剩下的心跳或者是pingpong机制的写法我在下一篇的总结中我们具体的写一写。

Demo地址

        下面是这个简单的Demo的地址,服务端的代码以及运行看这个系列第一篇文章: Socket学习总结系列(一) -- IM & Socket

Demo下载

        最后还是这个Telegram的一个学习群,Android PC iOS 版本的关于Telegram的问题都可以在群里面相互探讨学习!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏猿人谷

从hello world 解析程序运行机制

开篇 学习任何一门编程语言,都会从hello world 开始。对于一门从未接触过的语言,在短时间内我们都能用这种语言写出它的hello world。 然而,对...

22660
来自专栏Golang语言社区

【提高篇】Go语言并发技术详解

有人把Go比作21世纪的C语言,第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持了并行。 goroutine goro...

28360
来自专栏Golang语言社区

社区leaf学习笔记|04. MongoDB测试

大家好,本篇文章给大家带来的是leaf原作者仅支持的mongoDB的相关测试;测试之前我们简单了解下MongoDB

18130
来自专栏Golang语言社区

从websocket看go的应用

Go是互联网时代的通用编程语言。这样它就和命令行时代的C语言、图示界面时代的C++、以及互联网早期的Java语言等有不同的侧重。它强调保持自身的精巧和独立,从而...

31660
来自专栏达摩兵的技术空间

惰性单例分析与学习

本文基于你已经知道单例模式的要点,本文内容借鉴于《javascript设计模式与开发实践》这本书,做出了整理和一些思考。

8110
来自专栏GreenLeaves

Web API系列之二WebApi基础框架搭建

本文主要介绍如何搭建一个WebApi的项目.关于如何搭建WebApi的方式一共有两种: 一、通过vs直接新建一个WebApi的项目,步骤如下: 第一步: 新建一...

25490
来自专栏漫漫深度学习路

tensorflow学习笔记(四十一):control dependencies

tf.control_dependencies()设计是用来控制计算流图的,给图中的某些计算指定顺序。比如:我们想要获取参数更新后的值,那么我们可以这么组织我们...

61090
来自专栏知无涯

【教程】快速入门,十天学会ASP

1.5K80
来自专栏欧阳大哥的轮子

iOS应用程序的脱壳实现原理浅析

对于诸多逆向爱好者来说,给一个app脱壳是一项必做的事情。基于安全性的考虑,苹果对上架到appstore的应用都会进行加密处理,所以如果直接逆向一个从appst...

10930
来自专栏java一日一条

40+个对初学者非常有用的PHP技巧(二)

考虑使用ob_gzhandler?不,别这样做。它没有任何意义。PHP应该是来写应用程序的。不要担心PHP中有关如何优化在服务器和浏览器之间传输的数据。

10210

扫码关注云+社区

领取腾讯云代金券