Netty Learning Summary

Keywords: Netty socket codec network

Articles Catalogue

Summary

What is Netty?

Netty is a NIO client server framework, which supports rapid and simple development of network applications and can quickly build TCP and UDP service programs.

Why don't Netty 2 use NIO directly?

The following is excerpted from the second edition of the Netty Authoritative Guide

  1. NIO class libraries and API s are complex and difficult to use. It requires proficiency in Selector, Server Socket Channel, Socket Channel, ByteBuffer, etc.
  2. Other additional skills are needed to pave the way, such as familiarity with Java multithreading programming. This is because NIO programming design to Reactor mode, you must be very familiar with multi-threading and network programming, in order to write high-quality NIO programs;
  3. Reliability ability ability is complementary, workload and difficulty are very large. For example, clients are faced with problems such as reconnection, network interruption, half-packet read-write, failure cache, network congestion and abnormal bit stream processing. NIO programming is characterized by relatively easy function development, but the workload and difficulty of reliability capability complementation are very large.
  4. JDK NIO bugs, such as the notorious epoll bug, can lead to Selector empty polling and eventually CPU 100%. Officials claim to fix the problem in JDK version 1.6 update 8, but it still exists until JDK version 1.7, except that the probability of the bug occurring is reduced a little, and it has not been fundamentally solved.

For these reasons, in most scenarios, it is not recommended to use JDK's NIO libraries directly unless you are proficient in NIO programming or have special requirements. In most business scenarios, we can use the NIO framework Netty for NIO programming. It can be used as both a client and a server, while supporting UDP and asynchronous file transfer. It is very powerful.

API usage

Using Netty to Create Server-side Programs

The following describes how to use the API provided by Netty to build a basic server-side program.

I. Body Code

    private void bind(int port) {
        // Initialize two thread rents
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            // Server Channel bootstrap to make Server Channel easier to use
            ServerBootstrap bootstrap = new ServerBootstrap();
            /* bossGroup It is two thread groups with workerGroup, which receives requests from clients.
                workerGroup Network Read and Write for Processing Received Socket Channel */
            bootstrap.group(bossGroup, childGroup)
                    .channel(NioServerSocketChannel.class)// Setting up Channel instances that need to be started
                    /* Setting the TCP parameters of NioServerSocket Channel,
                        backlog For an analysis, see https://www.cnblogs.com/qiumingcheng/p/9492962.html */
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChildChannelHandler());// Setting the processing class to process the received request
            ChannelFuture future = bootstrap.bind(port).sync();// Binding listening ports for ServerBootstrap
            future.channel().closeFuture().sync();// Close NioServer Socket Channel
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // Release Thread Group
            bossGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        SimpleTimeServer server = new SimpleTimeServer();
        server.bind(8088);
    }

childHandler

Server Bootstrap's childHandler function requires an instance that implements io.netty.channel.ChannelHandler. ChildHandler is a time callback that provides threads in the child group. As you can see from the name, we pass in an instance of ChildChannelHandler, which is a class we implemented ourselves. It is not a class that handles SocketChannel directly, but inherits ChannelInitializer to organize more actual processing classes.

ChildChannelHandler code:

    /**
     * ServerBootstrap The processing class of the ChildChannel received in the childGroup in the.
     * Used to process received requests
     */
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel socketChannel) {
            /* Here the Socket Channel is provided by Netty, pipeline is a list of ChildHandler.
                That is, we can use multiple handlers to process the received Socket Channel. */
            socketChannel.pipeline()
                    .addLast(new ActualHandler1(), new ActualHandler2());
        }
    }

Actual Handler

As mentioned above, ChildChannelHandler is actually just an aggregation management class used to organize actual processing classes. The actual processing classes are ActualHandler 1 and ActualHandler 2 in the code. Let's see how the next ActualHandler should be implemented.

    private class ActualHandler extends ChannelHandlerAdapter {

        /**
         * When a new connection is created, this function is called back first.
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            System.out.println("Create a new connection");
        }

        /**
         * Read the data in the received Server Channel
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ByteBuf reqBuf = (ByteBuf) msg;
            byte[] bytes = new byte[reqBuf.readableBytes()];
            reqBuf.readBytes(bytes);
            String reqMsg = new String(bytes, StandardCharsets.UTF_8);
            System.out.println("Receive the request message:" + reqMsg);
            ByteBuf respBuf = Unpooled.copiedBuffer("Hello, I have received your message.".getBytes(StandardCharsets.UTF_8));
            ctx.write(respBuf);
        }

        /**
         * read Functions called after completion, some data read after the completion of the operation
         *
         * @param ctx
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            System.out.println("Read out");
            ctx.flush();
        }

        /**
         * Callback function in case of exception
         *
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            System.out.println("There's something unusual!");
            cause.printStackTrace();
            ctx.close();
        }
    }

Analysis:

Actual Handler is the real processing class used to process the received SocketChannel, where we can read, write and decode messages and so on.

Our Actual Handler inherits ChannelHandler Adapter and ChannelHandler Adapter inherits ChannelHandler. There are many kinds of callback functions, which can be selected flexibly according to their own needs.

Using Netty to Create Client Programs

The method of creating server-side program was introduced before, and the client and the server are very similar in fact.

I. Body Code

    /**
     * Connect to the specified remote service, similar to the bind on the Server side
     *
     * @param host Remote service ip
     * @param port Remote Service Port
     */
    public void connect(String host, int port) {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            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) {
                            ch.pipeline().addLast(new ActualHandler());
                        }
                    });
            // Initiate an asynchronous connection operation
            ChannelFuture future = bootstrap.connect(host, port).sync();
            // Waiting for Client Link to Close
            future.channel().closeFuture().sync();
        } catch (Throwable t) {
            t.printStackTrace();
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new SimpleTimeClient().connect("localhost", 8088);
    }

handler

childHandler is set on the server side, and handler needs to be set on the client side. The principle is consistent with that on the server side.

Here we use anonymous inner classes directly to initialize ChannelInitializer instances.

Actual Handler

The principle is consistent with the server.

private class ActualHandler extends ChannelHandlerAdapter {
    /**
     * When the connection is successful, this function is called back, where we can send messages to the server.
     *
     * @param ctx
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
            byte[] reqMsg = "Hello~".getBytes(StandardCharsets.UTF_8);
            ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
            ctx.writeAndFlush(buf);
    }

    /**
     * This function is called back when the server replies to the message
     *
     * @param ctx
     * @param msg
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf buf = (ByteBuf) msg;
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);
        String respMsg = new String(bytes, StandardCharsets.UTF_8);
        System.out.println("Receive the return message from the server:" + respMsg);
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

Using Netty to Solve the Sticking Pack Problem

In network programming, sticky package problem is often unavoidable. Usually we deal with sticky package in the following ways:

  1. Use a specific identifier character as the terminator;
  2. Fixed message length;
  3. The message is divided into header and body, and the total length of the message is declared in the header.

In the past, we need to implement the unpacking code manually, which is tedious and tedious. Netty provides us with several ready-made unpacking classes, which are very easy to use.

  1. LineBasedFrameDecoder;
  2. DelimiterBasedFrameDecoder;
  3. Fixed Length Frame Decoder uses fixed bytes as message length for unpacking.

LineBasedFrameDecoder

LineBasedFrameDecoder unpacks using line breaks as terminators

Usage method:

  1. LineBasedFrameDecoder is added to SocketChannel.pipeline of the receiving party. The constructor parameter is the maximum length of each line of message. If the maximum length has not been received, an exception will be thrown.

    socketChannel.pipeline().addLast(
        new LineBasedFrameDecoder(1024)
        , new StringDecoder()
        , new LineBasedTimeServerHandler());
    

    We also added String Decoder after Line Based Frame Decoder, which is very simple, that is, to convert the bytecode into a string, so that the message we receive is not ByteBuf, but String.

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String respMsg = (String) msg;// Received directly is String
        System.out.println("Current time:" + respMsg);
        ctx.close();
    }
    
  2. Add a newline character to the end of the message

    byte[] msg = ("I'm the news." + System.getProperty("line.separator"))
        .getBytes(StandardCharsets.UTF_8);
    

Complete these two steps, based on the newline character unpacking will be done, whether fried chicken is simple, saving us a lot of unpacking work.

DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder allows us to unpackage using specified characters as terminators. In fact, newline characters are a special kind of specified characters. So the use of DelimiterBasedFrameDecoder is exactly the same as that of LineBasedFrameDecoder. It only needs the sender to change the newline characters to other specified characters.

byte[] msg = ("I'm the news." + "$")// End with $
    .getBytes(StandardCharsets.UTF_8);

FixedLengthFrameDecoder

Fixed Length Frame Decoder allows us to split messages using a fixed number of bytes.

  1. Message Receiver:

    socketChannel.pipeline()
        .addLast(
        new FixedLengthFrameDecoder(39)// frameLength is the byte data length of a complete message
        , new StringDecoder()
        , new FixedLengthTimeServerHandler());
    
  2. The sender of the message only needs to send the message according to the agreed byte length.

Simple Time Service Written with Netty

The following program is a simple time service based on Netty. There are server and client programs. Because the most basic byte-based data and three decoders are integrated together, the code is relatively long. It is recommended that the code be read in the IDE, and the necessary annotations are included in the code.

The program is stored in Gitee and the warehouse address is https://gitee.com/imdongrui/study-repo.git.

ioserver and ioclient engineering in warehouse

Server-side program

package com.dongrui.study.ioserver.nettyserver;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * Simple Time Service Based on Netty
 */
public class SimpleTimeServer {

    private int counter = 0;

    private String[] modes = {"simple", "lineBased", "delimiterBased", "fixedLength"};

    private String mode = modes[3];

    /**
     * Initialize ServerBootstrap and bind the specified port
     *
     * @param port Ports to listen on
     */
    private void bind(int port) {
        // Initialize two thread rents
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            // Server Channel bootstrap to make Server Channel easier to use
            ServerBootstrap bootstrap = new ServerBootstrap();
            /* bossGroup It is two thread groups with workerGroup, which receives requests from clients.
                workerGroup Network Read and Write for Processing Received Socket Channel */
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)// Setting up Channel instances that need to be started
                    /* Setting the TCP parameters of NioServerSocket Channel,
                        backlog For an analysis, see https://www.cnblogs.com/qiumingcheng/p/9492962.html */
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChildChannelHandler());// Setting the processing class to process the received request
            ChannelFuture future = bootstrap.bind(port).sync();// Binding listening ports for ServerBootstrap
            future.channel().closeFuture().sync();// Close NioServer Socket Channel
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // Release Thread Group
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    /**
     * ServerBootstrap The processing class of the ChildChannel received in the childGroup in the.
     * Used to process received requests
     */
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel socketChannel) {
            /* Here the Socket Channel is provided by Netty, pipeline is a list of ChildHandler.
                That is, we can use multiple handlers to process the received Socket Channel. */
            switch (mode) {
                case "simple":
                    socketChannel.pipeline()
                            .addLast(new SimpleTimeServerHandler());
                    break;
                case "lineBased":
                    socketChannel.pipeline()
                            // Using LineBasedFrameDecoder, messages can be split with line breaks as end identifiers. The constructor parameter is the maximum length of each line of messages. If the maximum length has not been received, an exception will be thrown.
                            .addLast(new LineBasedFrameDecoder(1024))
                            // StringDecoder can transcode messages to String
                            .addLast(new StringDecoder())
                            .addLast(new LineBasedTimeServerHandler());
                    break;
                case "delimiterBased":
                    socketChannel.pipeline()
                            // Using DelimiterBasedFrameDecoder, messages can be split with a designated identifier as the end identifier
                            .addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8))))
                            // StringDecoder can transcode messages to String
                            .addLast(new StringDecoder())
                            .addLast(new DelimiterBasedTimeServerHandler());
                    break;
                case "fixedLength":
                    socketChannel.pipeline()
                            // frameLength is the byte data length of a complete message
                            .addLast(new FixedLengthFrameDecoder(39))
                            .addLast(new StringDecoder())
                            .addLast(new FixedLengthTimeServerHandler());
                    break;
            }
        }
    }

    /**
     * Handler, the actual processing time service, does not process messages in rows, that is, it does not solve the sticky package problem.
     */
    private class SimpleTimeServerHandler extends ChannelHandlerAdapter {

        /**
         * When a new connection is created, this function is called back first.
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            System.out.println("Create a new connection");
        }

        /**
         * Read the data in the received Server Channel
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            // Writing without using LineBasedFrameDecoder and StringDecoder to process sticky packages, the received msg is ByteBuf
            ByteBuf reqBuf = (ByteBuf) msg;
            byte[] bytes = new byte[reqBuf.readableBytes()];
            reqBuf.readBytes(bytes);
            String reqMsg = new String(bytes, StandardCharsets.UTF_8);
            System.out.println("counter: " + ++counter);
            System.out.println("Receive the request message:" + reqMsg);
            ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + "").getBytes(StandardCharsets.UTF_8));
            ctx.write(respBuf);
        }

        /**
         * read Functions called after completion, some data read after the completion of the operation
         *
         * @param ctx
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            System.out.println("Read out");
            ctx.flush();
        }

        /**
         * Callback function in case of exception
         *
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }

    }

    /**
     * Handler, the actual processing time service, processes messages in rows to solve the sticky package problem
     */
    private class LineBasedTimeServerHandler extends ChannelHandlerAdapter {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("New connections");
        }

        /**
         * Read the data in the received Server Channel
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            // Writing when processing sticky packages using LineBasedFrameDecoder and StringDecoder, the received msg is String
            String reqMsg = (String) msg;
            System.out.println("counter: " + ++counter);
            System.out.println("Receive the request message:" + reqMsg);
            // When the server uses the LineBasedFrameDecoder, the line.separator is added at the end of each message, otherwise the message reading on the server cannot be completed.
            ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8));
            ctx.write(respBuf);
        }

        /**
         * read Functions called after completion, some data read after the completion of the operation
         *
         * @param ctx
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            System.out.println("Read out");
            ctx.flush();
        }

        /**
         * Callback function in case of exception
         *
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }

    }

    private class DelimiterBasedTimeServerHandler extends ChannelHandlerAdapter {
        /**
         * Read the data in the received Server Channel
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String reqMsg = (String) msg;
            System.out.println("counter: " + ++counter);
            System.out.println("Receive the request message:" + reqMsg);
            ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + "$").getBytes(StandardCharsets.UTF_8));
            ctx.write(respBuf);
        }

        /**
         * read Functions called after completion, some data read after the completion of the operation
         *
         * @param ctx
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            System.out.println("Read out");
            ctx.flush();
        }

        /**
         * Callback function in case of exception
         *
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    private class FixedLengthTimeServerHandler extends ChannelHandlerAdapter {
        /**
         * Read the data in the received Server Channel
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String reqMsg = (String) msg;
            System.out.println("counter: " + ++counter);
            System.out.println("Receive the request message:" + reqMsg);
            ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + "").getBytes(StandardCharsets.UTF_8));
            ctx.write(respBuf);
        }

        /**
         * read Functions called after completion, some data read after the completion of the operation
         *
         * @param ctx
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            System.out.println("Read out");
            ctx.flush();
        }

        /**
         * Callback function in case of exception
         *
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    public static void main(String[] args) {
        SimpleTimeServer server = new SimpleTimeServer();
        server.bind(8088);
    }
}

Client program

package com.dongrui.study.ioclient.nettyclient;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

import java.nio.charset.StandardCharsets;

public class SimpleTimeClient {

    private String[] modes = {"simple", "lineBased", "delimiterBased", "fixedLength"};

    private String mode = modes[3];

    /**
     * Connect to the specified remote service, similar to the bind on the Server side
     *
     * @param host Remote service ip
     * @param port Remote Service Port
     */
    public void connect(String host, int port) {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            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) {
                            switch (mode) {
                                case "simple":
                                    ch.pipeline()
                                            .addLast(new SimpleTimeClientHandler());
                                    break;
                                case "lineBased":
                                    ch.pipeline()
                                            // Using LineBasedFrameDecoder, messages can be split with line breaks as end identifiers. The constructor parameter is the maximum length of each line of messages. If the maximum length has not been received, an exception will be thrown.
                                            .addLast(new LineBasedFrameDecoder(1024))
                                            // StringDecoder can transcode messages to String
                                            .addLast(new StringDecoder())
                                            .addLast(new LineBasedTimeClientHandler());
                                    break;
                                case "delimiterBased":
                                    ch.pipeline()
                                            // Using DelimiterBasedFrameDecoder, messages can be split with a designated identifier as the end identifier
                                            .addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8))))
                                            // StringDecoder can transcode messages to String
                                            .addLast(new StringDecoder())
                                            .addLast(new DelimiterBasedTimeClientHandler());
                                    break;
                                case "fixedLength":
                                    ch.pipeline()
                                            .addLast(new FixedLengthFrameDecoder(13))
                                            .addLast(new StringDecoder())
                                            .addLast(new FixedLengthTimeClientHandler());
                                    break;
                            }
                        }
                    });
            // Initiate an asynchronous connection operation
            ChannelFuture future = bootstrap.connect(host, port).sync();
            // Waiting for Client Link to Close
            future.channel().closeFuture().sync();
        } catch (Throwable t) {
            t.printStackTrace();
            group.shutdownGracefully();
        }

    }

    private class SimpleTimeClientHandler extends ChannelHandlerAdapter {
        /**
         * When the connection is successful, this function is called back, where we can send messages to the server.
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            for (int i = 0; i < 100; i++) {
                byte[] reqMsg = ("Tell me the time or kill you!" + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8);
                ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
                ctx.writeAndFlush(buf);
            }
        }

        /**
         * This function is called back when the server replies to the message
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ByteBuf buf = (ByteBuf) msg;
            byte[] bytes = new byte[buf.readableBytes()];
            buf.readBytes(bytes);
            String respMsg = new String(bytes, StandardCharsets.UTF_8);
            System.out.println("Current time:" + respMsg);
            ctx.close();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    private class LineBasedTimeClientHandler extends ChannelHandlerAdapter {
        /**
         * When the connection is successful, this function is called back, where we can send messages to the server.
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            for (int i = 0; i < 100; i++) {
                // When the server uses the LineBasedFrameDecoder, the line.separator is added at the end of each message, otherwise the message reading on the server cannot be completed.
                byte[] reqMsg = ("Tell me the time or kill you!" + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8);
                ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
                ctx.writeAndFlush(buf);
            }
        }

        /**
         * This function is called back when the server replies to the message
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String respMsg = (String) msg;
            System.out.println("Current time:" + respMsg);
            ctx.close();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    private class DelimiterBasedTimeClientHandler extends ChannelHandlerAdapter {
        /**
         * When the connection is successful, this function is called back, where we can send messages to the server.
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            for (int i = 0; i < 100; i++) {
                // When the server uses the LineBasedFrameDecoder, the line.separator is added at the end of each message, otherwise the message reading on the server cannot be completed.
                byte[] reqMsg = ("Tell me the time or kill you!" + "$").getBytes(StandardCharsets.UTF_8);
                ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
                ctx.writeAndFlush(buf);
            }
        }

        /**
         * This function is called back when the server replies to the message
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String respMsg = (String) msg;
            System.out.println("Current time:" + respMsg);
            ctx.close();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    private class FixedLengthTimeClientHandler extends ChannelHandlerAdapter {
        /**
         * When the connection is successful, this function is called back, where we can send messages to the server.
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            for (int i = 0; i < 100; i++) {
                // When the server uses the LineBasedFrameDecoder, the line.separator is added at the end of each message, otherwise the message reading on the server cannot be completed.
                byte[] reqMsg = "Tell me the time or kill you!".getBytes(StandardCharsets.UTF_8);
                ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
                ctx.writeAndFlush(buf);
            }
        }

        /**
         * This function is called back when the server replies to the message
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String respMsg = (String) msg;
            System.out.println("Current time:" + respMsg);
            ctx.close();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    public static void main(String[] args) {
        new SimpleTimeClient().connect("localhost", 8088);
    }
}

Posted by FeralReason on Sun, 08 Sep 2019 23:12:24 -0700