⭐️ 前言
大家好,笔者之前写过一篇文章,《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
可以看到,三个对象成功的被服务器接收并正确的解析了。
笔者水平有限,若有不对的地方欢迎评论指正!