likes
comments
collection
share

Netty封装了NIO的很多细节,使用更简单?简单在哪里?

作者站长头像
站长
· 阅读数 35

Netty封装了NIO的很多细节,使得我们在使用NIO进行网络编程时更加简单和高效。但是,对于初学者而言,理解Netty的工作原理和使用方式可能比较困难,需要花费一定的学习时间和精力。相对于直接使用Java NIO来说,Netty确实会增加一些学习成本。

​那么为什么还说Netty使用更简单呢?主要原因如下:

  1. Netty将复杂的NIO操作封装在自己的框架中,开发者只需要关注业务逻辑的实现,而不需要关注底层的网络通信细节,这大大简化了网络编程的复杂度。

  2. Netty提供了一套完善的API和组件,开发者可以快速构建高性能的网络应用程序。

  3. Netty的代码结构清晰,注释详细,文档齐全,生态完善,开发者可以轻松地找到所需的资料和工具。

总之,虽然Netty的学习成本可能比较高,但它的确是一款非常优秀的网络编程框架,适用于构建高性能、可扩展的网络应用程序。

下面用一个简单的代码示例,演示如何使用Netty和Java NIO实现一个简单的网络服务器。这个服务器会监听指定的端口,一旦有客户端连接上来,就会向客户端发送一条欢迎消息,并且将客户端发送的消息原样返回。

使用Netty实现的代码如下:

public class EchoServer {
    private static final Logger logger = LoggerFactory.getLogger(EchoServer.class);
    private final int port;
    // 构造函数,传入端口号
    public EchoServer(int port) {
        this.port = port;
    }
    // 启动服务器的方法
    public void start() throws Exception {
        // 创建事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 创建服务端启动器
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(group) // 设置事件循环组
                    .channel(NioServerSocketChannel.class) // 设置通道类型
                    .localAddress(new InetSocketAddress(port)) // 设置本地地址
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 设置通道处理器
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoServerHandler()); // 添加处理器
                        }
                    });
            // 绑定端口并启动服务器
            ChannelFuture future = bootstrap.bind().sync();
            logger.info("EchoServer started and listening on {}", future.channel().localAddress());
            future.channel().closeFuture().sync();
            logger.info("server close");
        } finally {
            // 优雅关闭服务器
            group.shutdownGracefully().sync();
        }
    }
    // 主函数
    public static void main(String[] args) throws Exception {
        int port = 8080;
        new EchoServer(port).start(); // 启动服务器
    }
}
// 自定义处理器
class EchoServerHandler extends ChannelInboundHandlerAdapter {
    private static final Logger logger = LoggerFactory.getLogger(EchoServerHandler.class);
    // 处理连接建立事件
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("Channel active: {}", ctx.channel().remoteAddress());
    }
    // 处理读事件
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        logger.info("Server received: {}", msg);
        ctx.write(msg); // 将消息写回客户端
    }
    // 读事件读取完成后的处理
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelReadComplete");
        ctx.flush(); // 刷新缓冲区
    }
    // 处理异常事件
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("Exception caught: {}", cause.getMessage(), cause);
        ctx.close(); // 关闭通道
    }
}

使用Java NIO实现的代码如下:

```
public class NioServer {
    public static void main(String[] args) throws IOException {
        // 创建一个ServerSocketChannel对象
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 将ServerSocketChannel设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // 绑定本地地址和端口
        serverSocketChannel.bind(new InetSocketAddress("localhost", 8888));
        // 创建一个Selector对象
        Selector selector = Selector.open();
        // 将ServerSocketChannel注册到Selector上,并监听OP_ACCEPT事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server started on port 8888");
        while (true) {
            // 阻塞等待事件发生
            selector.select();
            // 获取事件集合
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                // 处理事件
                if (key.isAcceptable()) {
                    // 如果是OP_ACCEPT事件,则创建一个SocketChannel,并将它注册到Selector上
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("New client connected: " + socketChannel.getRemoteAddress());
                } else if (key.isReadable()) {
                    // 如果是OP_READ事件,则读取客户端发送的数据
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len = socketChannel.read(buffer);
                    if (len > 0) {
                        buffer.flip();
                        String msg = new String(buffer.array(), 0, len);
                        System.out.println("Received message from " + socketChannel.getRemoteAddress() + ": " + msg);
                    } else if (len == -1) {
                        // 如果读取到-1,说明客户端已经断开连接
                        System.out.println("Client disconnected: " + socketChannel.getRemoteAddress());
                        socketChannel.close();
                        key.cancel();
                    }
                }
            }
        }
    }
}
```

相对于基于Java NIO手写服务端的实现,上面使用Netty实现的服务端代码有以下简洁之处:

  1. 代码结构简单:Netty的服务端代码结构非常简单,只需要实例化一个ServerBootstrap对象,设置相关配置参数,绑定端口即可。而使用Java NIO实现服务端需要手动创建ServerSocketChannel、Selector、SelectionKey等对象,并且需要手动管理缓冲区等资源,代码结构相对复杂。
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 创建boss线程组,用于处理连接请求
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 创建worker线程组,用于处理客户端请求
try {
    ServerBootstrap b = new ServerBootstrap(); // 创建ServerBootstrap对象
    b.group(bossGroup, workerGroup) // 设置线程组
     .channel(NioServerSocketChannel.class) // 指定使用NIO进行网络通信
     .childHandler(new ChannelInitializer<SocketChannel>() { // 设置事件处理器
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ch.pipeline().addLast(new MyServerHandler()); // 添加自定义的事件处理器
         }
     })
     .option(ChannelOption.SO_BACKLOG, 128) // 设置TCP参数
     .childOption(ChannelOption.SO_KEEPALIVE, true); // 设置TCP参数
    // 绑定端口并启动服务
    ChannelFuture f = b.bind(port).sync();
    // 等待服务端监听端口关闭
    f.channel().closeFuture().sync();
} finally {
    workerGroup.shutdownGracefully(); // 释放线程池资源
    bossGroup.shutdownGracefully(); // 释放线程池资源
}
  1. 事件处理代码简单:在Netty中,使用childHandler方法可以将所有相关的事件处理代码放在一个地方,包括读写事件、异常事件等。而在Java NIO中,需要手动编写所有事件处理代码,代码量比较大,维护成本高。
public class MyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // 处理读操作
        // 处理读事件
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // 处理异常操作
        // 处理异常事件
    }
}
  1. I/O操作简单:Netty自动进行缓冲区管理和数据编解码等操作,代码量少,可读性高。而Java NIO需要手动进行缓冲区管理和数据编解码等操作,代码量相对较大,可读性较差。
public class MyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // msg已经被自动解码了
        // 处理读操作
        ByteBuf in = (ByteBuf) msg;
        System.out.println(in.toString(CharsetUtil.UTF_8)); 
        // 输出客户端发送的信息
    }
    @Override
    public void channelActive(final ChannelHandlerContext ctx) { // 通道激活时执行
        // 写入数据时,不需要手动进行编码操作
        final ByteBuf time = ctx.alloc().buffer(4); // 创建缓冲区
        time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L)); // 向缓冲区写入数据
        final ChannelFuture f = ctx.writeAndFlush(time); // 发送数据
        f.addListener(new ChannelFutureListener() { // 监听发送结果
            @Override
            public void operationComplete(ChannelFuture future) {
                assert f == future;
                ctx.close(); // 发送完毕后关闭通道
            }
        }); // (4)
    }
}
  1. 功能丰富:Netty提供了许多其他功能,例如编码器和解码器等,这些功能可以简化I/O操作并提高性能。而Java NIO只提供了基本的I/O操作,需要手动编写其他功能的实现。
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 添加编解码器
        pipeline.addLast(new MyMessageDecoder());
        pipeline.addLast(new MyMessageEncoder());
        // 添加业务处理器
        pipeline.addLast(new MyServerHandler());
    }
}

综上所述,相对于基于Java NIO手写服务端的实现,使用Netty实现服务端代码更简洁,更易于维护,并且还提供了许多其他功能,可以提高开发效率和程序性能。

以上代码可正常运行,有疑问可以交流~~

欢迎关注公众号:程序员的思考与落地

公众号提供大量实践案例,Java入门者不容错过哦,可以陪聊!!

转载自:https://juejin.cn/post/7217053578710761529
评论
请登录