Netty简介
Netty是一款异步的事件驱动的网络应用程序框架,支持快速开发可维护的高性能的面向协议的服务器和客户端。Netty主要是对java 的 nio包进行的封装
为什么要使用 Netty
上面介绍到 Netty是一款 高性能的网络通讯框架,那么我们为什么要使用Netty,换句话说,Netty有哪些优点让我们值得使用它,为什么不使用原生的 Java Socket编程,或者使用 Java 1.4引入的 Java NIO。接下来分析分析 Java Socket编程和 Java NIO。
Java 网络编程
首先来看一个Java 网络编程的例子:
public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(8888); Socket socket = serverSocket.accept(); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = ""; while ((line = reader.readLine()) != null) { System.out.println("received: " + line); } } catch (IOException e) { e.printStackTrace(); } } 复制代码
上面展示了一个简单的Socket服务端例子,该代码只能同时处理一个连接,要管理多个并发客户端,需要为每个新的客户端Socket创建一个 新的Thread。这种并发方案对于中小数量的客户端来说还可以接受,如果是针对高并发,超过100000的并发连接来说该方案并不可取,它所需要的线程资源太多,而且任何时候都可能存在大量线程处于阻塞状态,等待输入或者输出数据就绪,整个方案性能太差。所以,高并发的场景,一般的Java 网络编程方案是不可取的。
Java NIO
还是先来看一个 Java NIO的例子:
public class ServerSocketChannelDemo {
private ServerSocketChannel serverSocketChannel; private Selector selector; public ServerSocketChannelDemo(int port) throws IOException { serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(port)); selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); } public void listener() throws IOException { while (true) { int n = selector.select(); if (n == 0) { continue; } Iterator iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = (SelectionKey) iterator.next(); if (selectionKey.isAcceptable()) { ServerSocketChannel server_channel = (ServerSocketChannel) selectionKey.channel(); SocketChannel socketChannel = server_channel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } if (selectionKey.isReadable()) { //如果通道处于读就绪的状态 //读操作 //TODO } } } } } 复制代码
NIO的核心部分主要有:
- 通道 Channel
- 缓冲区 Buffer
- 多路复用器 Selector
选择器 Selector 是 Java 非阻塞 I/O实现的关键,将通道Channel注册在 Selector上,如果某个通道 Channel发送 读或写事件,这个Channel处于就绪状态,会被Selector轮询出来,进而进行后续I/O操作。这种I/O多路复用的方式相比上面的阻塞 I/O模型,提供了更好的资源管理:
- 使用较少的线程便可以处理很多连接,因此也减少了内存管理和上下文切换所带来的开销
- 当没有I/O操作需要处理的时候,线程也可以被用于其他任务。
尽管使用 Java NIO可以让我们使用较少的线程处理很多连接,但是在高负载下可靠和高效地处理和调度I/O操作是一项繁琐而且容易出错的任务,所以才引出了高性能网络编程专家——Netty
Netty特性
Netty有很多优秀的特性值得让我们去使用它(摘自《Netty实战》):
设计
- 统一的API,适用于不同的协议(阻塞和非阻塞)
- 基于灵活、可扩展的事件驱动模型
- 高度可定制的线程模型
- 可靠的无连接数据Socket支持(UDP)
性能
- 更好的吞吐量,低延迟
- 更低的资源消耗
- 最少的内存复制
健壮性
- 不再因过快、过慢或超负载连接导致OutOfMemoryError
- 不再有在高速网络环境下NIO读写频率不一致的问题
安全性:
- 完整的SSL/TLS和STARTTLS的支持
- 可用于受限环境下,如 Applet 和OSGI
易用:
- 详实的Javadoc和大量的示例集
- 不需要超过 JDK 1.6+的依赖
Netty示例代码
下面是server 和client的示例代码,先来看看Netty代码是怎么样的,后续文章会详细分析各个模块。
Server代码
public class EchoServer {
private final int port; public EchoServer(int port) { this.port = port; } public static void main(String[] args) throws InterruptedException { new EchoServer(8888).start(); } public void start() throws InterruptedException { final EchoServerHandler serverHandler = new EchoServerHandler(); //创建EventLoopGroup,处理事件 EventLoopGroup boss = new NioEventLoopGroup(); EventLoopGroup worker = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(boss,worker) //指定所使用的NIO传输 Channel .channel(NioServerSocketChannel.class) //使用指定的端口设置套接字地址 .localAddress(new InetSocketAddress(port)) //添加一个EchoServerHandler到子Channel的ChannelPipeline .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //EchoServerHandler标志为@Shareable,所以我们可以总是使用同样的实例 socketChannel.pipeline().addLast(serverHandler); } }); //异步的绑定服务器,调用sync()方法阻塞等待直到绑定完成 ChannelFuture future = b.bind().sync(); future.channel().closeFuture().sync(); } finally { //关闭EventLoopGroup,释放所有的资源 group.shutdownGracefully().sync(); worker.shutdownGracefully().sync(); } } } 复制代码
EchoServerHandler
@ChannelHandler.Sharable //标识一个 ChannelHandler可以被多个Channel安全地共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buffer = (ByteBuf) msg; //将消息记录到控制台 System.out.println("Server received: " + buffer.toString(CharsetUtil.UTF_8)); //将接受到消息回写给发送者 ctx.write(buffer); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { //将未消息冲刷到远程节点,并且关闭该 Channel ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) .addListener(ChannelFutureListener.CLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //打印异常栈跟踪 cause.printStackTrace(); //关闭该Channel ctx.close(); } } 复制代码
代码要点解读:
ServerBootStrap
是引导类,帮助服务启动的辅助类,可以设置 Socket参数EventLoopGroup
是处理I/O操作的线程池,用来分配 服务于Channel的I/O和事件的EventLoop
,而NioEventLoopGroup
是EventLoopGroup
的一个实现类。这里实例化了两个NioEventLoopGroup
,一个boss
,主要用于处理客户端连接,一个worker
用于处理客户端的数据读写工作EchoServerHandler
实现了业务逻辑- 通过调用
ServerBootStrap.bind()
方法以绑定服务器
Client 代码
public class EchoClient {
private final String host; private final int port; public EchoClient(String host, int port) { this.host = host; this.port = port; } public void start() throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .remoteAddress(new InetSocketAddress(host, port)) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { socketChannel.pipeline().addLast(new EchoClientHandler()); } }); ChannelFuture channelFuture = b.connect().sync(); channelFuture.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } public static void main(String[] args) throws InterruptedException { new EchoClient("127.0.0.1", 8888).start(); } } 复制代码
EchoClientHandler
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception { System.out.println("Client received: "+byteBuf.toString()); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks",CharsetUtil.UTF_8)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 复制代码
代码要点解读:
- 为初始化客户端,创建了一个BootStrap实例,与
ServerBootStrap
一样,也是一个引导类,主要辅助客户端 - 分配了一个
NioEventLoopGroup
实例,里面的EventLoop
,处理连接的生命周期中所发生的事件 EchoClientHandler
类负责处理业务逻辑,与服务端的EchoSeverHandler
作用相似。
参考资料 & 鸣谢
作者:pjmike_pj
链接:https://juejin.im/post/5ba35a76f265da0a951ed4db