iOS中socket的實戰訓練

前言

我是培訓班出身的,我至今還記得老師關于socket的一句話:http是短連接,socket是長連接。我估計是老師對我們這群菜鳥不報什么希望,所以才這么說的,而我直到前一陣子還一直當真理相信著。。。

最近工作上接觸了socket,看了很多文檔,漸漸的對socket有了一個清晰的了解,下面附上2個比較好的連接:

在這個充斥著互聯網的世界,單機的APP已經漸漸銷聲匿跡,網絡編程成為了一個程序員的基本素養。在此,我推薦2本我準備要看的書給和我一樣非科班出身的程序猿:《計算機網絡-自頂向下方法》,還有就是《TCP-IP詳解》的3卷。這2本有先后順序,先讀第一本,在理解第二本會好很多。書單鏈接:

與君共勉

socket框架

我用的是GCDAsyncSocket,畢竟對c的api一臉懵逼的,所以找一個成名的、封裝好、面向對象的socket框架,GCDAsyncSocket的用法我就不多說了,自己可以百度。

image.png

大家可以去https://github.com/robbiehanson/CocoaAsyncSocket下載,當然,也可以下我的demo直接拿。注意到圖片里的udp了么,我們用的是tcp協議的,后面可以帶大家看3次握手和4次分手的過程。

socket客戶端

image.png

首先在storyboard里拖個小界面,在ViewController里關聯下,接著聲明一個GCDAsyncSocket的對象,如果不持有屬性的話,對象釋放的時候會自動斷開連接。

@property (nonatomic, strong) GCDAsyncSocket *socket;

創建socket,這邊GCDAsyncSocket用法詳解就不說了,自己百度。

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

給button加個點擊事件,里面有連接服務器,和發送數據事件的切換。值得注意的是,每個發送的消息必須要有特定字符分隔開,不然后臺無法識別數據是否已經發送完成,常用的是換行符。[GCDAsyncSocket CRLFData]框架里已經封裝給我們了。所以每次發送的數據都要拼接上[GCDAsyncSocket CRLFData]

 if (self.button.tag == SendType_Connent) {
        NSArray *arr = [self.textField.text componentsSeparatedByString:@":"];
        NSError *error;
        [self.socket connectToHost:arr.firstObject onPort:[arr.lastObject intValue] withTimeout:15 error:&error];
        if (error) {
            NSLog(@"%@, %d", error, __LINE__);
        }
    }else {
        
        NSMutableData *data = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
        [data appendData: [GCDAsyncSocket CRLFData]];
      
        [self.socket writeData:data withTimeout:30 tag:0];
    }

接著你可以把GCDAsyncSocketDelegate中的所有代理都拷貝過來,方便自己學習,你可以在所有代理方法中加上這句,這樣就很方便就看到那些代理方法調用了。

NSLog(@"%s,%d", __func__, __LINE__);

下面是代理方法的書寫

首先是連上服務器的回調,連上的時候把button的事件改成發送,然后監聽服務器的數據。

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
    NSLog(@"%s,%d", __func__, __LINE__);
    self.button.tag = SendType_SendMessage;
    [self.button setTitle:@"發送" forState:UIControlStateNormal];  
    [self.socket readDataWithTimeout:-1 tag:0];
}

然后是數據監聽的回調,將NSData轉成NSString,并在控制臺打印

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSLog(@"%s,%d", __func__, __LINE__);
    NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    [self.socket readDataWithTimeout:-1 tag:0];
}

最后是斷開連接的回調,把button調回連接狀態。

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
    NSLog(@"%s,%d", __func__, __LINE__);
    self.button.tag = SendType_Connent;
    [self.button setTitle:@"連接" forState:UIControlStateNormal];
}

一個簡單demo就完成了,寫完了當然要測試,在終端用netcat工具實現簡單的服務器聊天功能,命令是nc -lk 端口

image.png

當光標移到下面去的時候,表示服務器已經開始監聽啦。運行demo,如果是模擬器,那么ip寫上127.0.0.1的回環地址,如果是真機的話,寫上電腦的ip地址就行。端口的話就和服務器監聽的一致就行。輸入ip后,點擊連接,如果成功的話,控制臺會打印成功的回調。

image.png

接著在輸入框里可以輸入內容聊天了,比如我輸入一個hello
image.png

控制臺便會跳出來一個hello,控制臺也會打印didWriteDataWithTag的回調。如果你在終端輸入內容(回車鍵發送),那么你也會收到信息
image.png

你關掉終端或者按ctrl+c便能關掉服務端,會收到socketDidDisconnect的回調。

客戶端的小demo就完成啦。

socket服務端

手機當服務器,有沒有覺得很有成就感?

服務端的demo很多內容和上面一樣,具體可以看demo中的內容,著重說下不同點。

首先是socket的創建,這個socket對象只負責端口的監聽,并不負責data的傳送。一旦這個socket斷開了連接,其他客戶端就再也連不上這臺服務器了。

self.serverSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    //監聽某個端口 等待被鏈接 0-25535  1000以內是系統預留端口
 [self.serverSocket acceptOnPort:5555 error:nil];

一旦有客戶端有連接的話,便會回調這個方法:

- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket{
    NSLog(@"%s,%d", __func__, __LINE__);
    //把鏈接進來的socket對象 持有住不被自動釋放 釋放掉的話 鏈接會自動斷開
    [self.sockets addObject:newSocket];
    [newSocket readDataWithTimeout:-1 tag:0];
}

這里會有一個newSocket的參數,這個newSocket對象表示是與當前客戶端的連接,作用是和當前客戶端相互傳送data的,self.serverSocket的代理對象會賦值給這個newSocket,所以newSocket也會走你寫的代理方法。當然,可能會有好幾個客戶端接進來,所以你需要用一個數組來管理,創建數組我就不展示了。

所以走didReadData回調的是你sockets數組中的某一個,并不是一開始創建的self.serverSocket。我為了識別是哪一個客戶端給我發的消失,創建了一個對象指向它:

@property (nonatomic, weak)GCDAsyncSocket *currentSocket;

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSLog(@"%s,%d", __func__, __LINE__);
    NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    self.currentSocket = sock;
    [sock readDataWithTimeout:-1 tag:0];
}

當有消息進來的時候,我指向那個客戶端,方便給他回消息(暫時不考慮并發的情況,只是demo嘛)

客戶端斷開連接的時候,socket記得從數組中移除。

接下來就是測試啦,先運行demo,然后用終端當客戶端,命令是nc 127.0.0.1 5555,host和port可以根據自己的實際情況自己改,你可以多開幾個終端,同時連上服務器。

image.png

image.png

我連了3個,就有3次回調,數組也有3個。data傳送你們自己試吧。

TCP的3次握手和4次分手

我們平時用的抓包工具是Charles,但這個一般用來抓http協議請求的,他幫我們做了很多處理,所以很多細節都看不到,對于socket來說,這工具就不夠看了。推薦一個新工具--Wireshark。


image.png

工具的使用自己百度搜吧,我也用的很生疏。

接下來我模擬器運行服務端,真機運行客戶端,模擬器ip是10.10.2.47,真機ip是10.10.2.50


image.png

當我點擊連接的時候出現了4條數據,前面3條是不是很熟悉,就是一直念在口中的3次握手,第4條數據是滑動窗口的概念。


image.png

然后我客戶端發送了2條后,服務器也發送了1條。由圖可以得到每條消息要2個數據包,來回各一次。


image.png

最后斷開連接,是不是很完美的4次分手?

結語

附上代碼地址:https://github.com/harryphone/SocketDemo

用Wireshark可以分解出一次完整的http請求,下次有機會用socket封裝出一個http請求,甚至是https請求。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內容

  • 前言 本文會用實例的方式,將iOS各種IM的方案都簡單的實現一遍。并且提供一些選型、實現細節以及優化的建議。 注:...
    maTianHong閱讀 2,399評論 4 12
  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數據庫組件 SD...
    陽明AGI閱讀 16,003評論 3 119
  • 轉載:http://www.cocoachina.com/ios/20170615/19529.html 參考:h...
    F麥子閱讀 4,024評論 3 2
  • 工具使用用素描本和彩鉛、碳素筆。中間有一幅畫使用勾線筆勾線時在本上暈開。 如果不逼自己一下,永遠也不會下筆去畫。
    西柚hy閱讀 310評論 1 1
  • 早起《大學》一遍,艾灸。掄胳膊。 早課感受:今天聽的早課是節氣養生課。 受到的啟發:1養生養在根本。養生可以說很多...
    悅2017137閱讀 231評論 0 3