Netty-数据传输秘密

本文通过示例代码对比了网络编程中OIO阻塞传输、NIO异步传输的实现,并展示了Netty如何通过统一的API简化这两种模型的编程。Netty的Oio和NioServer示例表明,通过更换EventLoopGroup和ServerSocketChannel的实现,可以在阻塞和非阻塞模型间轻松切换,提高代码复用性和简洁性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

描述

网络编程中传输的数据总是具有相同的类型: 字节,这些字节是如何流动的主要取决于我们所说的网络传输

一个帮助我们抽象底层的数据传输机制的概念,

在网络编程中主要的传输有 OIO-阻塞传输,NIO-异步传输,Local-JVM内部的异步通信Netty-异步通信

在实现这种编程的转换的时候,比如 阻塞io传输到 nio异步传输,的时候,你会发现代码往往不能进行复用。并且

自己可能要重新编写一套代码来解决问题,然而Netty 为他所有的传输实现了提供了一个通用的API,这相较于JDK

所提供的简单的很多,接下来我们就来编写一下代码来看看,Netty 是如何方便。

代码实例

OIO 阻塞传输:

/**
 * @Author qrn
 * @Title https://blog.youkuaiyun.com/qq_41971087
 * @Date 2021/5/24 17:35
 * @time 17:35
 * 使用 oio 进行 服务端与客户端连接通信,并且 发送一个简单 hi 给客户端
 * 传输 实例:
 *
 * 1.创建 socker 服务,定义好ip 和端口
 * 2.获取客户端链接accept
 * 3.循环一直等待请求连接,使用 线程 ,每一个请求都创建一个线程去处理
 * 4. 连接成功后,写入数据 hi到 客户端
 * 5.关闭流
 */
public class PlainOioServer {


    /**
     * 启动server 方法
     * @param port 端口:
     * @throws IOException
     */
    public static  void server(int port) throws IOException{
        final ServerSocket socket = new ServerSocket(port);
        try{
            for(;;){
               final Socket clientSocket = socket.accept();
                System.out.println("Accpted connection from "+clientSocket);
                //创建线程去处理:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        OutputStream out;
                        try{
                            out =  clientSocket.getOutputStream();
                            //写数据到 客户端
                            out.write("Hi ! \r\n".getBytes(Charset.forName("UTF-8")));
                            out.flush();
                            clientSocket.close();
                        }catch (IOException e){
                            e.printStackTrace();
                        }finally {
                            try{
                                clientSocket.close();
                            }catch (IOException ex){
                                // ignorte on close
                            }
                        }
                    }
                }).start();

            }
        }catch (IOException e){
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws  Exception{
        server(8080);

    }


}

NIO 异步传输

/**
 * @Author qrn
 * @Title https://blog.youkuaiyun.com/qq_41971087
 * @Date 2021/5/24 18:08
 * @time 18:08
 *
 * nio 服务端传输:
 *
 */
public class PlainNioServer {

    public void server(int port)throws IOException{
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);// 开启非阻塞
        //通过获取 socket 然后注册 端口和ip
        ServerSocket ssocket = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        ssocket.bind(address); //绑定
        //选择器,获取选择器 把通道注册进选中器中
        Selector selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        final  ByteBuffer msg = ByteBuffer.wrap("Hi! \r\n".getBytes());

        for(;;){
            try{
                //等待需要处理的新事件,阻塞将一直持续下一个传入事件
            selector.select();
            }catch (IOException e){
                e.printStackTrace();
                break;
            }
            //获取所有接收事件的SelectionKey 实例,
            //通过 SelectionKey 可以获取到1 通道,这样就可以把接收的数据
            //转发给客户端。
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                try{
                    //连接事件
                    if(key.isAcceptable()){
                        ServerSocketChannel  server =(ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        client.register(selector, SelectionKey.OP_WRITE
                        |SelectionKey.OP_READ,msg.duplicate());
                        System.out.println("当前创建连接的客户端是:"+client);
                    }
                    //检查套接字是否已经装备好写数据
                    if(key.isWritable()){
                        SocketChannel  client =(SocketChannel) key.channel();
                        ByteBuffer buffer = (ByteBuffer)key.attachment();
                        while (buffer.hasRemaining()){
                            if(client.write(buffer) == 0){
                                break;
                            }
                        }
                        client.close();
                    }
                }catch (IOException ex){
                    key.cancel();
                    try{
                        key.channel().close();
                    }catch (IOException cex){
                        //close
                    }
                }
            }
        }

    }

}

从上面可以看出,阻塞io传输到 nio 异步传输的代码全变了,而且编码越来越复杂,接下来我们看 Netty的代码实例。

Netty 阻塞的io模型

/**
 * @Author qrn
 * @Title https://blog.youkuaiyun.com/qq_41971087
 * @Date 2021/5/24 19:34
 * @time 19:34
 * netty oio 传统阻塞io:
 * OioEventLoopGroup()
 * OioServerSocketChannel
 * 表示当前是阻塞的io模型
 */
public class NettyOioServer {

    public void server(int port) throws  Exception{
      final ByteBuf buf =  Unpooled.unreleasableBuffer(
                Unpooled.copiedBuffer("Hi! \r\n", Charset.forName("UTF-8"))
        );
        EventLoopGroup group = new OioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(group).channel(OioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){

                            @Override
                            public void channelActive(
                                    ChannelHandlerContext ctx)
                                    throws Exception {
                                ctx.writeAndFlush(buf.duplicate())
                                        .addListener(
                                                ChannelFutureListener.CLOSE);
                            }
                        });
                        }
                    });
            ChannelFuture f = b.bind().sync();
            f.channel().closeFuture().sync();
        }finally {
            //释放所有资源:
            group.shutdownGracefully().sync();
        }
    }
}

Netty 异步非阻塞io模型

/**
 * @Author qrn
 * @Title
 * @Date 2021/5/24 19:52
 * @time 19:52
 * 传统非阻塞io
 *
 * 与传统阻塞io的区别就在于
 * OioEventLoopGroup() 替换  NioEventLoopGroup
 * OioServerSocketChannel 替换 NioServerSocketChannel
 * 就可以了
 *
 */
public class NettyNioServer {


    public void server(int port) throws  Exception{
        final ByteBuf buf =  Unpooled.unreleasableBuffer(
                Unpooled.copiedBuffer("Hi! \r\n", Charset.forName("UTF-8"))
        );
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(group).channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){

                                @Override
                                public void channelActive(
                                        ChannelHandlerContext ctx)
                                        throws Exception {
                                    ctx.writeAndFlush(buf.duplicate())
                                            .addListener(
                                                    ChannelFutureListener.CLOSE);
                                }
                            });
                        }
                    });
            ChannelFuture f = b.bind().sync();
            f.channel().closeFuture().sync();
        }finally {
            //释放所有资源:
            group.shutdownGracefully().sync();
        }
    }

}

可以看到这上面的代码基本是一致的,唯一的区别就是

  • OioEventLoopGroup() 替换 NioEventLoopGroup
  • OioServerSocketChannel 替换 NioServerSocketChannel

很大程度上面得到了复用,并且代码也不在那么的复杂。

传输API

传输API的核心就是 channel 接口了,他被用于所有的IO操作,结构图:

在这里插入图片描述

如上图所示,没一个Channel 都对应一个ChannelPipeline 和 ChannelConfig ,

ChannelConfig 包含了该Channel的所有配置设置,并且支持热更新。

由于Channel 是独一无二的,为了保证顺序性,该接口继承 Comparable,一旦两个Channel实例有2个相同的实例,那么 public int compareTo(T o); 方法就会抛出一个 error。

方法介绍

方法名描述
eventLoop返回分配给 Channel 的 EventLoop
pipeline返回分配给 Channel 的 ChannelPipeline
isActive如果 Channel 是活动的,则返回 true。活动的意义可能依赖于底层的传输。
localAddress返回本地的 SokcetAddress
remoteAddress返回远程的 SocketAddress
write将数据写到远程节点这个数据将被传递给 ChannelPipeline并且排队直到它被 冲刷
flush将之前已写的数据冲刷到底层传输,如一个 Socket
writeAndFlush一个简便的方法,等同于调用 write()并接着调用 flush()

代码实例放在github上面了,需要的可以自己去上面看 : netty-notes
建议大家都自己去画一画,加深一下印象。
如果这篇文章,有帮助到大家的,请给作者一个一键三连,谢谢。

### Netty-all 和 Netty-buffer 的差异及其应用场景 #### 1. Netty-all 组件概述 `netty-all` 是一个聚合模块,包含了整个 Netty 框架所需的所有功能组件。该模块旨在提供一种简便的方式引入完整的 Netty 功能集到项目中,使得开发者无需单独管理多个依赖项。<groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>x.x.x.Final</version> </dependency> ``` #### 2. Netty-buffer 特定作用域分析 相比之下,`netty-buffer` 则专注于处理字节缓冲区的操作。它提供了高效的内存管理和数据传输机制,支持堆外内存(Direct Memory),这有助于减少垃圾回收压力并提高性能表现,在高并发场景下尤为重要。此外还实现了多种优化策略来增强读写效率,比如复合缓冲区 CompositeByteBuf 能够有效降低复制成本[^1]。 ```java // 创建一个新的 ByteBuf 实例 ByteBuf buffer = Unpooled.buffer(1024); try { // 向 Buffer 中写入一些数据 String message = "Hello, world!"; byte[] bytes = message.getBytes(CharsetUtil.UTF_8); buffer.writeBytes(bytes); // 将 Buffer 数据转换成字符串形式输出 System.out.println(buffer.toString(CharsetUtil.UTF_8)); } finally { // 使用完毕后释放资源 buffer.release(); } ``` #### 3. 应用场景对比 当应用程序只需要特定部分的功能时,可以选择只导入 `netty-buffer` 或其他单个子模块以减小程序体积;而如果需要全面运用 Netty 提供的各种网络通信能力,则推荐采用 `netty-all` 来简化构建配置过程。另外值得注意的是,虽然两者都可以用于开发基于异步I/O模式的服务端程序,但在实际部署过程中应当依据具体的业务需求和技术栈选型做出合理决策[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值