【原创】NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示
申明:本文由作者基于日常实践整理,希望对初次接触MINA、Netty的人有所启发。如需与作者交流,见文签名,互相学习。
学习交流
- 更多学习资料:点此进入 推荐
 - 移动端即时通讯交流:
 215891622 推荐 
前言
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 篇,目录如下:
本文亮点
[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
public class EchoServer
{
public static void main(String[] args) throws InterruptedException
{
Bootstrap b = new Bootstrap();
EventLoopGroup group = new NioEventLoopGroup();
b.group(group)
.channel(NioDatagramChannel.class)
.handler(new EchoSeverHandler());
// 服务端监听在9999端口
b.bind(9999).sync().channel().closeFuture().await();
}
}
如上述代码所示:不得不说,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
public class EchoSeverHandler extends SimpleChannelInboundHandler<DatagramPacket>
{
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
throws Exception
{
// 读取收到的数据
ByteBuf buf = (ByteBuf) packet.copy().content();
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, CharsetUtil.UTF_8);
System.out.println("【NOTE】>>>>>> 收到客户端的数据:"+body);
// 回复一条信息给客户端
ctx.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer("Hello,我是Server,我的时间戳是"+System.currentTimeMillis()
, CharsetUtil.UTF_8)
, packet.sender())).sync();
}
}
如上所示,这个Handler看起来比MINA的类似实现,要简洁不少。
客户端代码
为了让客户端代码看起来更简洁,我把Socket管理代码提炼到 LocalUDPSocketProvider类、把UDP数据监听和接收提炼到了 LocalUDPDataReciever类(实际上这两个同名类是简化自MobileIMSDK工程哦)。
[1] 客户端主类 EchoClient.java
public class EchoClient
{
public static void main(String[] args) throws Exception
{
// 初始化本地UDP的Socket
LocalUDPSocketProvider.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", "发往服务端的信息已送出.");
else
Log.e("EchoClient", "发往服务端的信息没有成功发出!!!");
// 3000秒后进入下一次循环
Thread.sleep(3000);
}
}
}
补充说明:客户端代码没有使用任何依赖,纯Java UDP代码实现(如果是Andriod平台,代码也几乎不用改就能用),部分代码修改自 开源即时通讯框架 MobileIMSDK(去掉了很多保证健壮性代码,现在看起来要简洁的多,便于初学者学习)。
[2] Socket操作类 LocalUDPSocketProvider.java
public class LocalUDPSocketProvider
{
private static final String TAG = LocalUDPSocketProvider.class.getSimpleName();
private static LocalUDPSocketProvider instance = null;
private DatagramSocket localUDPSocket = null;
public static LocalUDPSocketProvider getInstance()
{
if (instance == null)
instance = new LocalUDPSocketProvider();
return instance;
}
public void initSocket()
{
try
{
// UDP本地监听端口(如果为0将表示由系统分配,否则使用指定端口)
this.localUDPSocket = new DatagramSocket(ConfigEntity.localUDPPort);
// 调用connect之后,每次send时DatagramPacket就不需要设计目标主机的ip和port了
// * 注意:connect方法一定要在DatagramSocket.receive()方法之前调用,
// * 不然整send数据将会被错误地阻塞。这或许是官方API的bug,也或许是调
// * 用规范就应该这样,但没有找到官方明确的说明
this.localUDPSocket.connect(
InetAddress.getByName(ConfigEntity.serverIP), ConfigEntity.serverUDPPort);
this.localUDPSocket.setReuseAddress(true);
Log.d(TAG, "new DatagramSocket()已成功完成.");
}
catch (Exception e)
{
Log.w(TAG, "localUDPSocket创建时出错,原因是:" + e.getMessage(), e);
}
}
public DatagramSocket getLocalUDPSocket()
{
return this.localUDPSocket;
}
}
[3] 数据接收类 LocalUDPDataReciever.java
public class LocalUDPDataReciever
{
private static final String TAG = LocalUDPDataReciever.class.getSimpleName();
private static LocalUDPDataReciever instance = null;
private Thread thread = null;
public static LocalUDPDataReciever getInstance()
{
if (instance == null)
instance = new LocalUDPDataReciever();
return instance;
}
public void startup()
{
this.thread = new Thread(new Runnable()
{
public void run()
{
try
{
Log.d(LocalUDPDataReciever.TAG, "本地UDP端口侦听中,端口=" + ConfigEntity.localUDPPort + "...");
//开始侦听
LocalUDPDataReciever.this.udpListeningImpl();
}
catch (Exception eee)
{
Log.w(LocalUDPDataReciever.TAG, "本地UDP监听停止了(socket被关闭了?)," + eee.getMessage(), eee);
}
}
});
this.thread.start();
}
private void udpListeningImpl() throws Exception
{
while (true)
{
byte[] data = new byte[1024];
// 接收数据报的包
DatagramPacket packet = new DatagramPacket(data, data.length);
DatagramSocket localUDPSocket = LocalUDPSocketProvider.getInstance().getLocalUDPSocket();
if ((localUDPSocket == null) || (localUDPSocket.isClosed()))
continue;
// 阻塞直到收到数据
localUDPSocket.receive(packet);
// 解析服务端发过来的数据
String pFromServer = new String(packet.getData(), 0 , packet.getLength(), "UTF-8");
Log.w(LocalUDPDataReciever.TAG, "【NOTE】>>>>>> 收到服务端的消息:"+pFromServer);
}
}
}
运行效果
客户端运行结果:
服务端运行结果:
补充说明
客户端代码没什么特别,就是标准的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 自行下载。
完整源码工程截图如下:

(本文同步发布于:http://www.52im.net/thread-367-1-1.html)
【原创】NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示的更多相关文章
- 【原创】NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示
		
前言 NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能.这其中最流行的无非就是MINA和Netty了,MINA目前的主要版本是MINA2.而Netty的主要版本是Netty3和Netty ...
 - 【原创】NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战
		
概述 本文演示的是一个Android客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo. 当前由于NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能. ...
 - 【原创】NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战
		
前言 本文将演示一个iOS客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo.服务端将分别用MINA2和Netty4进行实现,而通信时服务端你只需选其一就行了.同 ...
 - 部署基于.netcore5.0的ABP框架后台Api服务端,以及使用Nginx部署Vue+Element前端应用
		
前面介绍了很多关于ABP框架的后台Web API 服务端,以及基于Vue+Element前端应用,本篇针对两者的联合部署,以及对部署中遇到的问题进行处理.ABP框架的后端是基于.net core5.0 ...
 - gRPC (1):入门及服务端创建和调用原理
		
1. RPC 入门 1.1 RPC 框架原理 RPC 框架的目标就是让远程服务调用更加简单.透明,RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP).序列化方式(XML/Json/ 二进制)和 ...
 - day112:MoFang:种植园使用websocket代替http&服务端基于flask-socketio提供服务&服务端响应信息&种植园页面显示初始化
		
目录 1.种植园使用websocket代替http 2.服务端基于socket提供服务 3.服务端响应信息 4.种植园页面展示 1.种植园使用websocket代替http 我们需要完成的种植园,是一 ...
 - 前端学习 node 快速入门 系列 —— 服务端渲染
		
其他章节请看: 前端学习 node 快速入门 系列 服务端渲染 在简易版 Apache一文中,我们用 node 做了一个简单的服务器,能提供静态资源访问的能力. 对于真正的网站,页面中的数据应该来自服 ...
 - 基于vue的nuxt框架cnode社区服务端渲染
		
nuxt-cnode 基于vue的nuxt框架仿的cnode社区服务端渲染,主要是为了seo优化以及首屏加载速度 线上地址 http://nuxt-cnode.foreversnsd.cngithub ...
 - 基于netty实现rpc框架-spring boot服务端
		
demo地址 https://gitee.com/syher/grave-netty RPC介绍 首先了解一下RPC:远程过程调用.简单点说就是本地应用可以调用远程服务器的接口.那么通过什么方式调用远 ...
 
随机推荐
- 代码规范、GitHub提交源码的标准  答题人-杨宇杰
			
1.格式与命名规范1.1 缩进 使用Tab缩进,而不是空格键1.2 换行 每行120字符 if,for,while语句只有单句时,如果该句可能引起阅读混淆,需要用" {"和&quo ...
 - npm下载速度太慢
			
因为墙的原因导致下载速度过慢,解决方式就是使用国内镜像 使用淘宝的镜像 npm config set registry="http://registry.npm.taobao.org/&qu ...
 - Mysql示例数据库employees.sql导入问题
			
Mysql版本:Server version: 5.7.9-log MySQL Community Server (GPL) 问题一.Unknown system variable 'storage_ ...
 - Global文件编译发布,代码不执行的问题与解决
			
问题:在Application_BeginRequest添加防止跨站点注入的过滤代码,VS2005编译成DLL发布后发现代码不会被执行: 环境:windows server 2008 r2 x64位 ...
 - [Django] Setting up Django Development Environment in Ubuntu 14.04
			
1. Python Of course you will need Python. Still Python 2.7 is preferred, however if you would like t ...
 - 比较C++和C#的一个性能问题
			
C++:只要你的代码正确,算法良好,你比较少关注性能问题,编译器会替你搞定绝大部分工作 C#:你的代码正确,算法良好,你还得用工具去分析优化性能,JIT为了快速工作,很多优化工作没有深入开展. 手工优 ...
 - Java实现四则运算,使用堆栈,检查语法
			
突然发闲想试一试自己实现算术的四则运算,支持加减乘除和括号.正负号:支持语法检查:思路很常规,利用两个堆栈,一个压操作符,一个压操作数,念头冒出来之后,立马动手:然后本以为很容易的一个实现,却存在各种 ...
 - OpenMP之求和(用section分块完成)
			
// Sum_section.cpp : 定义控制台应用程序的入口点. //section功能:; //1.指定其内部的代码划分给线程中某个线程,不同的section由不同的线程执行; //2.将一个 ...
 - Ubuntu下eclipse开发hadoop应用程序环境配置
			
第一步:下载eclipse-jee-kepler-SR2-linux-gtk-x86_64.tar.gz 注意:如果电脑是64位,就下载linux下的64位eclipse,不要下载32位的eclips ...
 - c++的转换
			
1.静态转换 static_cast 用于明确定义的变换 ,包括 编译器允许的非强制转换和不太安全但定义清楚的变换.ps:(非强制变换,窄化变换,隐式转换,类层次静态定位,void*强制转换) 2.常 ...