In the last blog ( netty entry to implement a simple echo program ), we know how to use netty to send a simple message, but it is far from enough. In this blog, we will use netty to send a message of a java bean object, but to send a message of object type, the java object must be serialized. There are many serialization frameworks in java, here we use protostuff To serialize, if you don't know protostuff, you can take a look at this blog ( protostuff serialization ) to understand the simple usage.
need:
When the client connects to the server, it sends 100 Person objects to the server.
After the server receives the message, it can be printed on the console.
Protocol for message sending: 4 bytes in length (the length is the length of the following object, excluding its own 4 bytes), followed by the actual data to be sent
Implementation ideas:
1. Server side:
1. The server first uses LengthFieldBasedFrameDecoder to decode and obtains a complete ByteBuf message
2. Then write the ProtostuffDecoder decoder to convert the message decoded in the previous step into a Person object
3. Write the ProtoStuffServerHandler class to output the Person object decoded in the previous step
2. Client
1. Write the ProtostuffClientHandler class to send the Person object to the server
2. Write a ProtostuffEncoder to convert the Person object into a byte array
3. Use netty's LengthFieldPrepender to add 4 bytes of message length to the previous byte array
The processing of the half-package is mainly processed by the LengthFieldBasedFrameDecoder submitted by netty
Notice:
new LengthFieldPrepender(4) ==> will add 4 bytes before the sent data to indicate the length of the message
new LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 4) ==> 10240 means that if the length of the bytes read this time is larger than this, it means that someone else forged a socket attack, and an exception will be thrown, the first 4 means read Take four bytes to indicate the length of this message, and a 4 behind it indicates that four bytes are discarded, and then the service data is read.
Implementation steps:
1. Introduce maven dependencies
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.huan.netty</groupId> <artifactId>netty-study</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>netty-study</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.6.Final</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.18</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-access</artifactId> <version>1.1.7</version> </dependency> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-api</artifactId> </dependency> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-core</artifactId> </dependency> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-bom</artifactId> <version>1.4.4</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
Second, write the entity class Person ( the client will send this object, and the server will receive this object )
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class Person { private int id; private String name; }
3. Write ProtostuffDecoder to convert the data in ByteBuf into Person object
public class ProtostuffDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { Schema<Person> schema = RuntimeSchema.getSchema(Person.class); Person person = schema.newMessage(); byte[] array = new byte[in.readableBytes()]; in.readBytes(array); ProtobufIOUtil.mergeFrom(array, person, schema); out.add(person); } }
Fourth, write ProtoStuffServerHandler to output the received data to the console
@ Slf4j public class ProtoStuffServerHandler extends ChannelInboundHandlerAdapter { private int counter = 0; /** * Called when data is received */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Person person = (Person) msg; log.info("This is the [{}]th time to get the person object [{}] sent by the client.", ++counter, person); } /** When an exception occurs, this method is called */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { log.error("error:", cause); ctx.close(); } }
5. Write the netty server to start the netty service
@ Slf4j public class NettyServer { public static void main(String[] args) throws Exception { EventLoopGroup boss = new NioEventLoopGroup(1); EventLoopGroup worker = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(boss, worker)// .channel(NioServerSocketChannel.class)// corresponds to the ServerSocketChannel class .option(ChannelOption.SO_BACKLOG, 128)// .handler(new LoggingHandler(LogLevel.TRACE))// .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 4)); ch.pipeline().addLast(new ProtostuffDecoder()); ch.pipeline().addLast(new ProtoStuffServerHandler()); } }); ChannelFuture future = bootstrap.bind(9090).sync(); log.info("server start in port:[{}]", 9090); future.channel().closeFuture().sync(); boss.shutdownGracefully(); worker.shutdownGracefully(); } }
Here you need to pay attention to the order of the decoders:
Must be LengthFieldBasedFrameDecoder first, then ProtostuffDecoder, then ProtoStuffServerHandler
6. Write the handler processor of the client to send the Person object to the server
@ Slf4j public class ProtostuffClientHandler extends ChannelInboundHandlerAdapter { /** * This method is called after the client and server TCP links are successfully established */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { Person person; for (int i = 0; i < 100; i++) { person = new Person(); person.setId(i); person.setName("张三" + i); ctx.writeAndFlush(person); } } /** * Called when an exception occurs */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { log.error("client error:", cause); ctx.close(); } }
7. Write the ProtostuffEncoder encoder to encode the Person object into a byte array
public class ProtostuffEncoder extends MessageToByteEncoder<Person> { @Override protected void encode(ChannelHandlerContext ctx, Person msg, ByteBuf out) throws Exception { LinkedBuffer buffer = LinkedBuffer.allocate(1024); Schema<Person> schema = RuntimeSchema.getSchema(Person.class); byte[] array = ProtobufIOUtil.toByteArray(msg, schema, buffer); out.writeBytes(array); } }
Eight, write the client
@ Slf4j public class NettyClient { public static void main(String[] args) throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group)// .channel(NioSocketChannel.class)// .option(ChannelOption.TCP_NODELAY, true)// .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LengthFieldPrepender(4)); ch.pipeline().addLast(new ProtostuffEncoder()); ch.pipeline().addLast(new ProtostuffClientHandler()); } }); ChannelFuture future = bootstrap.connect("127.0.0.1", 9090).sync(); log.info("client connect server."); future.channel().closeFuture().sync(); group.shutdownGracefully(); } }
Note: Here you also need to pay attention to the order in which the encoders in the initChannel method are added
ProtostuffEncoder -> Convert Person object to byte array
LengthFieldPrepender->Add 4 bytes of length before the byte array of the previous step
Nine, start the server and client for testing