Netty传输object并解决粘包拆包问题

简介: Netty传输object并解决粘包拆包问题

⭐️ 前言

大家好,笔者之前写过一篇文章,《Netty中粘包拆包问题解决探讨》,就Netty粘包拆包问题及其解决方案进行了探讨,本文算是这篇博客的延续。探讨netty传输object的问题。

本文将netty结合java序列化来传输object并解决粘包拆包问题,虽然netty已经提供了ObjectEncoder和ObjectDecoder来完成此任务,但本文想把这个功能简单化,来拆解其中的原理。

⭐️ java序列化

java序列化相关的资料很多,这里就不再赘述,show the code!!!

⭐️ netty传输object

这里客户端发送了三条消息(对象实例),在最后一条消息发送时进行flush操作。

Trade类定义了我们要传输的对象,其中除了一般实体类应该具备的元素外,按ddd领域模型设计的思想,还封装了两个业务方法:toByteArray和fromByteArray,来方便Trade对象与字节数组的转换。

public class Trade implements Serializable {
    private int id;
    private String name;
    private float payment;
    private Date maturity;
    public Trade() {}
    public Trade(int id, String name, float payment, Date maturity) {
        this.id = id;
        this.name = name;
        this.payment = payment;
        this.maturity = maturity;
    }
    public String toString(){
        return String.format("id: %d, name: %s, payment: %f, maturity: %s", id, name, payment,
                new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(maturity));
    }
    /***
     * 对象序列化为字节数组
     * @return
     * @throws IOException
     */
    public byte[] toByteArray() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = null;
        byte[] data;
        try {
            oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            data = baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                oos.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
        return data;
    }
    /***
     * 从字节数组中读取对象
     * @param bytes
     * @return
     */
    public static Trade fromByteArray(byte[] bytes) {
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(bais);
            return (Trade) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                ois.close();
                bais.close();
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public float getPayment() {
        return payment;
    }
    public void setPayment(float payment) {
        this.payment = payment;
    }
    public Date getMaturity() {
        return maturity;
    }
    public void setMaturity(Date maturity) {
        this.maturity = maturity;
    }
}

ServerTestHandler用以处理并显示客户端发给服务端的数据

public class ServerTestHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        // 消息(传输的对象)长度
        int len = buf.readInt();
        System.out.println(String.format("消息(传输的对象)长度:%s", len));
        // 接收对象内容的字节数组
        byte[] arr = new byte[len];
        // 将对象读到字节数组中
        buf.readBytes(arr);
        // 从字节数组中解析Trade对象
        Trade tr = Trade.fromByteArray(arr);
        System.out.println("来自client的消息:" + tr.toString());
    }
}

需要注意的是,在再给server设置Handler时,LengthFieldBasedFrameDecoder中的参数initialBytesToStrip要设置为0,因为我们用LengthFieldBasedFrameDecoder对Frame进行界定的同时,还要在ServerTestHandler读取对象的长度。

server端代码

public class Server {
        public static void main(String[] args) {
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .childOption(ChannelOption.SO_KEEPALIVE, true)
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                ChannelPipeline pipeline = socketChannel.pipeline();
                                pipeline.addLast(new LoggingHandler())
                                        .addLast(new LengthFieldBasedFrameDecoder(1024,0, 4, 0, 0))
                                        .addLast(new ServerTestHandler());
                            }
                        });
                System.out.println("server ready");
                ChannelFuture sync = bootstrap.bind(8888).sync();
                sync.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
}

client端代码

public class Client {
    public static void main(String[] args) {
        EventLoopGroup workGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.group(workGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new LoggingHandler())
                                    .addLast(new LengthFieldPrepender(4));
                        }
                    });
            System.out.println("client ok");
            ChannelFuture localhost = bootstrap.connect("localhost", 8888).sync();
            // 发送消息
            Calendar instance = Calendar.getInstance();
            instance.set(2023, 11, 11, 10, 10, 5);
            Trade trade1 = new Trade(1, "trade1", 1.5f, instance.getTime());
            byte[] trade1Bytes = trade1.toByteArray();
            localhost.channel().write(Unpooled.copiedBuffer(trade1Bytes));
            instance.set(2023, 10, 20, 9, 8, 5);
            Trade trade2 = new Trade(2, "trade2", 0.5f, instance.getTime());
            byte[] trade2Bytes = trade2.toByteArray();
            localhost.channel().write(Unpooled.copiedBuffer(trade2Bytes));
            instance.set(2023, 4, 11, 13, 13, 45);
            Trade trade3 = new Trade(3, "trade3", 7.1f, instance.getTime());
            byte[] trade3Bytes = trade3.toByteArray();
            localhost.channel().writeAndFlush(Unpooled.copiedBuffer(trade3Bytes));
            localhost.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workGroup.shutdownGracefully();
        }
    }
}

client端日志

+-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 b1                                     |....            |
+--------+-------------------------------------------------+----------------+
2023/11/17 18:12:15,096 [DEBUG]AbstractInternalLogger-[id: 0xd71e6e91, L:/127.0.0.1:53675 - R:localhost/127.0.0.1:8888] WRITE: 177B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| ac ed 00 05 73 72 00 18 63 6f 6d 2e 78 68 63 2e |....sr..com.xhc.|
|00000010| 6e 65 74 2e 65 6e 74 69 74 79 2e 54 72 61 64 65 |net.entity.Trade|
|00000020| 99 8e 9b 76 ff dc b3 1f 02 00 04 49 00 02 69 64 |...v.......I..id|
|00000030| 46 00 07 70 61 79 6d 65 6e 74 4c 00 08 6d 61 74 |F..paymentL..mat|
|00000040| 75 72 69 74 79 74 00 10 4c 6a 61 76 61 2f 75 74 |urityt..Ljava/ut|
|00000050| 69 6c 2f 44 61 74 65 3b 4c 00 04 6e 61 6d 65 74 |il/Date;L..namet|
|00000060| 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 |..Ljava/lang/Str|
|00000070| 69 6e 67 3b 78 70 00 00 00 01 3f c0 00 00 73 72 |ing;xp....?...sr|
|00000080| 00 0e 6a 61 76 61 2e 75 74 69 6c 2e 44 61 74 65 |..java.util.Date|
|00000090| 68 6a 81 01 4b 59 74 19 03 00 00 78 70 77 08 00 |hj..KYt....xpw..|
|000000a0| 00 01 8c 56 a3 80 74 78 74 00 06 74 72 61 64 65 |...V..txt..trade|
|000000b0| 31                                              |1               |
+--------+-------------------------------------------------+----------------+
2023/11/17 18:12:15,098 [DEBUG]AbstractInternalLogger-[id: 0xd71e6e91, L:/127.0.0.1:53675 - R:localhost/127.0.0.1:8888] WRITE: 4B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 b1                                     |....            |
+--------+-------------------------------------------------+----------------+
2023/11/17 18:12:15,098 [DEBUG]AbstractInternalLogger-[id: 0xd71e6e91, L:/127.0.0.1:53675 - R:localhost/127.0.0.1:8888] WRITE: 177B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| ac ed 00 05 73 72 00 18 63 6f 6d 2e 78 68 63 2e |....sr..com.xhc.|
|00000010| 6e 65 74 2e 65 6e 74 69 74 79 2e 54 72 61 64 65 |net.entity.Trade|
|00000020| 99 8e 9b 76 ff dc b3 1f 02 00 04 49 00 02 69 64 |...v.......I..id|
|00000030| 46 00 07 70 61 79 6d 65 6e 74 4c 00 08 6d 61 74 |F..paymentL..mat|
|00000040| 75 72 69 74 79 74 00 10 4c 6a 61 76 61 2f 75 74 |urityt..Ljava/ut|
|00000050| 69 6c 2f 44 61 74 65 3b 4c 00 04 6e 61 6d 65 74 |il/Date;L..namet|
|00000060| 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 |..Ljava/lang/Str|
|00000070| 69 6e 67 3b 78 70 00 00 00 02 3f 00 00 00 73 72 |ing;xp....?...sr|
|00000080| 00 0e 6a 61 76 61 2e 75 74 69 6c 2e 44 61 74 65 |..java.util.Date|
|00000090| 68 6a 81 01 4b 59 74 19 03 00 00 78 70 77 08 00 |hj..KYt....xpw..|
|000000a0| 00 01 8b ea 45 31 34 78 74 00 06 74 72 61 64 65 |....E14xt..trade|
|000000b0| 32                                              |2               |
+--------+-------------------------------------------------+----------------+
2023/11/17 18:12:15,098 [DEBUG]AbstractInternalLogger-[id: 0xd71e6e91, L:/127.0.0.1:53675 - R:localhost/127.0.0.1:8888] WRITE: 4B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 b1                                     |....            |
+--------+-------------------------------------------------+----------------+
2023/11/17 18:12:15,099 [DEBUG]AbstractInternalLogger-[id: 0xd71e6e91, L:/127.0.0.1:53675 - R:localhost/127.0.0.1:8888] WRITE: 177B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| ac ed 00 05 73 72 00 18 63 6f 6d 2e 78 68 63 2e |....sr..com.xhc.|
|00000010| 6e 65 74 2e 65 6e 74 69 74 79 2e 54 72 61 64 65 |net.entity.Trade|
|00000020| 99 8e 9b 76 ff dc b3 1f 02 00 04 49 00 02 69 64 |...v.......I..id|
|00000030| 46 00 07 70 61 79 6d 65 6e 74 4c 00 08 6d 61 74 |F..paymentL..mat|
|00000040| 75 72 69 74 79 74 00 10 4c 6a 61 76 61 2f 75 74 |urityt..Ljava/ut|
|00000050| 69 6c 2f 44 61 74 65 3b 4c 00 04 6e 61 6d 65 74 |il/Date;L..namet|
|00000060| 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 |..Ljava/lang/Str|
|00000070| 69 6e 67 3b 78 70 00 00 00 03 40 e3 33 33 73 72 |ing;xp....@.33sr|
|00000080| 00 0e 6a 61 76 61 2e 75 74 69 6c 2e 44 61 74 65 |..java.util.Date|
|00000090| 68 6a 81 01 4b 59 74 19 03 00 00 78 70 77 08 00 |hj..KYt....xpw..|
|000000a0| 00 01 88 09 3a bf 54 78 74 00 06 74 72 61 64 65 |....:.Txt..trade|
|000000b0| 33                                              |3               |
+--------+-------------------------------------------------+----------------+
2023/11/17 18:12:15,099 [DEBUG]AbstractInternalLogger-[id: 0xd71e6e91, L:/127.0.0.1:53675 - R:localhost/127.0.0.1:8888] FLUSH

server端日志

+-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 b1 ac ed 00 05 73 72 00 18 63 6f 6d 2e |........sr..com.|
|00000010| 78 68 63 2e 6e 65 74 2e 65 6e 74 69 74 79 2e 54 |xhc.net.entity.T|
|00000020| 72 61 64 65 99 8e 9b 76 ff dc b3 1f 02 00 04 49 |rade...v.......I|
|00000030| 00 02 69 64 46 00 07 70 61 79 6d 65 6e 74 4c 00 |..idF..paymentL.|
|00000040| 08 6d 61 74 75 72 69 74 79 74 00 10 4c 6a 61 76 |.maturityt..Ljav|
|00000050| 61 2f 75 74 69 6c 2f 44 61 74 65 3b 4c 00 04 6e |a/util/Date;L..n|
|00000060| 61 6d 65 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 |amet..Ljava/lang|
|00000070| 2f 53 74 72 69 6e 67 3b 78 70 00 00 00 01 3f c0 |/String;xp....?.|
|00000080| 00 00 73 72 00 0e 6a 61 76 61 2e 75 74 69 6c 2e |..sr..java.util.|
|00000090| 44 61 74 65 68 6a 81 01 4b 59 74 19 03 00 00 78 |Datehj..KYt....x|
|000000a0| 70 77 08 00 00 01 8c 56 a3 80 74 78 74 00 06 74 |pw.....V..txt..t|
|000000b0| 72 61 64 65 31 00 00 00 b1 ac ed 00 05 73 72 00 |rade1........sr.|
|000000c0| 18 63 6f 6d 2e 78 68 63 2e 6e 65 74 2e 65 6e 74 |.com.xhc.net.ent|
|000000d0| 69 74 79 2e 54 72 61 64 65 99 8e 9b 76 ff dc b3 |ity.Trade...v...|
|000000e0| 1f 02 00 04 49 00 02 69 64 46 00 07 70 61 79 6d |....I..idF..paym|
|000000f0| 65 6e 74 4c 00 08 6d 61 74 75 72 69 74 79 74 00 |entL..maturityt.|
|00000100| 10 4c 6a 61 76 61 2f 75 74 69 6c 2f 44 61 74 65 |.Ljava/util/Date|
|00000110| 3b 4c 00 04 6e 61 6d 65 74 00 12 4c 6a 61 76 61 |;L..namet..Ljava|
|00000120| 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 78 70 00 |/lang/String;xp.|
|00000130| 00 00 02 3f 00 00 00 73 72 00 0e 6a 61 76 61 2e |...?...sr..java.|
|00000140| 75 74 69 6c 2e 44 61 74 65 68 6a 81 01 4b 59 74 |util.Datehj..KYt|
|00000150| 19 03 00 00 78 70 77 08 00 00 01 8b ea 45 31 34 |....xpw......E14|
|00000160| 78 74 00 06 74 72 61 64 65 32 00 00 00 b1 ac ed |xt..trade2......|
|00000170| 00 05 73 72 00 18 63 6f 6d 2e 78 68 63 2e 6e 65 |..sr..com.xhc.ne|
|00000180| 74 2e 65 6e 74 69 74 79 2e 54 72 61 64 65 99 8e |t.entity.Trade..|
|00000190| 9b 76 ff dc b3 1f 02 00 04 49 00 02 69 64 46 00 |.v.......I..idF.|
|000001a0| 07 70 61 79 6d 65 6e 74 4c 00 08 6d 61 74 75 72 |.paymentL..matur|
|000001b0| 69 74 79 74 00 10 4c 6a 61 76 61 2f 75 74 69 6c |ityt..Ljava/util|
|000001c0| 2f 44 61 74 65 3b 4c 00 04 6e 61 6d 65 74 00 12 |/Date;L..namet..|
|000001d0| 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e |Ljava/lang/Strin|
|000001e0| 67 3b 78 70 00 00 00 03 40 e3 33 33 73 72 00 0e |g;xp....@.33sr..|
|000001f0| 6a 61 76 61 2e 75 74 69 6c 2e 44 61 74 65 68 6a |java.util.Datehj|
|00000200| 81 01 4b 59 74 19 03 00 00 78 70 77 08 00 00 01 |..KYt....xpw....|
|00000210| 88 09 3a bf 54 78 74 00 06 74 72 61 64 65 33    |..:.Txt..trade3 |
+--------+-------------------------------------------------+----------------+
消息(传输的对象)长度:177
来自client的消息:id: 1, name: trade1, payment: 1.500000, maturity: 2023-12-11 10:10:05
消息(传输的对象)长度:177
来自client的消息:id: 2, name: trade2, payment: 0.500000, maturity: 2023-11-20 09:08:05
消息(传输的对象)长度:177
来自client的消息:id: 3, name: trade3, payment: 7.100000, maturity: 2023-05-11 01:13:45
2023/11/17 18:12:15,143 [DEBUG]AbstractInternalLogger-[id: 0x143e8152, L:/127.0.0.1:8888 - R:/127.0.0.1:53675] READ COMPLETE

可以看到,三个对象成功的被服务器接收并正确的解析了。

笔者水平有限,若有不对的地方欢迎评论指正!

相关文章
|
24天前
|
消息中间件 存储 网络协议
拼多多面试:Netty如何解决粘包问题?
粘包和拆包问题也叫做粘包和半包问题,**它是指在数据传输时,接收方未能正常读取到一条完整数据的情况(只读取了部分数据,或多读取到了另一条数据的情况)就叫做粘包或拆包问题。** 从严格意义上来说,粘包问题和拆包问题属于两个不同的问题,接下来我们分别来看。 ## 1.粘包问题 粘包问题是指在网络通信中,发送方连续发送的多个小数据包被接收方一次性接收的现象。这可能是因为底层传输层协议(如 TCP)会将多个小数据包合并成一个大的数据块进行传输,导致接收方在接收数据时一次性接收了多个数据包,造成粘连。 例如以下案例,正常情况下客户端发送了两条消息,分别为“ABC”和“DEF”,那么接收端也应该收到两
13 0
拼多多面试:Netty如何解决粘包问题?
|
6天前
|
Java
Netty中粘包拆包问题解决探讨
Netty中粘包拆包问题解决探讨
2 0
|
1月前
|
网络协议 Java 物联网
Spring Boot与Netty打造TCP服务端(解决粘包问题)
Spring Boot与Netty打造TCP服务端(解决粘包问题)
166 1
|
1月前
|
JSON 移动开发 网络协议
数据拆散与黏连:深入剖析Netty中的半包与粘包问题
数据拆散与黏连:深入剖析Netty中的半包与粘包问题
36 0
|
存储 缓存 NoSQL
跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码)
本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯一ID、红包、消息同步等功能,并且还支持集群部署。
13281 1
|
6天前
|
机器学习/深度学习 缓存 算法
netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
|
1月前
|
消息中间件 Oracle Dubbo
Netty 源码共读(一)如何阅读JDK下sun包的源码
Netty 源码共读(一)如何阅读JDK下sun包的源码
54 1
|
1月前
|
编解码 前端开发 网络协议
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
65 0
|
1月前
|
编解码 安全 前端开发
Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读
Netty Review - StringEncoder字符串编码器和StringDecoder 解码器的使用与源码解读
74 0
|
6月前
|
NoSQL Java Redis
跟着源码学IM(十二):基于Netty打造一款高性能的IM即时通讯程序
关于Netty网络框架的内容,前面已经讲了两个章节,但总归来说难以真正掌握,毕竟只是对其中一个个组件进行讲解,很难让诸位将其串起来形成一条线,所以本章中则会结合实战案例,对Netty进行更深层次的学习与掌握,实战案例也并不难,一个非常朴素的IM聊天程序。 原本打算做个多人斗地主练习程序,但那需要织入过多的业务逻辑,因此一方面会带来不必要的理解难度,让案例更为复杂化,另一方面代码量也会偏多,所以最终依旧选择实现基本的IM聊天程序,既简单,又能加深对Netty的理解。
111 1