Java网络通信基础系列-Netty入门
一.基于各种模型开发的难点
之所以在整个的开发之中去使用系统实现的程序类,核心的意义在于:它可以帮助开发者隐藏协议的实现细节。
但是在开发中依然会发现有如下几点:
1.程序的实现方式上的差异,因为代码的执行会有底层的实现琐碎问题;
2.在现在给定的通讯里面并没有处理长连接的问题,也就是说按照当前编写的网络通讯,一旦要发送的是稍微大一些的文件,则很大可能是无法传送完成;
3.在数据量较大的时候需要考虑粘包与拆包问题;
4.实现的协议细节操作不好控制;
5.在很多项目开发中(RCP底层实现)需要提供有大量的IO通讯的问题,如果直接使用原始的程序类,开发难度过高;
二.Netty框架
Netty的产生也是符合时代的要求,可以简化大量的繁琐的程序代码,官网:https://netty.io/
Dubbo使用了Netty作为底层的通讯实现,Netty是基于NIO实现的,也采用了线程池的概念。Netty的最新版本为"4.1.31",
一开始为了进一步提高Netty处理性能,所以研发了5.0版本,但是研发之后(修改了一些类名称和方法名称、通讯算法)发现性能没有比4.x提升多少。
所以Netty 5.x是暂时不会被更新的版本了,而现在主要更新的就是Netty 4.x。

在AIO的模型里面,会发现都采用了回调的处理形式,所以在Netty里面是基于状态的处理形式,例如:连接成功、信息的读取、失败等操作都会由一系列的状态方法来进行定义的。
1.使用Netty实现了一个Echo数据交互处理操作

EchoServerMain.java
package com.bijian.netty.server.main; import com.bijian.netty.server.EchoServer; public class EchoServerMain { public static void main(String[] args) throws Exception { new EchoServer().run(); } }
EchoServer.java
package com.bijian.netty.server; import com.bijian.netty.info.HostInfo; import com.bijian.netty.server.handler.EchoServerHandler; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * 实现了基础的线程池与网络连接的配置项 */ public class EchoServer { public void run() throws Exception { // 进行服务器端的启动处理 // 线程池是提升服务器性能的重要技术手段,利用定长的线程池可以保证核心线程的有效数量 // 在Netty之中线程池的实现分为两类:主线程池(接收客户端连接)、工作线程池(处理客户端连接) EventLoopGroup bossGroup = new NioEventLoopGroup(10); // 创建接收线程池 EventLoopGroup workerGroup = new NioEventLoopGroup(20); // 创建工作线程池 System.out.println("服务器启动成功,监听端口为:" + HostInfo.PORT); try { // 创建一个服务器端的程序类进行NIO启动,同时可以设置Channel ServerBootstrap serverBootstrap = new ServerBootstrap(); // 服务器端 // 设置要使用的线程池以及当前的Channel类型 serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class); // 接收到信息之后需要进行处理,于是定义子处理器 serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new EchoServerHandler()); // 追加了处理器 } }); // 可以直接利用常量进行TCP协议的相关配置 serverBootstrap.option(ChannelOption.SO_BACKLOG, 128); serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); // ChannelFuture描述的是异步回调的处理操作 ChannelFuture future = serverBootstrap.bind(HostInfo.PORT).sync(); future.channel().closeFuture().sync();// 等待Socket被关闭 } finally { workerGroup.shutdownGracefully() ; bossGroup.shutdownGracefully() ; } } }
EchoServerHandler.java
package com.bijian.netty.server.handler; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; /** * 处理Echo的操作方式,其中ChannelInboundHandlerAdapter是针对于数据输入的处理 * Netty是基于NIO的一种开发框架的封装,这里面和AIO是没有任何关系的。 */ public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // 当客户端连接成功之后会进行此方法的调用,明确可以给客户端发送一些信息 byte data [] = "【服务器激活信息】连接通道已经创建,服务器开始进行响应交互。".getBytes() ; // NIO是基于缓存的操作,所以Netty也提供有一系列的缓存类(封装了NIO中的Buffer) ByteBuf buf = Unpooled.buffer(data.length) ; // Netty自己定义的缓存类 buf.writeBytes(data) ; // 将数据写入到缓存之中 ctx.writeAndFlush(buf) ; // 强制性发送所有的数据 } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { // 表示要进行数据信息的读取操作,对于读取操作完成后也可以直接回应 // 对于客户端发送来的数据信息,由于没有进行指定的数据类型,所以都统一按照Object进行接收 ByteBuf buf = (ByteBuf) msg; // 默认情况下的类型就是ByteBuf类型 // 在进行数据类型转换的过程之中还可以进行编码指定(NIO的封装) String inputData = buf.toString(CharsetUtil.UTF_8); // 将字节缓冲区的内容转为字符串 String echoData = "【ECHO】" + inputData; // 数据的回应处理 // exit是客户端发送来的内容,可以理解为客户端的编码,而quit描述的是一个客户端的结束 if ("exit".equalsIgnoreCase(inputData)) { // 进行沟通的端开 echoData = "quit"; // 结束当前交互 } byte[] data = echoData.getBytes(); // 将字符串变为字节数组 ByteBuf echoBuf = Unpooled.buffer(data.length); echoBuf.writeBytes(data);// 将内容保存在缓存之中 ctx.writeAndFlush(echoBuf); // 回应的输出操作 } finally { ReferenceCountUtil.release(msg) ; // 释放缓存 } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close() ; } }
EchoClientMain.java
package com.bijian.netty.client.main; import com.bijian.netty.client.EchoClient; public class EchoClientMain { public static void main(String[] args) throws Exception { new EchoClient().run(); } }
EchoClient.java
package com.bijian.netty.client; import com.bijian.netty.info.HostInfo; import com.bijian.netty.client.handler.EchoClientHandler; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; public class EchoClient { public void run() throws Exception { // 1、如果现在客户端不同,那么也可以不使用多线程模式来处理; // 在Netty中考虑到代码的统一性,也允许你在客户端设置线程池 EventLoopGroup group = new NioEventLoopGroup(); // 创建一个线程池 try { Bootstrap client = new Bootstrap(); // 创建客户端处理程序 client.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 EchoClientHandler()); // 追加了处理器 } }); ChannelFuture channelFuture = client.connect(HostInfo.HOST_NAME, HostInfo.PORT).sync(); channelFuture.channel().closeFuture().sync() ; // 关闭连接 } finally { group.shutdownGracefully(); } } }
EchoClientHandler.java
package com.bijian.netty.client.handler; import com.bijian.netty.util.InputUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; /** * 需要进行数据的读取操作,服务器端处理完成的数据信息会进行读取 */ public class EchoClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 只要服务器端发送完成信息之后,都会执行此方法进行内容的输出操作 try { ByteBuf readBuf = (ByteBuf) msg ; String readData = readBuf.toString(CharsetUtil.UTF_8).trim() ; // 接收返回数据内容 if("quit".equalsIgnoreCase(readData)) { // 结束操作 System.out.println("【EXIT】拜拜,您已经结束了本次网络传输,再见!"); ctx.close() ; // 关闭通道 } else { System.out.println(readData); // 输出服务器端的响应内容 String inputData = InputUtil.getString("请输入要发送的消息:") ; byte [] data = inputData.getBytes() ; // 将输入数据变为字节数组的形式 ByteBuf sendBuf = Unpooled.buffer(data.length) ; sendBuf.writeBytes(data) ; // 将数据保存在缓存之中 ctx.writeAndFlush(sendBuf) ; // 数据发送 } } finally { ReferenceCountUtil.release(msg); // 释放缓存 } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close() ; } }
先启动服务端,再启动客户端,测试如下:


2.实现服务器连接状态监听
修改EchoClient.java如下:

EchoClient.java
package com.bijian.netty.client; import com.bijian.netty.info.HostInfo; import com.bijian.netty.client.handler.EchoClientHandler; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; public class EchoClient { public void run() throws Exception { // 1、如果现在客户端不同,那么也可以不使用多线程模式来处理; // 在Netty中考虑到代码的统一性,也允许你在客户端设置线程池 EventLoopGroup group = new NioEventLoopGroup(); // 创建一个线程池 try { Bootstrap client = new Bootstrap(); // 创建客户端处理程序 client.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 EchoClientHandler()); // 追加了处理器 } }); ChannelFuture channelFuture = client.connect(HostInfo.HOST_NAME, HostInfo.PORT).sync(); channelFuture.addListener(new GenericFutureListener() { @Override public void operationComplete(Future future) throws Exception { if(future.isSuccess()) { System.out.println("服务器连接已经完成,可以确保进行消息的准确传输。"); } } }) ; channelFuture.channel().closeFuture().sync() ; // 关闭连接 } finally { group.shutdownGracefully(); } } }
先启动服务端,再启客户端,运行效果如下:


posted on 2019-06-15 21:34 bijian1013 阅读(155) 评论(0) 收藏 举报
浙公网安备 33010602011771号