NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示
2019獨角獸企業重金招聘Python工程師標準>>>
前言
NIO框架的流行,使得開發大并發、高性能的互聯網服務端成為可能。這其中最流行的無非就是MINA和Netty了,MINA目前的主要版本是MINA2、而Netty的主要版本是Netty3和Netty4(Netty5已經被取消開發了:詳見此文)。
本文將演示的是一個基于Netty4的UDP服務端和一個標準UDP客戶端(Java實現)雙向通信的完整例子。實際上,Netty4的UDP例子非常難找(恕我愚鈍,找遍全網也沒有有價值的代碼,有也是Netty3,而Netty3和Netty4的風格差的不是一點點,參考意義不大),官方的代碼演示里只有一個簡單的UDP廣播例子,不足以用于演示Netty4的UDP通信最佳實踐。
學習交流
- 更多即時通訊技術資料:http://www.52im.net/forum.php?mod=collection&op=all
- 移動端即時通訊交流群:215891622?推薦
《NIO框架入門》系列文章目錄
有關MINA和Netty的入門文章很多,但多數都是復制、粘貼的未經證實的來路不明內容,對于初次接觸的人來說,一個可以運行且編碼規范的Demo,顯然要比各種“詳解”、“深入分析”之類的要來的直接和有意義。本系列入門文章正是基于此種考慮而寫,雖無精深內容,但至少希望對初次接觸MINA、Netty的人有所啟發,起到拋磚引玉的作用。
本文是《NIO框架入門》系列文章中的第?4?篇,目錄如下:
-
- 《NIO框架入門(一):服務端基于Netty4的UDP雙向通信Demo演示》(本文)
- 《NIO框架入門(二):服務端基于MINA2的UDP雙向通信Demo演示》
- 《NIO框架入門(三):iOS與MINA2、Netty4的跨平臺UDP雙向通信實戰》
- 《NIO框架入門(四):Android與MINA2、Netty4的跨平臺UDP雙向通信實戰》
本文亮點
[1] Netty4的UDP例子太難找:
Netty4的完整雙向UDP通信例子很難找(官方沒有),本文就是要用代碼來演示這個;
[2] 本例中客戶端UDP實現無需第3方依賴:
通常MINA或Netty的例子里,客戶端也都是用的MINA或Netty的客戶端lib,本文將直接使用標準UDP代碼(便于跨平臺實現,比如iOS平臺),不依賴于第3方包;
[3] 完整可執行源碼、方便學習:
完整的Demo源碼,適合新手直接運行,便于學習和研究。
?
題外話
社區里經常在爭論到底該用MINA還是Netty,比如這幾篇文章:《開源NIO框架八卦——到底是先有MINA還是先有Netty?》、《有關“為何選擇Netty”的11個疑問及解答》、《選Netty還是Mina:深入研究與對比(一)》,哈哈這確實挺難抉擇的。不過,個人觀點是,對那個熟悉就用哪個吧,沒什么好糾結的,必竟二者并比本質區別,也都是出自同一作者之手。
?
寫個什么樣的Demo?
言歸正傳,本文要演示的Demo包含兩部分,Java UDP客戶端和Netty4 UDP服務端,客戶端將每隔3秒向服務端發送一條消息,而服務端在收到消息后馬上回復一條消息給客戶端。
也就是說,服務端和客戶端都要實現消息的發送和接收,這也就實現了雙向通信。如果有心的話,稍加改造,也就很容易實現一個簡陋的聊天程序了。下節將將給出真正的實現代碼。
?
Netty4 服務端準備工作
[1] 第一步:下載Netty4
Netty4的官方網站是:http://netty.io/,直接下最新的Netty 4.1吧,如下圖:
[2] 第二步:找到Netty4的核心庫文件
直接用這個all in one的jar包吧,反正用在服務端,2M大小的東西無所謂,方便管理,見下圖:
[3] 第三步:建好Java工程準備準備開擼
個人習慣用Eclipse,你用NetBeans或InteliJ也都沒問題,具體建立過程不熟練的衣自行百度吧,唯一注意的是把Netty4的lib包引用進來就行了,我的見下圖:
服務端代碼
[1] 服務端主類 EchoServer.java
1 public class EchoServer2 {3 ????????public static void main(String[] args) throws InterruptedException4 ????????{5 ????????????????Bootstrap b = new Bootstrap();6 ????????????????EventLoopGroup group = new NioEventLoopGroup();7 ????????????????b.group(group)8 ????????????????????????.channel(NioDatagramChannel.class)9 ????????????????????????.handler(new EchoSeverHandler()); 10 ????????????????? 11 ????????????????// 服務端監聽在9999端口 12 ????????????????b.bind(9999).sync().channel().closeFuture().await(); 13 ????????} 14 }如上述代碼所示:不得不說,Netty4的UDP服務端代碼實現起來確實很簡單,一個?Bootstrap、一個?EventLoopGroup、外加一個SimpleChannelInboundHandler,就這么被Netty4輕松搞定(準確地說Netty4是在Java NIO上的封裝而已,但最終API對開發者來說確實很友好)。
吐個槽:話說Netty4的代碼跟MINA相比,風格確實大不相同,雖說兩者有很深的源源,但經過Netty3、Netty4的進化,兩者的差異(至少代碼看起來是這樣)還是很明顯的。另外,b.bind(9999).sync().channel().closeFuture().await()?這一大串的連續方法調用,看起來很詭異,不小心調錯的話,服務器會不會爆炸?
[2] 服務端Handler類 EchoSeverHandler.java
1 public class EchoSeverHandler extends SimpleChannelInboundHandler<DatagramPacket> 2 {3 ????????@Override4 ????????protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)5 ????????????????????????throws Exception6 ????????{7 ????????????????// 讀取收到的數據8 ????????????????ByteBuf buf = (ByteBuf) packet.copy().content();9 ????????????????byte[] req = new byte[buf.readableBytes()]; 10 ????????????????buf.readBytes(req); 11 ????????????????String body = new String(req, CharsetUtil.UTF_8); 12 ????????????????System.out.println("【NOTE】>>>>>> 收到客戶端的數據:"+body); 13 ????????????????? 14 ????????????????// 回復一條信息給客戶端 15 ????????????????ctx.writeAndFlush(new DatagramPacket( 16 ????????????????Unpooled.copiedBuffer("Hello,我是Server,我的時間戳是"+System.currentTimeMillis() 17 ????????????????????????????????, CharsetUtil.UTF_8) 18 ????????????????????????????????, packet.sender())).sync(); 19 ????????} 20 }如上所示,這個Handler看起來比MINA的類似實現,要簡潔不少。
客戶端代碼
為了讓客戶端代碼看起來更簡潔,我把Socket管理代碼提煉到 LocalUDPSocketProvider類、把UDP數據監聽和接收提煉到了 LocalUDPDataReciever類(實際上這兩個同名類是簡化自MobileIMSDK工程哦)。
[1] 客戶端主類 EchoClient.java
public class EchoClient {public static void main(String[] args) throws Exception{// 初始化本地UDP的SocketLocalUDPSocketProvider.getInstance().initSocket();// 啟動本地UDP監聽(接收數據用的)LocalUDPDataReciever.getInstance().startup();// 循環發送數據給服務端while(true){// 要發送的數據String toServer = "Hi,我是客戶端,我的時間戳"+System.currentTimeMillis();byte[] soServerBytes = toServer.getBytes("UTF-8");// 開始發送boolean ok = UDPUtils.send(soServerBytes, soServerBytes.length);if(ok)Log.d("EchoClient", "發往服務端的信息已送出.");elseLog.e("EchoClient", "發往服務端的信息沒有成功發出!!!");// 3000秒后進入下一次循環Thread.sleep(3000);}} }補充說明:客戶端代碼沒有使用任何依賴,純Java UDP代碼實現(如果是Andriod平臺,代碼也幾乎不用改就能用),部分代碼修改自?開源即時通訊框架 MobileIMSDK(去掉了很多保證健壯性代碼,現在看起來要簡潔的多,便于初學者學習)。
[2] Socket操作類 LocalUDPSocketProvider.java
1 public class LocalUDPSocketProvider2 {3 ????????private static final String TAG = LocalUDPSocketProvider.class.getSimpleName();4 ????????private static LocalUDPSocketProvider instance = null;5 ????????private DatagramSocket localUDPSocket = null;6 ?7 ????????public static LocalUDPSocketProvider getInstance()8 ????????{9 ????????????????if (instance == null) 10 ????????????????????????instance = new LocalUDPSocketProvider(); 11 ????????????????return instance; 12 ????????} 13 ????????? 14 ????????public void initSocket() 15 ????????{ 16 ????????????????try 17 ????????????????{ 18 ????????????????????????// UDP本地監聽端口(如果為0將表示由系統分配,否則使用指定端口) 19 ????????????????????????this.localUDPSocket = new DatagramSocket(ConfigEntity.localUDPPort); 20 ????????????????????????// 調用connect之后,每次send時DatagramPacket就不需要設計目標主機的ip和port了 21 ????????????????????????// * 注意:connect方法一定要在DatagramSocket.receive()方法之前調用, 22 ????????????????????????// * 不然整send數據將會被錯誤地阻塞。這或許是官方API的bug,也或許是調 23 ????????????????????????// * 用規范就應該這樣,但沒有找到官方明確的說明 24 ????????????????????????this.localUDPSocket.connect( 25 ????????????????????????????????????????InetAddress.getByName(ConfigEntity.serverIP), ConfigEntity.serverUDPPort); 26 ????????????????????????this.localUDPSocket.setReuseAddress(true); 27 ????????????????????????Log.d(TAG, "new DatagramSocket()已成功完成."); 28 ????????????????} 29 ????????????????catch (Exception e) 30 ????????????????{ 31 ????????????????????????Log.w(TAG, "localUDPSocket創建時出錯,原因是:" + e.getMessage(), e); 32 ????????????????} 33 ????????} 34 ? 35 ????????public DatagramSocket getLocalUDPSocket() 36 ????????{ 37 ????????????????return this.localUDPSocket; 38 ????????} 39 }[3] 數據接收類 LocalUDPDataReciever.java
1 public class LocalUDPDataReciever2 {3 ????????private static final String TAG = LocalUDPDataReciever.class.getSimpleName();4 ????????private static LocalUDPDataReciever instance = null;5 ????????private Thread thread = null;6 ?7 ????????public static LocalUDPDataReciever getInstance()8 ????????{9 ????????????????if (instance == null) 10 ????????????????????????instance = new LocalUDPDataReciever(); 11 ????????????????return instance; 12 ????????} 13 ? 14 ????????public void startup() 15 ????????{ 16 ????????????????this.thread = new Thread(new Runnable() 17 ????????????????{ 18 ????????????????????????public void run() 19 ????????????????????????{ 20 ????????????????????????????????try 21 ????????????????????????????????{ 22 ????????????????????????????????????????Log.d(LocalUDPDataReciever.TAG, "本地UDP端口偵聽中,端口=" + ConfigEntity.localUDPPort + "..."); 23 ? 24 ????????????????????????????????????????//開始偵聽 25 ????????????????????????????????????????LocalUDPDataReciever.this.udpListeningImpl(); 26 ????????????????????????????????} 27 ????????????????????????????????catch (Exception eee) 28 ????????????????????????????????{ 29 ????????????????????????????????????????Log.w(LocalUDPDataReciever.TAG, "本地UDP監聽停止了(socket被關閉了?)," + eee.getMessage(), eee); 30 ????????????????????????????????} 31 ????????????????????????} 32 ????????????????}); 33 ????????????????this.thread.start(); 34 ????????} 35 ? 36 ????????private void udpListeningImpl() throws Exception 37 ????????{ 38 ????????????????while (true) 39 ????????????????{ 40 ????????????????????????byte[] data = new byte[1024]; 41 ????????????????????????// 接收數據報的包 42 ????????????????????????DatagramPacket packet = new DatagramPacket(data, data.length); 43 ? 44 ????????????????????????DatagramSocket localUDPSocket = LocalUDPSocketProvider.getInstance().getLocalUDPSocket(); 45 ????????????????????????if ((localUDPSocket == null) || (localUDPSocket.isClosed())) 46 ????????????????????????????????continue; 47 ????????????????????????? 48 ????????????????????????// 阻塞直到收到數據 49 ????????????????????????localUDPSocket.receive(packet); 50 ????????????????????????? 51 ????????????????????????// 解析服務端發過來的數據 52 ????????????????????????String pFromServer = new String(packet.getData(), 0 , packet.getLength(), "UTF-8"); 53 ????????????????????????Log.w(LocalUDPDataReciever.TAG, "【NOTE】>>>>>> 收到服務端的消息:"+pFromServer); 54 ????????????????} 55 ????????} 56 }運行效果
客戶端運行結果:
服務端運行結果:
補充說明
客戶端代碼沒什么特別,就是標準的Java UDP代碼,看起來跟其它例子不一樣的原因只是我把它提煉了一下,沒本質區別。同樣的代碼改改也可以很好的用在Android端。實際上,上面的客戶端代碼就是從開源 MobileIMSDK?的Java端扒出來的,有興趣的也可以看看它的Android端、Server端、iOS端,簡化一下可以用作你自已的各種用途。
補充說明
客戶端代碼沒什么特別,就是標準的Java UDP代碼,看起來跟其它例子不一樣的原因只是我把它提煉了一下,沒本質區別。同樣的代碼改改也可以很好的用在Android端。實際上,上面的客戶端代碼就是從開源 MobileIMSDK?的Java端扒出來的,有興趣的也可以看看它的Android端、Server端、iOS端,簡化一下可以用作你自已的各種用途。
?
更多學習資源
[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-367-1-1.html?自行下載。
完整源碼工程截圖如下:
轉載于:https://my.oschina.net/u/3346994/blog/909413
總結
以上是生活随笔為你收集整理的NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 产品和产品经理
- 下一篇: Objections vs. excus