brief introduction
In the previous series of articles, we learned about the basic structure and working principle of netty. You guys must be overjoyed and want to start writing code to experience this magical netty framework. Just recently, at the Tokyo Olympic Games, shall we write a netty client and server to cheer for China?
Scene planning
So what kind of system are we going to build today?
First, we need to build a server to handle the connections of all netty clients and the messages sent by the client to the server.
Also build a client, which is responsible for establishing a connection with the server and sending messages to the server. In today's example, after establishing a connection, the client will first send a "China" message to the server, and then the server will return a "come on" after receiving the message! "The message is sent to the client, and then the client receives the message and sends a" China "message to the server... In this way, the cycle repeats until the end of the Olympic Games!
We know that both client-side and server-side message processing are carried out through the handler. In the handler, we can rewrite the channelRead method, so that after reading the messages in the channel, we can process the messages, and then configure the handler of the client-side and server-side to start in the Bootstrap. Is it very simple? Let's do it together.
Start Server
Suppose the handler on the server side is called CheerUpServerHandler. We use ServerBootstrap to build two eventloopgroups to start the server. Some friends who have read the previous articles in this series may know that for the server side, we need to start two eventloopgroups, a bossGroup and a workerGroup. These two groups are parent-child relationships, and bossGroup is responsible for handling connection related problems, The worker group is responsible for handling specific messages in the channel.
The code for starting the service is the same as follows:
// Server configuration //boss loop EventLoopGroup bossGroup = new NioEventLoopGroup(1); //worker loop EventLoopGroup workerGroup = new NioEventLoopGroup(); final CheerUpServerHandler serverHandler = new CheerUpServerHandler(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // The backlog parameter in the listen function of tcp/ip protocol, waiting for the size of the connection pool .option(ChannelOption.SO_BACKLOG, 100) //Log processor .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override //Initialize channel and add handler public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); //Log processor p.addLast(new LoggingHandler(LogLevel.INFO)); p.addLast(serverHandler); } }); // Start the server ChannelFuture f = b.bind(PORT).sync(); // Wait for the channel to close f.channel().closeFuture().sync();
The code for starting the server is basically the same for different services. Here we need to pay attention to these points.
In the server bootstrap, we added an option: channeloption SO_ BACKLOGļ¼ChannelOption.SO_BACKLOG corresponds to the backlog parameter in the listen(int socketfd,int backlog) function of tcp/ip protocol, which is used to initialize the server connectable queue. The backlog parameter specifies the size of the queue. Because the processing of client connection requests is sequential for a connection, only one client connection can be processed at the same time. When multiple clients come, the server puts the client connection requests that cannot be processed in the queue for processing,
In addition, we have added two logginghandlers, one for handler and the other for childHandler. LoggingHandler mainly monitors various events in the channel and then outputs corresponding messages. It is very easy to use.
For example, the following logs will be output when the server is started:
[nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0xd9b41ea4] REGISTERED [nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0xd9b41ea4] BIND: 0.0.0.0/0.0.0.0:8007 [nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0xd9b41ea4, L:/0:0:0:0:0:0:0:0:8007] ACTIVE
This log is output by the first LoggingHandler and represents REGISTERED, BIND and ACTIVE events on the server side respectively. From the output, we can see that the server itself is bound to 0.0 0.0:8007.
When the client starts to establish a connection with the server, the following log will be output:
[nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x37a4ba9f, L:/0:0:0:0:0:0:0:0:8007] READ: [id: 0x6dcbae9c, L:/127.0.0.1:8007 - R:/127.0.0.1:54566] [nioEventLoopGroup-2-1] INFO i.n.handler.logging.LoggingHandler - [id: 0x37a4ba9f, L:/0:0:0:0:0:0:0:0:8007] READ COMPLETE
The above log indicates two events: READ and READ COMPLETE, where l: / 127.0 0.1:8007 - R:/127.0. 0.1:54566 represents that port 8007 of the local server is connected to port 54566 of the client.
For the second LoggingHandler, some specific messages will be output to process related messages. For example, REGISTERED, ACTIVE, READ, WRITE, FLUSH, READ COMPLETE and other events are not listed one by one.
Start client
Similarly, assuming that the handler name of the client is ChinaClientHandler, you can start the client like starting the server, as follows:
// eventLoop of client EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); //Add log processor p.addLast(new LoggingHandler(LogLevel.INFO)); p.addLast(new ChinaClientHandler()); } }); // Start client ChannelFuture f = b.connect(HOST, PORT).sync();
Bootstrap is used for client startup. We also configured a logging handler for it and added a custom ChinaClientHandler.
Message processing
We know that there are two kinds of handlers, one is inboundHandler and the other is outboundHandler. Here we want to monitor the events of reading data from socket, so the handlers on the client and server sides can inherit from ChannelInboundHandlerAdapter.
The process of message processing is that after the client establishes a connection with the server, it will first send a "China" message to the server.
After the client establishes a connection with the server, the channelActive event will be triggered, so the message can be sent in the handler of the client:
public void channelActive(ChannelHandlerContext ctx) { ctx.writeAndFlush("China"); }
The server side will trigger the channelRead event when reading messages from the channel, so the handler on the server side can override the channelRead method:
public void channelRead(ChannelHandlerContext ctx, Object msg) { log.info("Received message:{}",msg); ctx.writeAndFlush("come on.!"); }
Then the client reads "come on!" from the channel After that, write "China" to channel, so the client also needs to rewrite the method channelRead:
public void channelRead(ChannelHandlerContext ctx, Object msg) { ctx.writeAndFlush("China"); }
Can this be carried out in a circular way?
Traps in message processing
In fact, when you execute the above code, you will find that the client does write the "China" message to the channel, but the channelRead on the server is not triggered. Why?
According to the research, if the object written is a String, there will be such an error inside the program, but this error is hidden and you will not see it in the running program output, so it is still very unfriendly to novice partners. This error is:
DefaultChannelPromise@57f5c075(failure: java.lang.UnsupportedOperationException: unsupported message type: String (expected: ByteBuf, FileRegion))
From the error information, we can see that there are two supported message types: ByteBuf and FileRegion.
OK, let's change the above message type to ByteBuf and try:
message = Unpooled.buffer(ChinaClient.SIZE); message.writeBytes("China".getBytes(StandardCharsets.UTF_8)); public void channelActive(ChannelHandlerContext ctx) { log.info("Readable byte:{},index:{}",message.readableBytes(),message.readerIndex()); log.info("Writable byte:{},index:{}",message.writableBytes(),message.writerIndex()); ctx.writeAndFlush(message); }
Above, we define a ByteBuf global message object and send it to the server. After the server reads the message, we send a ByteBuf global message object to the client. This cycle is repeated.
But when you run the above program, you will find that the server does receive "China" and the client does receive "come on! ", but the" China "message sent later by the client can't be received by the server. What's the matter?
We know that ByteBuf has readableBytes, readerIndex, writableBytes, writerIndex, capacity, refCnt and other attributes. We compare these attributes before and after message sending:
Before sending a message:
Readable byte:6,readerIndex:0 Writable byte:14,writerIndex:6 capacity:20,refCnt:1
After the message is sent:
Readable byte:6,readerIndex:0 Writable byte:-6,writerIndex:6 capacity:0,refCnt:0
So the problem is found. After ByteBuf is processed once, refCnt becomes 0, so it cannot be written again. How to solve it?
The simple way is to re create a ByteBuf every time you send it, so there is no problem.
But it seems a bit wasteful to create an object every time. What should I do? Since refCnt becomes 0, can we call the retain() method in ByteBuf to add refCnt?
The answer is this, but be aware that you need to call the retain() method before sending, and if you call retain() after the message is processed, it will report an exception.
summary
Well, run the above program to cheer China all the time, YYDS!
Examples of this article can be referred to: learn-netty4
This article has been included in http://www.flydean.com/06-netty-cheerup-china/
The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to find!
Welcome to my official account: "those things in procedure", understand technology, know you better!