GCDAsyncSocket

GCDAsyncSocket類庫捐凭,IOS下TCP通訊使用心得

發(fā)表于 2013 年 7 月 25 日 由 水德星君

關于在iOS下使用Socket進行通訊的技術文章也許諾很久了赫冬,今日又是一個還債的日子,網(wǎng)上雖然很多介紹過AsyncSocket或GCDAsyncSocket的文章污它,但其實就那么一兩篇大部分都是轉(zhuǎn)載贞岭,于是我義正言辭又憨、慷慨激昂的批判他們這種不負責任的態(tài)度袜炕,學習,不是給自己學的献酗,是要和大家分享的寝受。技術的共享有利于整體行業(yè)的進步,也可以使自身更深入全面的了解凌摄。

之前的文章中我們講到過TCP通訊協(xié)議羡蛾,并且也對其進行了較為詳細的介紹和描述漓帅,關于TCP通訊的原理此處我們不再贅述锨亏,如有需要的看官可自行翻閱本人所寫的《IOS、安卓IM語音聊天開發(fā)初探部分心得——網(wǎng)絡基礎篇》一文忙干。

正如名稱一樣GCDAsyncSocket開源類庫是以蘋果的GCD多任務處理機制完成的一個異步交互套接字通訊器予。使用方法其實并不復雜,主要說的是在使用這個類庫的時候我的一些心得和理解捐迫,若有不妥之處望看官指點乾翔。首先,每一個GCDAsyncSocket對象(以下簡稱GCDSocket對象)都可以理解為一個socket套接字,我們的操作都是針對于這個socket執(zhí)行的各種命令反浓,可以打開一個端口偵聽萌丈,同樣也可以連接其他計算機的端口進行數(shù)據(jù)通訊等等等等。首先我們來創(chuàng)建一個socket雷则。當然這之前先要將CGDAsyncSocket的.h文件及.m文件加入到我們的項目辆雾,并且在需要使用socket連接的地方將.h頭文件包含,這些廢話我覺得不需要復述了應該(那你還嘚吧嘚的說半天干嘛啊喂T屡)度迂。具體代碼如下

GCDAsyncSocket? socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

代碼并不復雜,我們只需要給出一個委托對象也就是第一個參數(shù)中的self猜揪,以及一個委托運行的GCD隊列即可創(chuàng)建一個GCDAsyncSocket惭墓,當前代碼中我們是使用靜態(tài)全局函數(shù)取得的主消息隊列。當然也可以使用其他方法獲得其他的GCD隊列而姐,比如:dispatch_get_global_queue().

創(chuàng)建了Socket對象我們即可以立即為腊凶,當前我們的socket已經(jīng)進入程序以供操作。但如果你想和服務器進行通訊毅人,那么我們還需要和服務器進行連接吭狡。可能有的使用習慣了http協(xié)議的人會問丈莺,初始化函數(shù)中我們?yōu)楹尾恢苯又付ǚ掌饕约岸丝谔柣螅科鋵嵾@些肯定都是需要的,但是你要理解到缔俄,你的socket對象功能不只是可以用來連接服務器弛秋,換而言之我們的socket對象一樣可以偵聽某端口來等待他人連接,所以在通過套接字編程使用TCP協(xié)議的時候是我們從http協(xié)議過度到TCP協(xié)議的一個轉(zhuǎn)變(雖然本文并不會教你如何在IOS上構架服務器俐载。)蟹略,但并不是第一個,第一個轉(zhuǎn)變是要記得遏佣,我們要使用的是協(xié)議挖炬,并非某個類,所以我上述說明中都是說從http協(xié)議過度到TCP而不是跟大家說現(xiàn)在我們將從NSURLRequest和NSURLConnection過度到GCDAsyncSocket状婶。

好了接下來我們看看如何連接服務器意敛。源代碼如下:

NSError *err;

[socket connectToHost:@“192.168.10.111” onPort@"60000" error:&err];

if (err != nil)

{

NSLog(@”%@”,err);

}

代碼比前面稍微長了一點,不過實質(zhì)上完全不復雜膛虫,我們只是先聲明了一個錯誤信息的指針草姻,然后使用之前創(chuàng)建的對象調(diào)用他的連接方法,第一個參數(shù)不難看出是一個IP稍刀,第二個參數(shù)則是一個端口撩独,如果這里還不理解何為IP和端口的話,就先去看看在開頭就提到的我之前寫過的那邊網(wǎng)絡基礎篇文章吧…最后一個是出參,如果連接的過程中出現(xiàn)了錯誤综膀,該方法會把這根指針指向一個具體的錯誤信息澳迫,最后我們再判斷一下之前我們創(chuàng)建錯誤信息的指針是否還是指向空,如果并非指向空那么代表我們連接的過程中出現(xiàn)了錯誤剧劝,將錯誤信息打印一下吧~不過請切記纲刀,此處的錯誤信息并非你創(chuàng)建連接時所有的錯誤都會在此處得到反映。

說到這里我們該說一點真正有用的了担平,GCDAsyncSocket具有一系列完整的委托機制示绊,我們所做的一切處理基本都是異步處理的狀態(tài),換句話說暂论,連接之后是否連接成功面褐,連接成功要執(zhí)行什么懂并非應該寫在此處而應該寫在相應的委托之中,同樣的道理一樣適用于發(fā)送取胎、讀取數(shù)據(jù)等等展哭。也就是說我們在此處讀出的錯誤只是同步執(zhí)行的代碼處理一些連接時會發(fā)生的錯誤,而更多的處理我們應在相對應的委托中進行處理闻蛀。首先請看下面這個方法:

-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port

這個方法就是在成功連接服務器之后的委托方法匪傍。關于委托該如何使用我在此處就不贅述了和本文的關系實在不大,不過給諸位看管一個建議觉痛,也是我才剛剛糾正的一個編碼錯誤習慣役衡,之前碰到所有委托的地方我都會將直接將當前的類對象設置成委托處理對象,并且遵循委托協(xié)議擴充代碼薪棒,這么做的壞處顯而易見手蝎,顯示層與邏輯層的混淆是一方面,另一方面是一旦需要使用過多的委托俐芯,將造成大量不必要的代碼都堆積在一個類中棵介,并且我們很容易直接在委托方法中直接使用一些類內(nèi)成員屬性或者甚至是私有成員,而實際上這種做法是很不好的吧史,因為這種最發(fā)會使得邏輯出現(xiàn)混亂邮辽,處理委托應當是單獨處于后臺的邏輯,如果需要一些必要的數(shù)據(jù)傳遞也應該采取屬性偵聽贸营、甚至是通知等方式來實現(xiàn)而并非直接在顯示層中編寫邏輯代碼來實現(xiàn)吨述。使得代碼耦合性大增的同時也使得很多時候在切換操作對象時對委托對象的處理變得復雜,甚至可能完全相同的代碼要難免的復制粘貼莽使。所以我給大家的建議是單獨編寫一個委托類锐极,在每個類中設置一個該類類型的成員指針笙僚,將委托設置到專門的委托對象上去處理芳肌,這樣不僅效率更高,代碼可讀性更強,更便于維護亿笤,同時也更符合面向?qū)ο蟮木幊趟枷搿?/p>

回到對GCDAsyncSocket使用的講解上來翎迁,在這個委托方法中,我們可以取到一個socket對象一個服務器IP和一個端口號,你可以處理一切在連接建立之后應該馬上執(zhí)行的事情净薛,比如與服務器進行通信確認連接端以免出現(xiàn)其他人通過IP及端口隨意的和你的服務器通信汪榔,再比如開啟心跳包的發(fā)送,讓服務器一直可以確認你的存在肃拜。不管做什么痴腌,都是你和服務器的編寫者事前約定好的,就像數(shù)據(jù)傳輸格式什么的燃领,如果沒有當面約定我堅信他也一定要給你出個文檔什么的士聪,否則你的工作接下來將舉步維艱。但是不管你要在此處都做什么工作猛蔽,都要處理哪些事宜剥悟,請務必記得,在此處你必須要在函數(shù)的最后加上一句:

[socket readDataWithTimeout:-1 tag:0];

這是什么曼库?別慌区岗,按照你看到這個函數(shù)的第一反應取理解,沒錯他就是讀取數(shù)據(jù)的方法毁枯,兩個參數(shù)也略顯簡單慈缔,一個超時時間,如果你設置成-1則認為永不超時种玛,而第二參數(shù)則是區(qū)別該次讀取與其他讀取的標志胀糜,通常我們在設計視圖上的控件時也會有這樣的一個屬性就是tag。如果你做過web開發(fā)蒂誉,那你應該知道Http標簽上的id教藻,如果你做過一些桌面級開發(fā),你的控件或許有個id或者是index再或者是tag的屬性來區(qū)別這些控件右锨,沒錯此tag和彼tag功效基本一樣括堤。

我們可以這樣理解,socket在開啟之前是一個巨大門绍移,開啟這道門之后(也就是連接之后)就是一個寬敞的通道悄窃,通過這條通道所達到的地點就是我們連接的目標服務器,或者是連接過來的客戶端蹂窖,兩面都是一樣的轧抗。我們現(xiàn)在不論是發(fā)送數(shù)據(jù)還是讀取數(shù)據(jù)都是往返于這個大門之中的一個個門衛(wèi)與郵遞員,我們可以把讀取數(shù)據(jù)的方法看作是門衛(wèi)瞬测,而發(fā)送數(shù)據(jù)的人看做是郵遞員横媚,沒錯服務器與客戶端都一樣纠炮,我們都會派出一個個郵遞員去我們連接的另一端送信,但是如果你沒有命令你的門衛(wèi)去吧門口郵箱中的信拿過來灯蝴,那么你的郵遞員就會假裝看不見郵遞員恢口,然后呼呼睡大覺,好吧看起來這些門衛(wèi)實在沒什么責任心不是么穷躁,其實他們也是有苦衷的耕肩,因為這是最初設計者給他們的命令,不接到命令絕對不要出門问潭,萬一收到的是金剛葫蘆娃高清全集的種子怎么辦猿诸!好的就這樣,為了避免我們的郵件不被錯過狡忙,所以建立連接之后就讓一個門衛(wèi)跑去門口等著吧~慢著两芳,萬一我需要派出很多個門衛(wèi)我分不清他們該怎么辦,其實他們已經(jīng)被你分配了工號去枷,這個工號就是tag怖辆。

現(xiàn)在我們的連接動作算是完整的做完了,接下來我們要做的就只有兩件事删顶,第一個在需要發(fā)送數(shù)據(jù)的時候派出郵遞員竖螃,以及當門衛(wèi)接到消息的時候在我們的手機端上根據(jù)門衛(wèi)的消息做出反應。等等逗余,好像少了點什么特咆,沒錯 少點委托,我們來看一下讀取和寫入的委托录粱,讀取的委托即是門衛(wèi)接到信息的報告腻格,寫入的委托就是郵遞員將郵件送完的回復:

-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag

-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag

好的讓我們來看看這些委托中我們都能得到什么,首先是讀取的委托啥繁,是一個socket對象菜职,一個讀取到的數(shù)據(jù)以及一個“門衛(wèi)的工號”,嗯旗闽,大概也就這些酬核,我們還能要什么的,沒錯這些足夠了适室,別抱怨第二個參數(shù)的數(shù)據(jù)類型嫡意,要知道其實最開始接到數(shù)據(jù)的時候只是字節(jié)數(shù)組啊,已經(jīng)給你轉(zhuǎn)換成NSData對象了你就要學會感恩啊捣辆,誰讓你要用套接字傳輸了蔬螟,這就是活該的,所以改怎么讀取轉(zhuǎn)換解析這些數(shù)據(jù)你需要好好的和服務器編寫者溝通汽畴。除此之外你還要詳細的了解如何將NSData轉(zhuǎn)換成各種各樣的數(shù)據(jù)或者文件如果你還不知道該怎么做我這里實在幫不了你旧巾,因為我總不能吧多如牛毛的情況都列舉在這一篇文章中吧耸序,要知道我每篇文章的篇幅都夠長了。菠齿。。不過也別因此而氣餒百度和谷歌肯定可以幫到你~

接下來我們再看看“郵遞員的委托”,嗯一個socket的對象坐昙,一個tag嗯绳匀,沒錯,哎哎炸客,慢著疾棵,好像哪里不太對啊,我來看看痹仙,哪里不對呢是尔,哦對了!發(fā)送的數(shù)據(jù)呢开仰!怎么沒有拟枚!哎也不對。众弓。恩溅。明明是我自己發(fā)的數(shù)據(jù)我還要來干嘛,有了工號我不就知道發(fā)送的是什么了么谓娃。那是哪里不太對呢脚乡。。滨达。哦奶稠!是名字!我們的數(shù)據(jù)傳輸來說接受可以是讀取read而發(fā)送通常我們應該寫成Send一類的單詞捡遍,為何這里是Write锌订?寫入?沒錯就是寫入画株,向TCP的通訊流之中寫入數(shù)據(jù)瀑志。TCP通訊協(xié)議是一個基于字節(jié)流的運輸層通信協(xié)議,其數(shù)據(jù)傳輸?shù)男问揭彩且粤鞯男问教岈F(xiàn)污秆,而我感覺在使用GCDAsyncSocket的過程中我們可以很好的體會到流的概念劈猪,首先來說為什么這種TCP的這種傳輸形式要叫流而不像UDP中的那樣叫做包?流之中又寫入和讀出的概念良拼,我們可以把整個TCP通訊的連接看作為一條無水的河流战得,當然因為他沒水所以你可以稱它為溝,而向其寫入數(shù)據(jù)即是向河流注入水庸推,被寫入的數(shù)據(jù)會向水一樣流向連接的另一端常侦。讀即是從河流中取水浇冰,只要讀得動作在繼續(xù),并且河流之中有水聋亡,那么我們就可以不停的取到數(shù)據(jù)肘习,不論是河流之中有水你確沒有去讀亦或者是你去讀了而河流之中沒有水都會引發(fā)看起來完全相同的反應就是沒有數(shù)據(jù)返回,所以在很多時候我們要處理更多的關于接收數(shù)據(jù)的邏輯的處理坡倔。正如我們目前使用的方法就是一種比較粗暴有效的方法——一旦開啟連接讀取的動作就永不停歇漂佩。

接下來我們還要記住使用TCP流式傳輸數(shù)據(jù)時的一個關鍵性問題,數(shù)據(jù)是不會自己分段的罪塔。沒錯投蝉,就如一次次倒入河流中的水一樣,數(shù)據(jù)也同樣會向水一樣融合為一個整體征堪,換句話說瘩缆,數(shù)據(jù)在TCP中傳輸本身是沒有起始或結(jié)尾之分,如果我先向數(shù)據(jù)流中寫入兩個人的聊天記錄佃蚜,第一句是“你好”庸娱,對方回復了一句“不好”,結(jié)果發(fā)到了服務器谐算,服務器讀取出的信息是“你好不好”,同樣類似的情況會發(fā)生很多涌韩,比我舉出的這個例子要常見的多比如我先發(fā)了一段音頻,又發(fā)了一段圖片氯夷,又發(fā)了一段文字臣樱,最后服務器接收到了一個帶語音和字母的靜態(tài)圖片。實際情況上比我說的要遭的多腮考,因為由于字節(jié)之間并沒有邊界雇毫,所以字符、文字踩蔚、音頻棚放,我們根本無法確定他們各有多長,胡亂截取馅闽,只會導致無法編碼解析成圖片飘蚯、文字及音頻,所以如何界定數(shù)據(jù)之間的邊界是你開始使用TCP協(xié)議之后又一個問題福也。你可以使用一個固定的字節(jié)數(shù)組組合來區(qū)分開頭以及結(jié)尾局骤,也可以將所有的字符串都添加一個特殊的界定字符來區(qū)分不同的命令與操作。

如果看到這里的看官有心使用GCDAsyncSocket去編寫了一個服務器端暴凑,并且使用它來接受客戶端的數(shù)據(jù)峦甩,比如傳輸了一些音頻,圖片等從字節(jié)單位看來將會不小的長串數(shù)據(jù)的話就會發(fā)現(xiàn)现喳,服務器端接到的程序是一段一段的凯傲,沒錯犬辰,但我沒有欺騙你,TCP協(xié)議并不會區(qū)分你發(fā)送數(shù)據(jù)的頭尾冰单,被劃分為段知識GCDAsyncSocket為了保證在并不通常的移動互聯(lián)網(wǎng)之中一樣可以安全的傳輸數(shù)據(jù)幌缝,于是將你所有寫入到流的數(shù)據(jù)都一分割為一段一段的內(nèi)容,所以請正確理解我在上一段開頭所說的“數(shù)據(jù)是不會自己分段的诫欠『眩”這句話,不要較真哦親~

寫到這里呕诉,GCDAsyncSocket的基本操作及其核心思想就全部寫完了缘厢,對于思想部分皆為筆者本人個人理解吃度,若有缺少或意見不同之處甩挫,歡迎交換意見相互學習,感謝您的閱讀椿每。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伊者,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子间护,更是在濱河造成了極大的恐慌亦渗,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汁尺,死亡現(xiàn)場離奇詭異法精,居然都是意外死亡,警方通過查閱死者的電腦和手機痴突,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門搂蜓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辽装,你說我怎么就攤上這事帮碰。” “怎么了拾积?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵殉挽,是天一觀的道長。 經(jīng)常有香客問我拓巧,道長斯碌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任肛度,我火速辦了婚禮输拇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贤斜。我一直安慰自己策吠,他們只是感情好逛裤,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著猴抹,像睡著了一般带族。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蟀给,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天蝙砌,我揣著相機與錄音,去河邊找鬼跋理。 笑死择克,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的前普。 我是一名探鬼主播肚邢,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拭卿!你這毒婦竟也來了骡湖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤峻厚,失蹤者是張志新(化名)和其女友劉穎响蕴,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惠桃,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡浦夷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辜王。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劈狐。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖誓禁,靈堂內(nèi)的尸體忽然破棺而出懈息,到底是詐尸還是另有隱情,我是刑警寧澤摹恰,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布辫继,位于F島的核電站,受9級特大地震影響俗慈,放射性物質(zhì)發(fā)生泄漏姑宽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一闺阱、第九天 我趴在偏房一處隱蔽的房頂上張望炮车。 院中可真熱鬧,春花似錦、人聲如沸瘦穆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扛或。三九已至绵咱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熙兔,已是汗流浹背悲伶。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留住涉,地道東北人麸锉。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像舆声,于是被迫代替她去往敵國和親花沉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 關于在IOS下使用Socket進行通訊的技術文章也許諾很久了泻拦,今日又是一個還債的日子毙芜,網(wǎng)上雖然很多介紹過Async...
    okerivy閱讀 778評論 2 6
  • iPhone的標準推薦是CFNetwork 庫編程,其封裝好的開源庫是 cocoa AsyncSocket庫争拐,用它...
    Ethan_Struggle閱讀 2,223評論 2 12
  • 一腋粥、socket 1.網(wǎng)絡體系結(jié)構和網(wǎng)絡協(xié)議 在說socket之前,先要簡單說一說網(wǎng)絡體系結(jié)構架曹。OSI(Open ...
    lugic閱讀 12,880評論 20 30
  • 放生吧隘冲! 我的心蝸 攀上嫩枝去尋找 睡在墻角來做夢 到海里去 化作一只海螺 踩著水 到海的那邊去 去看花開花落
    左錦蘭可樂閱讀 190評論 0 2
  • 不同。 不斷學習绑雄,卻看不清現(xiàn)實的應用展辞。 然而,生活是現(xiàn)實的万牺。 平衡罗珍。 不然遲早會放棄。
    8c85031cd8e5閱讀 106評論 0 0