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

相關文章
相關標籤/搜索