前言


这是这个系列文章的第二篇,要是没有看第一篇的还是建议看看第一篇,以为这个是接着第一篇梳理的先大概的总结一下在上篇的文章中说的些内容:

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源码 (建议先文章最后下载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看具体的情况而定,我们这里是为了不让无用代码占篇幅,我们的注意点放在它是通过哪些条件作了前置的判断,可以看代码中的注释:

  1. {
  2. // 先断言,如果当前的queue不是初始化quueue,直接报错
  3. // dispatch_get_specific 这个和dispatch_set_specific的用法具体的可以百度
  4. NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
  5. //无代理
  6. if (delegate == nil) // Must have delegate set{
  7. }
  8. //没有代理queue
  9. if (delegateQueue == NULL) // Must have delegate queue set{
  10. }
  11. //当前不是非连接状态
  12. if (![self isDisconnected]) // Must be disconnected{
  13. }
  14. // 判断是否支持IPV4 IPV6 &按位“与”运算,因为枚举是用 左位移<<运算定义的,所以可以用来判断 config包不包含某个枚举。因为一个值可能包含好几个枚举值,所以这时候不能用==来判断,只能用&来判断
  15. // 注意这个解释:kIPv4DisabledIf set, IPv4 is disabled,要是包含就说明IPV4是不能使用的,也就是要是返回YES,说明IPV4不能使用
  16.  
  17. BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
  18. BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
  19.  
  20. //是否都不支持
  21. if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled{
  22. }
  23. // 如果有interface,本机地址
  24. // interface这个参数这个就是我们设置的本机IP+端口号
  25. /*
  26. 一般情况不需要去设置这个参数,默认的为localhost(127.0.0.1)本机地址。而端口号会在本机中取一个空闲可用的端口。
  27. 而我们一旦设置了这个参数,就会强制本地IP和端口为我们指定的
  28. 这里端口号如果我们写死,万一被其他进程给占用,讲导致无法连接成功
  29. */
  30. if (interface)
  31. {
  32. NSMutableData * interface4 = nil;
  33. NSMutableData * interface6 = nil;
  34.  
  35. //得到本机的IPV4 IPV6地址
  36. [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0];
  37.  
  38. //如果两者都为nil
  39. if ((interface4 == nil) && (interface6 == nil)){
  40. }
  41. //IPV4不能正常使用且本机的IPV6为nil
  42. if (isIPv4Disabled && (interface6 == nil)){
  43. }
  44. //IPV6不能正常使用且本机的IPV4为nil
  45. if (isIPv6Disabled && (interface4 == nil)){
  46. }
  47. //如果都没问题,则赋值
  48. connectInterface4 = interface4;
  49. connectInterface6 = interface6;
  50. }
  51.  
  52. // Clear queues (spurious read/write requests post disconnect)
  53. // 读写Queue清除
  54. // 走到这里则前面的全没有返回值,在这里就返回YES,
  55. [readQueue removeAllObjects];
  56. [writeQueue removeAllObjects];
  57.  
  58. //能走到这里的条件 有delegate delegateQueue 包含IPV4或者IPV6
  59. return YES;
  60. }

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

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

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

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

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

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

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

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

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

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

  1. //连接本机的url上,IP8C,进程间通信
  2. - (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
  3. {
  4. LogTrace();
  5.  
  6. __block BOOL result = NO;
  7. __block NSError *err = nil;
  8.  
  9. dispatch_block_t block = ^{ @autoreleasepool {
  10.  
  11. //判断长度
  12. if ([url.path length] == 0)
  13. {
  14. NSString *msg = @"Invalid unix domain socket url.";
  15. err = [self badParamError:msg];
  16.  
  17. return_from_block;
  18. }
  19.  
  20. // Run through standard pre-connect checks
  21. //前置的检查
  22. if (![self preConnectWithUrl:url error:&err])
  23. {
  24. return_from_block;
  25. }
  26.  
  27. // We've made it past all the checks.
  28. // It's time to start the connection process.
  29.  
  30. flags |= kSocketStarted;
  31.  
  32. // Start the normal connection process
  33.  
  34. NSError *connectError = nil;
  35. //调用另一个方法去连接,连接Unix域服务器
  36. if (![self connectWithAddressUN:connectInterfaceUN error:&connectError])
  37. {
  38. [self closeWithError:connectError];
  39. return_from_block;
  40. }
  41.  
  42. [self startConnectTimeout:timeout];
  43.  
  44. result = YES;
  45. }};
  46.  
  47. //在socketQueue中同步执行
  48. if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
  49. block();
  50. else
  51. dispatch_sync(socketQueue, block);
  52.  
  53. if (result == NO)
  54. {
  55. if (errPtr)
  56. *errPtr = err;
  57. }
  58.  
  59. return result;
  60. }

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

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

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

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

三:Writing

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

四:Reading

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

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

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

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

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

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

 
 
总结整个过程

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

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

Socket学习总结系列(二) -- CocoaAsyncSocket的更多相关文章

  1. Telegram学习解析系列(二):这我怎么给后台传输数据?

    写在前面: 在iOS开发的过程中,有很多时候我们都在和数据打交道,最基本的就是数据的下载和上传了,估计很多很多的小伙伴都在用AFNetworking与后台数据打交道,可有没有想过,哪天AFNetwor ...

  2. Socket学习总结系列(一) -- IM & Socket

    写在准备动手的时候: Socket通讯在iOS中也是很常见,自己最近也一直在学习Telegram这个开源项目,Telegram就是在Socket的基础上做的即时通讯,这个相信了解这个开源项目的也都知道 ...

  3. 小白学习Spark系列二:spark应用打包傻瓜式教程(IntelliJ+maven 和 pycharm+jar)

    在做spark项目时,我们常常面临如何在本地将其打包,上传至装有spark服务器上运行的问题.下面是我在项目中尝试的两种方案,也踩了不少坑,两者相比,方案一比较简单,本博客提供的jar包适用于spar ...

  4. 学习CNN系列二:训练过程

    卷积神经网络在本质上是一种输入到输出的映射,它能够学习大量的输入与输出之间的映射关系,而不需要任何输入和输出之间精确的数学表达式,只要用已知的模式对卷积神经网络加以训练,网络就具有输入.输出之间映射的 ...

  5. Dubbo源码学习总结系列二 dubbo-rpc远程调用模块

    dubbo本质是一个RPC框架,我们首先讨论这个骨干中的骨干,dubbo-rpc模块. 主要讨论一下几部分内容: 一.此模块在dubbo整体框架中的作用: 二.此模块需要完成的需求功能点及接口定义: ...

  6. JNI 学习笔记系列(二)

    c中没有Boolean类型的值,一般是使用1表示true,0表示false,c中也没有String类型的数据,c中的字符串要通过char数组来表示.c中没有byte类型,一般用char表示byte类型 ...

  7. Windows-universal-samples学习笔记系列二:Controls, layout, and text

    Controls, layout, and text AutoSuggestBox migration Clipboard Commanding Context menu Context menu ( ...

  8. 步步为营 SharePoint 开发学习笔记系列总结

    转:http://www.cnblogs.com/springyangwc/archive/2011/08/03/2126763.html 概要 为时20多天的sharepoint开发学习笔记系列终于 ...

  9. MyBatis学习系列二——增删改查

    目录 MyBatis学习系列一之环境搭建 MyBatis学习系列二——增删改查 MyBatis学习系列三——结合Spring 数据库的经典操作:增删改查. 在这一章我们主要说明一下简单的查询和增删改, ...

随机推荐

  1. Linux内核的基本概念

    Linux内核学习,推荐的书籍: <linux设备驱动开发详解第二版>.<Linux内核设计与实现第三版>.<嵌入式Linux应用开发完全手册> 第一篇:讲解Lin ...

  2. Kindeditor编辑插件的使用

    1.首先kindeditor这个插件需要配合着asp.net的自生带的控件textbox来实现 2.首先前台界面代码 <f:FormRow runat="server"> ...

  3. [TYVJ1728/BZOJ3224]普通平衡树-替罪羊树

    Problem 普通平衡树 Solution 本题是裸的二叉平衡树.有很多种方法可以实现.这里打的是替罪羊树模板. 此题极其恶心. 前驱后继模块需要利用到rank模块来换一种思路求. 很多细节的地方容 ...

  4. springmvc(四) springmvc的数据校验的实现

    so easy~ --WH 一.什么是数据校验? 这个比较好理解,就是用来验证客户输入的数据是否合法,比如客户登录时,用户名不能为空,或者不能超出指定长度等要求,这就叫做数据校验. 数据校验分为客户端 ...

  5. 关于Verilog HDL的一些技巧、易错、易忘点(不定期更新)

    本文记录一些关于Verilog HDL的一些技巧.易错.易忘点等(主要是语法上),一方面是方便自己忘记语法时进行查阅翻看,另一方面是分享给大家,如果有错的话,希望大家能够评论指出. 关键词: ·技巧篇 ...

  6. The first day,I get a blogs!!

    我拥有了自己的博客,很happy! 今天学习了kvm,虽然命令行界面比较枯燥,还好不算太难,在大家的热心帮助下我创建了一个虚拟机!!

  7. Sqoop将mysql数据导入hbase的血与泪

    Sqoop将mysql数据导入hbase的血与泪(整整搞了大半天)  版权声明:本文为yunshuxueyuan原创文章.如需转载请标明出处: https://my.oschina.net/yunsh ...

  8. SetConsoleWindowInfo 函数--设置控制台窗口的大小和位置

    SetConsoleWindowInfo函数 来源:https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85) ...

  9. 【Spring】关于SpringMvc监听的知识点

    一,在使用Spring系列框架时,我们需要在Web.xml配置Spring的监听:ContextLoaderListener ContextLoaderListener的作用就是,在Web容器初始化时 ...

  10. (转)OGNL表达式介绍

    OGNL是Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言(Expression Language,简称为EL),通过它简单一致的表达式语法,可以存 ...