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

 

这是系列的第二篇html


这是这个系列文章的第二篇,要是没有看第一篇的仍是建议看看第一篇,觉得这个是接着第一篇梳理的git

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

一、 整理了一下作IM咱们有那些途径,以及咱们怎样选择最适合本身的编程

二、在作IM的时候协议你又该怎样选择,以及这些协议之间一些的对比等等服务器

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

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

 

这篇咱们梳理那些dom


一、 对CocoaAsyncSocke这个三方的理解以及一些本身的见解 异步

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

三、利用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看具体的状况而定,咱们这里是为了避免让无用代码占篇幅,咱们的注意点放在它是经过哪些条件做了前置的判断,能够看代码中的注释:

{
        // 先断言,若是当前的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的问题均可以在群里面相互探讨学习!
 

     群号粘贴:485718322

相关文章
相关标签/搜索