Introduction

After the previous series of articles, we already know the operating principle of netty, and also introduced the basic netty service construction process and the writing of the message processor. Today this article will introduce you to a more complicated example, the text chat room.

The workflow of the chat room

Today I’m going to introduce a text chat room. For a text chat room, you first need to establish a server to handle the connection of each client. For the client, you need to establish a connection with the server, and then input chat information to the server. After the server receives the chat information, it responds to the message and returns the message to the client, so that the process of a chat room is completed.

Text processor

In the previous article, we mentioned that the transmission of netty only supports ByteBuf type, and the string entered directly in the chat room is not supported. The string needs to be encoded and decoded.

The encode and decode classes we introduced earlier are called ObjectDecoder and ObjectEncoder. Today we will introduce two StringDecoder and StringEncoder that specialize in processing strings.

StringEncoder is much simpler than ObjectEncoder, because for objects, we also need to set the size of the Byte array at the head of the Byte array to ensure that all data of the object is read correctly. For String, it is relatively simple, only need to ensure that the data read in one time is a string.

StringEncoder inherits from MessageToMessageEncoder, and its core encode code is as follows:

    protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
        if (msg.length() == 0) {
            return;
        }

        out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset));
    }

As can be seen from the above code, the core actually calls the ByteBufUtil.encodeString method to convert String into ByteBuf.

For string encoding, we also need to define a range of encoding. For example, we need to know how many strings need to be encoded at one time. Generally speaking, we use carriage returns to define the end of a string input.

Netty also provides such a very convenient class called DelimiterBasedFrameDecoder. By passing in different Delimiters, we can split the input into different Frames to process a line of strings.

new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))

Let me take a look at the core code of StringDecoder. StringDecoder inherits from MessageToMessageDecoder:

    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        out.add(msg.toString(charset));
    }

By calling the toString method of ByteBuf, the BuyteBuf is converted into a string and output to the channel.

Initialize ChannelHandler

When initChannel, we need to add a valid Handler to ChannelPipeline. For this example, you need to add StringDecoder, StringEncoder, DelimiterBasedFrameDecoder and a custom handler that actually processes the message.

We put all the operations of initializing the Pipeline in a new ChatServerInitializer class, which inherits from ChannelInitializer, and its core initChannel method is as follows:

    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 添加行分割器
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        // 添加String Decoder和String Encoder,用来进行字符串的转换
        pipeline.addLast(DECODER);
        pipeline.addLast(ENCODER);
        // 最后添加真正的处理器
        pipeline.addLast(SERVER_HANDLER);
    }

ChatServerInitializer is added to the childHandler in Bootstrap:

childHandler(new ChatServerInitializer())

The real message processing logic

With the above logic, we finally only need to focus on the real message processing logic.

Our logic here is to close the channel when the client enters "goodbye", otherwise it will write the message back to the client.

The core logic is as follows:

 public void channelRead0(ChannelHandlerContext ctx, String request) throws Exception {
        // 如果读取到"再见"就关闭channel
        String response;
        // 判断是否关闭
        boolean close = false;
        if (request.isEmpty()) {
            response = "你说啥?\r\n";
        } else if ("再见".equalsIgnoreCase(request)) {
            response = "再见,我的朋友!\r\n";
            close = true;
        } else {
            response = "你是不是说: '" + request + "'?\r\n";
        }

        // 写入消息
        ChannelFuture future = ctx.write(response);
        // 添加CLOSE listener,用来关闭channel
        if (close) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

Set whether to close the button by judging the access of the client. The closing of the channel here is achieved by adding ChannelFutureListener.CLOSE to the ChannelFuture.

ChannelFutureListener.CLOSE is a ChannelFutureListener, which will close the channel after the channel is executed. In fact, this is a very elegant way to close.

    ChannelFutureListener CLOSE = new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture future) {
            future.channel().close();
        }
    };

For the client, the core is to read input from the command line. Here, InputStreamReader is used to receive command line input, and BufferedReader is used to cache it.

Then write the command line input to the channel by calling ch.writeAndFlush, and finally monitor the command line input. If you hear "goodbye", wait for the server to close the channel. The core code is as follows.

// 从命令行输入
            ChannelFuture lastWriteFuture = null;
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            for (;;) {
                String line = in.readLine();
                if (line == null) {
                    break;
                }
                // 将从命令行输入的一行字符写到channel中
                lastWriteFuture = ch.writeAndFlush(line + "\r\n");
                // 如果输入'再见',则等待server端关闭channel
                if ("再见".equalsIgnoreCase(line)) {
                    ch.closeFuture().sync();
                    break;
                }
            }

            // 等待所有的消息都写入channel中
            if (lastWriteFuture != null) {
                lastWriteFuture.sync();
            }

Summarize

After the above introduction, a simple chat room was built. We will continue to explore more complex applications in the follow-up, hope you will like it.

For the examples in this article, please refer to: learn-netty4

This article has been included in http://www.flydean.com/10-netty-chat/

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!


flydean
890 声望433 粉丝

欢迎访问我的个人网站:www.flydean.com