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