前言
這是這個系列文章的第二篇,要是沒有看第一篇的還是建議看看第一篇,以為這個是接着第一篇梳理的先大概的總結一下在上篇的文章中說的些內容:
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
等繁瑣復雜的編程,下面是我們導入的整個框架的

GCDAsyncUdpSocket 是基於UDP協議寫的
GCD
版本。
CocoaAsyncSocket源碼 (建議先文章最后下載Demo)

第二部分:這一部分的內容就是代理和線程的設置,說實話也沒什么好說的,重點還是下面的連接部分,這個你也在Demo中配合注釋去理解理解。
第三部分:這一部分比起前面的兩小部分稍微就需要我們注意點了,這部分的內容在下面的重點的連接部分用的比較多,你仔細看這部分方法的名字也可以理解,都是一些設置、判斷IPV4和IPV6是否可用的方法。至於最下面userData的復制方法,這點我覺得似乎可以暫時忽略。

但按照我自己的理解,很少用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!
