NIO框架入門(三):iOS與MINA2、Netty4的跨平臺UDP雙向通訊實戰

jsjsjjs發表於2016-06-28

前言

本文將演示一個iOS客戶端程式,通過UDP協議與兩個典型的NIO框架服務端,實現跨平臺雙向通訊的完整Demo。服務端將分別用MINA2和Netty4進行實現,而通訊時服務端你只需選其一就行了。同時用MINA2和Netty4分別實現服務端的目的,是因為很多人都在糾結到底是用MINA還是Netty來實現高併發的Java網路通訊服務端,在此乾脆兩個都實現了,就看你怎麼選擇了,夠吊吧。

NIO框架的流行,使得開發大併發、高效能的網際網路服務端成為可能。這其中最流行的無非就是MINA和Netty了,MINA目前的主要版本是MINA2、而Netty的主要版本是Netty3Netty4(Netty5已經被取消開發了:詳見此文),本次將使用MINA2和Netty4來實現服務端的程式碼。

實際上,MINA2和Netty4的官方程式碼裡已經有UDP通訊的Demo程式碼,但客戶端並不是基於現今流行的移動端(主要是Android和iOS端)來實現,本文將演示用iOS客戶端來實現這種跨平臺的雙向網路通訊。演示Demo中,已經解決跨平臺通訊時的亂碼、資料位元組異常等問題,請繼續往下閱讀。

學習交流

– 更多即時通訊技術資料:http://www.52im.net/forum.php?mod=collection&op=all

– 移動端即時通訊交流群:215891622

《NIO框架入門》系列文章

有關MINA和Netty的入門文章很多,但多數都是複製、貼上的未經證實的來路不明內容,對於初次接觸的人來說,一個可以執行且編碼規範的Demo,顯然要比各種“詳解”、“深入分析”之類的要來的直接和有意義。本系列入門文章正是基於此種考慮而寫,雖無精深內容,但至少希望對初次接觸MINA、Netty的人有所啟發,起到拋磚引玉的作用。

本文是《NIO框架入門》系列文章中的第3篇,目錄如下:

NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示

NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示

NIO框架入門(三):iOS與MINA2、Netty4的跨平臺UDP雙向通訊實戰》(本文

《NIO框架入門(四):Android與MINA2、Netty4的跨平臺UDP雙向通訊實戰》

本文亮點

客戶端基於iOS移動端平臺實現:

通常這類跨平臺的網路通訊例子很難找,本文已解決跨平臺通訊的適配問題,是個難得的實踐入門示例;

完整可執行原始碼、方便學習:

完整的Demo原始碼,適合新手直接執行,便於學習和研究。

Demo中的程式碼源自作者的開源工程,有實用價值:

原始碼均修改自作者的即時通訊開源工程MobileIMSDK,只是為了方便學習理解而作了簡化,有一定的實用價值;

本文Demo的場景邏輯

本文要演示的Demo包含兩部分,iOS UDP客戶端和NIO框架實現的服務端(包括MINA2和Netty4實現兩個方案),客戶端每隔5秒向服務端傳送訊息,而服務端在收到訊息後馬上回復一條訊息給客戶端。

如上所述,服務端和客戶端都要實現訊息的傳送和接收,即實現跨平臺的雙向通訊。如果有心的話,稍加改造,也就很容易實現一個簡陋的聊天程式了。下節將將給出真正的實現程式碼。

iOS客戶端準備工作

[Step 1] 去Github上下載最新的CocoaAsyncSocket:

CocoaAsyncSocket原始碼地址:https://github.com/52im/CocoaAsyncSocket,如下圖:

補充說明:iOS裡的網路程式設計有多種途徑實現(具體請參看此文),本文選擇的是iOS裡非常熱門的 CocoaAsyncSocket 工程,它對iOS原生網路API做了進一步封裝,使得開發者更易使用。

[Step 2] 建好XCode工程,�準備開擼:

建好工程後把CocoaAsyncSocket的原始碼引用進來就行了,如下圖:

補充說明:如何新建一個XCode工程請自行百度之,按照系統預設的簡單建立一個就好了,本例不需要作額外配置和額外的系統庫引用。

iOS客戶端程式碼實現

[1] 客戶端主類 ViewController.m:

//  Copyright (C) 2016 即時通訊網(52im.net)- 即時通訊開發者社群.

//  All rights reserved.

//  Created by JackJiang on 16/06/22.

#import”ViewController.h”

#import”LocalUDPSocketProvider.h”

#import”LocalUDPDataSender.h”

#import”CharsetHelper.h”

#import”UDPUtils.h”

@interfaceViewController ()

@end

@implementationViewController

– (void)viewDidLoad

{

[superviewDidLoad];

// 初始化socket

[[LocalUDPSocketProvider sharedInstance] initialLocalUDPSocket];

// 注意:執行延遲的單位是秒哦

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5target:self selector:@selector(doSend) userInfo:nil repeats:YES];

[timer fire];

}

– (void)doSend

{

NSString *toServer = [NSString stringWithFormat:@”Hi,我是iOS客戶端,我的時間戳 %li”,[UDPUtils getTimeStampWithMillisecond_l]];

[[LocalUDPDataSender sharedInstance] send:[CharsetHelper getBytesWithString:toServer]];

}

@end

補充說明:本類本是介面主類,但為了省事,沒有去寫UI程式碼,只是作為本次Demo的主入口類而已,需要檢視資料輸出的,請在XCode控制檯看檢視log輸出哦。

[2] 客戶端Socket管理類 LocalUDPSocketProvider.m:

//  Copyright (C) 2016 即時通訊網(52im.net)- 即時通訊開發者社群.

//  All rights reserved.

//  Created by JackJiang on 16/06/22.

#import”LocalUDPSocketProvider.h”

#import”GCDAsyncUdpSocket.h”

#import”ConfigEntity.h”

#import”CompletionDefine.h”

@interfaceLocalUDPSocketProvider ()

@property(nonatomic, retain) GCDAsyncUdpSocket *localUDPSocket;

@property(nonatomic, copy) ConnectionCompletion connectionCompletionOnce_;

@end

@implementationLocalUDPSocketProvider

// 本類的單例物件

staticLocalUDPSocketProvider *instance = nil;

+ (LocalUDPSocketProvider *)sharedInstance

{

if(instance == nil)

instance = [[superallocWithZone:NULL] init];

returninstance;

}

– (GCDAsyncUdpSocket *)initialLocalUDPSocket

{

NSLog(@”【IMCORE】new GCDAsyncUdpSocket中…”);

// ** Setup our socket.

self.localUDPSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];

// ** START udp socket

// 本地繫結埠合法性檢查

intport = [ConfigEntity getLocalUdpSendAndListeningPort];

if(port <0|| port >65535)

port =0;

NSError *error = nil;

// 繫結到指定埠(以便收發資料)

if(![self.localUDPSocket bindToPort:port error:&error])

{

NSLog(@”【IMCORE】localUDPSocket建立時出錯,原因是 bindToPort: %@”, error);

returnnil;

}

// 開啟收資料處理

if(![self.localUDPSocket beginReceiving:&error])

{

NSLog(@”【IMCORE】localUDPSocket建立時出錯,原因是 beginReceiving: %@”, error);

returnnil;

}

NSLog(@”【IMCORE】localUDPSocket建立已成功完成.”);

returnself.localUDPSocket;

}

。。。。。。

– (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data

fromAddress:(NSData *)address

withFilterContext:(id)filterContext

{

NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

if(msg)

NSLog(@”【UDP_SOCKET】【NOTE】>>>>>> 收到服務端的訊息: %@”, msg);

else

{

NSString *host = nil;

uint16_t port =0;

[GCDAsyncUdpSocket getHost:&host port:&port fromAddress:address];

NSLog(@”【UDP_SOCKET】RECV: Unknown message from: %@:%hu”, host, port);

}

}

– (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address

{

NSLog(@”【UDP_SOCKET】成收到的了UDP的connect反饋, isCOnnected?%d”, [sock isConnected]);

// 連線結果回撥

if(self.connectionCompletionOnce_ != nil)

self.connectionCompletionOnce_(YES);

}

– (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error

{

NSLog(@”【UDP_SOCKET】成收到的了UDP的connect反饋,但連線沒有成功, isCOnnected?%d”, [sock isConnected]);

// 連線結果回撥

if(self.connectionCompletionOnce_ != nil)

self.connectionCompletionOnce_(NO);

}

@end

補充說明:�因程式碼較多,文中沒有列出該類的所有程式碼,請前往文末下載完整XCode工程自行檢視哦。

[3] 資料傳送實現類 LocalUDPDataSender.m:

//  Copyright (C) 2016 即時通訊網(52im.net)- 即時通訊開發者社群.

//  All rights reserved.

//  Created by JackJiang on 16/06/22.

#import”LocalUDPDataSender.h”

#import”CharsetHelper.h”

#import”GCDAsyncUdpSocket.h”

#import”LocalUDPSocketProvider.h”

#import”ConfigEntity.h”

#import”UDPUtils.h”

#import”CompletionDefine.h”

@implementationLocalUDPDataSender

// 本類的單例物件

staticLocalUDPDataSender *instance = nil;

– (BOOL) send:(NSData *)dataWithBytes

{

// 獲得UDPSocket例項

GCDAsyncUdpSocket *ds = [[LocalUDPSocketProvider sharedInstance] getLocalUDPSocket];

// 確保傳送資料開始前,已經進行connect的操作:如果Socket沒有“連線”上服務端,嘗試“連線”一次

if(ds != nil && ![ds isConnected])

{

// 此次資料只在“連線”成功後發出,“連線”成功則會呼叫此回撥block程式碼

ConnectionCompletion observerBlock = ^(BOOL connectResult) {

// 成功建立了UDP連線後就把包發出去

if(connectResult)

[UDPUtils sendImpl:ds withData:dataWithBytes];

};

// 調置連線回撥

[[LocalUDPSocketProvider sharedInstance] setConnectionObserver:observerBlock];

NSError *connectError = nil;

BOOL connectResult = [[LocalUDPSocketProvider sharedInstance] tryConnectToHost:&connectError withSocket:ds completion:observerBlock];

// 如果連線意圖沒有成功發出則返回錯誤碼

returnconnectResult;

}

else

return[UDPUtils sendImpl:ds withData:dataWithBytes];

}

// 獲取本類的單例。使用單例訪問本類的所有資源是唯一的合法途徑。

+ (LocalUDPDataSender *)sharedInstance

{

if(instance == nil)

instance = [[superallocWithZone:NULL] init];

returninstance;

}

@end

服務端準備工作

本文將分別基於MINA2和Netty4實現兩套服務端(你只需要使用其中之一即可),服務端準備工作已在本系列文章的前兩篇詳細記錄了,具體如下:

– Netty4實現服務端的準備工作請見:《NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示

– MINA2實現服務端的準備工作請見:《NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示

服務端程式碼實現

因兩套方案的服務端程式碼都不復雜,且已經本系列文章的前兩篇中詳細介紹,本文就不在重複貼上了。

– Netty4實現的服務端請見:《NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示

– MINA2實現的服務端請見:《NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示

Demo 執行行截圖

[1] 客戶端執行結果:

iOS客戶端執行結果

[2] 服務端執行結果(MINA2方案):

服務端執行結果(MINA2方案)

[3] 服務端執行結果(Netty4方案):

服務端執行結果(Netty4方案)

本文小結

本文中的客戶端程式碼是從開源即時通訊框架MobileIMSDK的iOS端中複製出來的(只是為了方便理解而做了大幅簡化),有興趣的可以看看 MobileIMSDKAndroid端Server端,簡化一下可以用作你自已的各種用途。

如果你閱讀過本系列的《NIO框架入門(一):服務端基於Netty4的UDP雙向通訊Demo演示》和《NIO框架入門(二):服務端基於MINA2的UDP雙向通訊Demo演示》,應該能明顯地感覺的出來MINA2的UDP服務端API介面使用要是Netty4的繁瑣,而且MINA2還存在獨立客戶端(非依賴於MINA2客戶端)實現時的多餘位元組和亂碼問題。但個人認為MINA2的程式碼風格更符合一般程式設計師的編碼習慣,更好懂一些,而Netty4因歷經多個大版本的進化,雖起來非常簡潔,但實現並不是那麼直觀。當然,至於MINA還是Netty,請客觀一評估和使用,因為二者並無本質區別。

更多學習資源

[1] MINA和Netty的原始碼線上學習和查閱:

MINA-2.x地址是:http://docs.52im.net/extend/docs/src/mina2/

MINA-1.x地址是:http://docs.52im.net/extend/docs/src/mina1/

Netty-4.x地址是:http://docs.52im.net/extend/docs/src/netty4/

Netty-3.x地址是:http://docs.52im.net/extend/docs/src/netty3/

[2] MINA和Netty的API文件線上查閱:

MINA-2.x API文件(線上�版):http://docs.52im.net/extend/docs/api/mina2/

MINA-1.x API文件(線上�版):http://docs.52im.net/extend/docs/api/mina1/

Netty-4.x API文件(線上�版):http://docs.52im.net/extend/docs/api/netty4/

Netty-3.x API文件(線上�版):http://docs.52im.net/extend/docs/api/netty3/

[3] 更多有關NIO程式設計的資料:

請進入精華資料專輯:http://www.52im.net/forum.php?mod=collection&action=view&ctid=9

[4] 有關IM聊天應用、訊息推送技術的資料:

請進入精華資料專輯:http://www.52im.net/forum.php?mod=collection&op=all

[5] 技術交流和學習:

可直接進入即時通訊開發者社群討論和學習網路程式設計、IM聊天應用、訊息推送應用的開發。

完整原始碼工程下載

部落格園貌似上傳不了附件,如需完整Eclipse原始碼工程請聯絡作者,或者進入連結http://www.52im.net/thread-378-1-1.html自行下載。

完整原始碼工程截圖如下:

iOS客戶端的Demo原始碼
服務端(MINA2和Netty4兩個方案)

(本文同步釋出於:http://www.52im.net/thread-378-1-1.html)

作者:Jack Jiang(點選作者姓名進入Github)

出處:http://www.52im.net/thread-373-1-1.html

聯絡方式:QQ: 413980957, 微信: hellojackjiang,Email: jb2011@163.com

Jack Jiang同時是【原創Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。


相關文章