Introduction
In the previous series of articles, we have learned the basic structure and working principle of netty. Everyone must not be able to restrain the joy in their hearts. I want to start writing code to experience this magical netty framework. It just happened to be the Tokyo Olympics recently. We wrote a netty. Can your client and server cheer for China?
Scenario planning
So what kind of system are we going to build today?
First, we must build a server to handle all the connections of netty clients and process the messages sent by the client to the server.
We also need to build a client, which is responsible for establishing a connection with the server and sending messages to the server. In today's example, after the client has established a connection, it will first send a "China" message to the server, and then the server will return a "Come on!" message to the client after receiving the message, and then the client will receive the message afterwards. Send a "China" message to the server.... After this, the cycle repeats until the end of the Olympics!
We know that the message processing of the client and the server is carried out through the handler. In the handler, we can rewrite the channelRead method, so that after reading the message in the channel, the message can be processed, and then the client The configuration of the handlers on the server side and the server side can be started in Bootstrap. Isn’t it simple? Let's do it together.
Start Server
Assuming that 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 first article in this series may know that for the server side, two EventLoopGroups need to be started, one bossGroup, one workerGroup, these two The group is a parent-child relationship, the bossGroup is responsible for handling connection related issues, and the workerGroup is responsible for handling specific messages in the channel.
The code to start the service is the same, as shown below:
// Server配置
//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)
// tcp/ip协议listen函数中的backlog参数,等待连接池的大小
.option(ChannelOption.SO_BACKLOG, 100)
//日志处理器
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
//初始化channel,添加handler
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
//日志处理器
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(serverHandler);
}
});
// 启动服务器
ChannelFuture f = b.bind(PORT).sync();
// 等待channel关闭
f.channel().closeFuture().sync();
Different services, the code to start the server is basically the same, here we need to pay attention to these points.
In ServerBootstrap, we have added an option: ChannelOption.SO_BACKLOG, ChannelOption.SO_BACKLOG corresponds to the backlog parameter in the tcp/ip protocol listen (int socketfd, int backlog) function, which is used to initialize the server connectable queue, and the backlog parameter specifies The size of this queue. Because for a connection, the processing of client connection requests is processed sequentially, so 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 Waiting to be processed,
In addition, we also added two LoggingHandlers, one for the handler and one for the childHandler. LoggingHandler mainly monitors various events in the channel, and then outputs corresponding messages, which is very easy to use.
For example, the following log 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 the REGISTERED, BIND, and ACTIVE events on the server side. From the output, we can see that the server itself is bound to 0.0.0.0:8007.
The following log will be output when the client starts and establishes a connection with the server:
[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 shows 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 message processing related messages will be output. For example, events such as REGISTERED, ACTIVE, READ, WRITE, FLUSH, READ COMPLETE, etc., are not listed here.
Start the client
Similarly, assuming that the name of the client's handler is called ChinaClientHandler, then the client can be started like the server, as follows:
// 客户端的eventLoop
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();
//添加日志处理器
p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new ChinaClientHandler());
}
});
// 启动客户端
ChannelFuture f = b.connect(HOST, PORT).sync();
The client started using Bootstrap, we also configured a LoggingHandler for him, and added a custom ChinaClientHandler.
Message processing
We know that there are two handlers, one is inboundHandler and the other is outboundHandler. Here we want to monitor events that read data from the socket, so the client and server-side handlers here both inherit from ChannelInboundHandlerAdapter.
The message processing flow is that after the client and the server establish a connection, it will first send a "China" message to the server.
After the client and server establish a connection, the channelActive event will be triggered, so the message can be sent in the client's handler:
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush("中国");
}
The server side triggers the channelRead event when reading a message from the channel, so the server-side handler can override the channelRead method:
public void channelRead(ChannelHandlerContext ctx, Object msg) {
log.info("收到消息:{}",msg);
ctx.writeAndFlush("加油!");
}
Then the client reads "Come on!" from the channel, and then writes "China" to the channel, so the client also needs to rewrite the method channelRead:
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.writeAndFlush("中国");
}
Can it go on in a loop?
Pitfalls 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 side is not triggered. why?
According to the research, if the written object 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 output of the running program, so it is still very unfriendly to novice friends of. The error is:
DefaultChannelPromise@57f5c075(failure: java.lang.UnsupportedOperationException: unsupported message type: String (expected: ByteBuf, FileRegion))
It can be seen from the error message that there are two types of messages currently supported, namely ByteBuf and FileRegion.
Okay, let's change the above message type to ByteBuf and have a try:
message = Unpooled.buffer(ChinaClient.SIZE);
message.writeBytes("中国".getBytes(StandardCharsets.UTF_8));
public void channelActive(ChannelHandlerContext ctx) {
log.info("可读字节:{},index:{}",message.readableBytes(),message.readerIndex());
log.info("可写字节:{},index:{}",message.writableBytes(),message.writerIndex());
ctx.writeAndFlush(message);
}
Above we define a ByteBuf global message object and send it to the server, and then after the server has read the message, send a ByteBuf global message object to the client, and so on.
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 subsequent "China" message sent by the client cannot be received by the server. How do I respond? What's the matter?
We know that ByteBuf has properties such as readableBytes, readerIndex, writableBytes, writerIndex, capacity, and refCnt. We compare these properties before and after the message is sent:
Before the message is sent:
可读字节:6,readerIndex:0
可写字节:14,writerIndex:6
capacity:20,refCnt:1
After the message is sent:
可读字节:6,readerIndex:0
可写字节:-6,writerIndex:6
capacity:0,refCnt:0
So the problem was found. After ByteBuf was processed once, refCnt became 0, so it cannot continue to write again. How to solve it?
The simple way is to renew a ByteBuf every time it is sent, so that there is no problem.
But creating a new object every time seems a waste of space. What should I do? Since refCnt becomes 0, can we call retain() in ByteBuf to increase refCnt?
The answer is this, but be aware that you need to call the retain() method before sending. If you call retain() after the message is processed, an exception will be reported.
Summarize
Okay, run the above program and you can always cheer for China, YYDS!
For the examples in this article, please refer 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 discover!
Welcome to pay attention to my official account: "Program those things", know technology, know you better!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。