文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Netty粘包拆包及使用原理详解

2022-11-13 13:54

关注

为什么使用Netty框架

由于上述原因,在大多数场景下,不建议大家直接使用JDK的NIO类库,除非你精通NIO编程或者有特殊的需求。在绝大多数的业务场景中,我们可以使用NIO框架Netty来进行NIO编程,它既可以作为客户端也可以作为服务端,同时支持UDP和异步文件传输,功能非常强大。

Netty框架介绍

Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如Hadoop的RPC框架Avro就使用了Netty作为底层通信框架,其他还有业界主流的RPC框架,也使用Netty来构建高性能的异步通信能力。

优点总结:

Netty实战

首先引入Netty的jar包。

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.42.Final</version>
</dependency>

Netty编写服务器端

NettyServer 类

public class NettyServer {
    
    private static int port = 8080;
    public static void main(String[] args) {
        
        // 用于接受客户端连接的请求 (并没有处理请求)
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        // 用于处理客户端连接的读写操作(处理请求操作)
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        //NioServerSocketChannel   标记当前是服务器端
        serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        // 设置我们分割最大长度为1024
                        socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        // 获取数据的结果为string类型
                        socketChannel.pipeline().addLast(new StringEncoder());
                         //处理每个handler(也就是每次客户端请求)
                        socketChannel.pipeline().addLast(new ServerHandler());
                    }
                });
        try {
            //绑定端口号
            ChannelFuture bind = serverBootstrap.bind(port);
            ChannelFuture sync = bind.sync();
            System.out.println("服务器端启动成功:" + port);
            //等待监听我们的请求
            sync.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //优雅的关闭我们的线程池
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

ServerHandler 类

public class ServerHandler extends SimpleChannelInboundHandler {
    
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        ByteBuf byteBuf = (ByteBuf) o;
        String request = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("request:" + request);
        // 响应内容:
        channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("这里是Netty服务端\n", CharsetUtil.UTF_8));
    }
}

Netty客户端

NettyClient 类

public class NettyClient {
    public static void main(String[] args) {
        //创建nioEventLoopGroup
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group).channel(NioSocketChannel.class)
                .remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        // 设置我们分割最大长度为1024
                        ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        // 获取数据的结果为string类型
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new ClientHandler());
                    }
                });
        try {
            // 发起同步连接
            ChannelFuture sync = bootstrap.connect().sync();
            sync.channel().closeFuture().sync();
        } catch (Exception e) {

        } finally {
            group.shutdownGracefully();
        }
    }
}

ClientHandler 类

public class ClientHandler extends SimpleChannelInboundHandler {
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            // 发送数据
            ctx.writeAndFlush(Unpooled.copiedBuffer("你是什么类型的服务端啊?\n", CharsetUtil.UTF_8));
        }
        //客户端发十条消息
    }
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("resp:" + byteBuf.toString(CharsetUtil.UTF_8));
    }
}

粘包与拆包

原因:因为我们现在tcp协议默认是长连接形式实现通讯,发送请求完了之后整个连接暂时不会关闭

1.短连接

客户端与服务器端建立连接的时候,客户端发送一条消息,客户端与服务器连接关闭

2.长连接

客户端与服务器端建立连接的时候,客户端发送多条消息,客户端与服务器连接关闭

什么是粘包:多次发送的消息,服务器一次合并读取msgmsg

什么是拆包:多次发送消息 服务器读取第一条数据完整+第二条不完整数据 第二条不完整数据 Msgm sg

为什么会造成拆包和粘包? 前提长连接、其次缓冲区

原因的造成:

Tcp协议为了能够高性能的传输数据,发送和接受时候都会采用缓冲区,必须等待缓冲区满了以后才可以发送或者读取;

当我们的应用程序如果发送的数据大于了我们的套字节的缓冲区大小的话,就会造成了拆包。拆分成多条消息读取。当我们应用程序如果发送的写入的消息如果小于套字节缓冲区大小的时候

粘包与拆包产生的背景:

Tcp协议为了高性能的传输,发送和接受的时候都采用了缓冲区

3. 当我们的应用程序发送的数据大于套字节缓冲区的时候,就会实现拆包。

4. 当我们的应用程序写入的数据小于套字节缓冲区的时候,多次发送的消息会合并到一起接受,这个过程我们可以称做为粘包。

5. 接受端不够及时的获取缓冲区的数据,也会产生粘包的问题

6. 进行mss(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>mss的时候将发生拆包。

解决思路:

7. 以固定的长度发送数据,到缓冲区

8. 可以在数据之间设置一些边界(\n或者\r\n)

9. 利用编码器LineBaseDFrameDecoder解决tcp粘包的问题

常用编码器:

利用编码器LineBaseDFrameDecoder解决tcp粘包的问题的Java代码案例,核心思路就是增加边界 \n

服务器端类 NettyServer 的修改点

serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                // 设置我们分割最大长度为1024
                socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
                // 获取数据的结果为string类型
                socketChannel.pipeline().addLast(new StringEncoder());
                //发送数据的时候设置边界 \n
                socketChannel.pipeline().addLast(new ServerHandler());
            }
        });

服务器端类 ServerHandler 的修改点

@Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        ByteBuf byteBuf = (ByteBuf) o;
        String request = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("request:" + request);
        // 响应内容:
        channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("这里是Netty服务端\n", CharsetUtil.UTF_8));
    }

客户端的类 NettyClient 的修改点

bootstrap.group(group).channel(NioSocketChannel.class)
                .remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        // 设置我们分割最大长度为1024
                        ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        // 获取数据的结果为string类型
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new ClientHandler());
                    }
                });

客户端的类 ClientHandler 的修改点

 @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            // 发送数据
            ctx.writeAndFlush(Unpooled.copiedBuffer("你是什么类型的服务端啊?\n", CharsetUtil.UTF_8));
        }
        //客户端发十条消息
    }

到此这篇关于Netty粘包拆包详解及实战流程的文章就介绍到这了,更多相关Netty粘包拆包内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯