在上一篇文章中介绍了NIO类库简介,从本章开始都围绕Netty
展开讨论和概述……
什么是Netty
Netty
是业界有名且最流行的NIO
框架之一,健壮,稳定,高性能,可定制,可扩展在同类框架都是首屈一指,而且成功的运用在各大商业项目中,比如Hadoop
的RPC
框架avro
,当当接盘的DubboX
都在使用…
Netty 的优点
- API使用简单,开发门槛低
- 功能强大,多种解码与编码器
- 支持多种主流的通讯协议
- 定制能力强大,可以通过
ChannelHandler
对通讯框架灵活的扩展
- 相比业界主流
NIO
框架,Netty
综合评价更高
- 成熟稳定,社区活跃
Netty 缺点
编译
GIT:https://github.com/netty/netty
如果需要编译Netty
,需要最低JDK1.7
,在运行时Netty3.x
只需要JDK1.5
,同时博客参考 李林峰
大神的 《Netty权威指南第二版》…
因为主要是学习Netty
,而不是实战,同时为了更好的适配即将推出的Netty6
,用Netty5
的API也许会更好点,就当为Netty6
做技术储备吧…
如果使用Maven
,在项目中需要添加
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>5.0.0.Alpha2</version> </dependency> </dependencies>
|
Hello Netty
继续用TimeServer
与 TimeClient
为例,改造代码使用Netty
实现服务端与客户端的通讯,以及上一章博客遗留的数据丢失问题,在使用Netty
后都是不在的…
TimeServer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public static void bind(int port) { EventLoopGroup masterGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(masterGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .option(ChannelOption.TCP_NODELAY, true) .childHandler(new ChildChannelHandler()); System.out.println("绑定端口,同步等待成功......"); ChannelFuture future = bootstrap.bind(port).sync(); future.channel().closeFuture().sync(); System.out.println("等待服务端监听端口关闭......"); } catch (Exception e) { e.printStackTrace(); } finally { masterGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); System.out.println("优雅退出释放线程池......"); } }
|
1.实例化个含NIO线程的线程组,专门用来处理网络事件,管理线程
2.使用ServerBootstrap
类来初始化Netty
服务器,并且开始监听端口的socket
请求
3.根据ServerBootstrap
內封装好的方法设置服务器基础信息
4.设置服务端的连接数 ChannelOption.SO_BACKLOG
为 1024
5.设置服务端消息不延迟,立即推送 ChannelOption.TCP_NODELAY
6.配置好服务器,在服务器启动时绑定闯入的port端口,等待同步
7.优雅退出,释放资源
数据操作类ServerHandler
:在该类中对客户端发来的数据进行操作,发送给客户端数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| private static class ChildChannelHandler extends ChannelInitializer {
@Override protected void initChannel(Channel channel) throws Exception { channel.pipeline().addLast(new TimeServerHandler()); }
private static class TimeServerHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("TimeServer 接收到的消息 :" + body); ByteBuf resp = Unpooled.copiedBuffer("你在说什么呢...".getBytes()); ctx.write(resp); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } } }
public static void main(String[] args) { TimeServer.bind(4040); }
|
从ChildChannelHandler
可以看到,只需重写对应的方法,即可简单的实现一个Netty
通讯的服务端,这里的ByteBuf
可以看成是Netty
对ByteBuffer
的增强实现…
接下来编写TimeClient
程序
TimeClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static void connect(String host, int port) { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); ChannelFuture future = null; try { bootstrap.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new TimeClientHandler()); } }); future = bootstrap.connect(host, port).sync(); future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } }
|
一个很简单的connect
连接操作,在服务端我们使用的是ServerBootstrap
客户端使用 Bootstrap
就可以了,是不是很像NIO
编程中的ServerSocketChannel
与 SocketChannel
,这里与服务端不同的是它的Channel
需要设置为NioSocketChannel
,然后为其添加一个handler
…..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| private static class TimeClientHandler extends ChannelHandlerAdapter { private final ByteBuf firstMessage; public TimeClientHandler() { byte[] req = "QUERY TIME ORDER ".getBytes(); firstMessage = Unpooled.buffer(req.length); firstMessage.writeBytes(req); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(firstMessage); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("TimeClient 接收到的消息 :" + body); }
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("释放资源:" + cause.getMessage()); ctx.close(); } }
public static void main(String[] args) { TimeClient.connect("127.0.0.1", 4040); }
|
1.首先跟服务器操作类一样继承ChannelHandlerAdapter
类,重写channelRead
和channelActive
和exceptionCaught
三个方法
2.其中channelActive
方法是用来发送客户端信息的,channelRead
方法客户端是接收服务器数据的
3.先声明一个全局变量firstMessage
,用来接收客户端发出去的信息的值
4.在channelActive
方法中,把要传输的数据转化为字节数组
5.在channelRead
方法中,通过ByteBuf
接收服务端回复的内容,然后关闭当前连接(当然具体是否关闭取决于业务了,如果请求完就关闭连接是看不到释放资源的日志)
6.在exceptionCaught
方法中,可以处理一些异常情况,比如服务器关闭,该方法会监听到…
测试一下
启动 TimeServer
1 2
| 绑定端口,同步等待成功...... TimeServer 接收到的消息 :QUERY TIME ORDER
|
启动 TimeClient
然后关闭 TimeServer
1 2
| TimeClient 接收到的消息 :你在说什么呢... 释放资源:远程主机强迫关闭了一个现有的连接。
|
- 说点什么
全文代码:https://git.oschina.net/battcn/battcn-netty/tree/master/Chapter3-1/battcn-netty-1
- 个人QQ:1837307557
- battcn开源群(适合新手):391619659
微信公众号:battcn
(欢迎调戏)