Jsbintask’s blog, reproduced please indicate the source.

Netty is introduced

Nowadays we use general purpose applications or libraries to communicate with each other. For example, we often use an HTTP client library to retrieve information from a web server and to invoke a remote procedure call via web services. However, a general purpose protocol or its implementation sometimes does not scale very well. It is like how we don’t use a general purpose HTTP server to exchange huge files, e-mail messages, and near-realtime messages such as financial information and multiplayer game data. What’s required is a highly optimized protocol implementation that is dedicated to a special purpose. For example, you might want to implement an HTTP server that is optimized for AJAX-based chat application, media streaming, or large file transfer. You could even want to design and implement a whole new protocol that is precisely tailored to your need. Another inevitable case is when you have to deal with a legacy proprietary protocol to ensure the interoperability with an old system. What matters in this case is how quickly we can implement that protocol while not sacrificing the stability and performance of the resulting application.

We often want our applications to communicate with other applications. For example, we often use HTTP requests to query information or use RPC to call webService, but for this particular protocol (HTTP, FTP, etc.), it is not easy to extend it specifically for your own application. For example, we won’t use HTTP to transfer large files, emails, instant messaging (financial information), which would require a major optimization of existing protocols! So we can use Netty to customize your own protocol!

Why learn Netty?

Here is an answer borrowed from Zhihu:

As a Java student, if you haven’t studied Netty, then your use and understanding of the Java language is only superficial. You can click SSH, write a few MVC’s, access databases and caches, and that’s just what a beginner Java programmer does. Netty is definitely a must pass if you want to get to the next level of Java server knowledge. With Netty, you can implement your own HTTP server, FTP server, UDP server, RPC server, WebSocket server, Redis Proxy server, MySQL Proxy server, etc. If you want to know how Nginx is written, if you want to know how Tomcat and Jetty and Dubbo are implemented, if you also want to implement a simple Redis server, then you should understand Netty, their high performance principles are similar.

  while ture
      events = takeEvents(fds)  // Gets the event. If there is no event, the thread is hibernated
      for event in events {
          if event.isAcceptable {
              doAccept() // A new link is coming
          } elif event.isReadable {
              request = doRead() / / read the message
              if request.isComplete() {
                  doProcess()
              }
          } elif event.isWriteable {
              doWrite()  / / write message}}}Copy the code

The NIO process is basically the process described in the pseudocode above, which is a little different from the actual code, but it’s enough for beginners to understand. Netty is based on NIO, and Netty provides a higher level of abstraction on top of NIO. In Netty, Accept connections are handled by a separate thread pool, and read and write operations are handled by a separate thread pool. Accept connections and read and write operations can also be processed using the same thread pool. The request processing logic can be processed using a separate thread pool or in parallel with the read-write thread. Each thread in the thread pool is a NIO thread. Users can assemble according to the actual situation and construct a concurrency model that meets the system requirements. Netty provides built-in common codecs, including line codecs [one request per line], prefix length codecs [the first N bytes define the length of the request in bytes], replayable codecs [records the status of half-packet messages], HTTP codecs, WebSocket message codecs, etc. Netty provides a series of lifecycle callback interfaces, so when a complete request arrives, when a connection is closed, and when a connection is established, the user receives the callback event and processes it logically. Netty’s ability to manage multiple ports at the same time and its ability to use the NIO client model are necessary for RPC services. Netty can handle TCP sockets as well as UDP sockets. In the process of message reading and writing, a large number of ByteBuffer needs to be used. Netty has optimized and abstracted ByteBuffer in terms of performance and convenience. In short, Netty is a must-have magic for Java programmers to get ahead. If you know this and want to know why, take a look at Netty. If Java is boring to you, Netty is the key to reopening your interest in Java.

Summary: programmer level advanced weapon!

practice

Note: For this example, in addition to the very important core classes will be explained, other classes will not be too much explained, this chapter only do the introduction, other chapters will focus on the explanation! We’ve seen what NetTY does (having the flexibility to optimize and customize your own protocols) and why you should learn netty. That next we step by step to customize their own protocol finally complete chat room!

Print the agreement

Print protocol: the server accepts the message from the client and prints it! First we write a ChannelInboundHandlerAdapter, used for processing the received message, we first analyze the role of the class, inheritance is as follows:

PrintServerHandler

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { / / (2)
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(byteBuf.toString(Charset.forName("utf-8")));
        ctx.writeAndFlush(msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { / / (4)
        // Close the connection when an exception is raised.cause.printStackTrace(); ctx.close(); }}Copy the code

PrintServerApp: PrintServerApp: PrintServerApp: PrintServerApp: PrintServerApp:

public class EchoServerApp {
    private int port;

    public EchoServerApp(int port) {
        this.port = port;
    }

    public void run(a) throws Exception {
        NioEventLoopGroup bossLoopGroup = new NioEventLoopGroup();
        NioEventLoopGroup workLoopGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossLoopGroup, workLoopGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();

            channelFuture.channel().closeFuture().sync();
        } finally{ bossLoopGroup.shutdownGracefully(); workLoopGroup.shutdownGracefully(); }}public static void main(String[] args) throws Exception {
        new EchoServerApp(8080).run(); }}Copy the code

Start. Then we use the Telnet tool of Win to test (Control panel – “Programs and Controls -” to enable or disable the Window function, check Telnet). Open CMD and type

telnet localhost 8080
Copy the code

Objective: Start the client and obtain the server time

Time Protocol

First, write a TimeServerHandler as above:

public class TimeServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf timeBuf = ctx.alloc().buffer();
        timeBuf.writeBytes(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()).getBytes());

        ChannelFuture channelFuture = ctx.writeAndFlush(timeBuf);
        channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
            @Override
            public void operationComplete(Future<? super Void> future) throws Exception {
                assert channelFuture == future;

                // ctx.close();}}); }@Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { / / (4)
        // Close the connection when an exception is raised.cause.printStackTrace(); ctx.close(); }}Copy the code

Next, write the client TimeClientHandler:

public class TimeClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        try {
            ByteBuf byteBuf = (ByteBuf) msg;
            int length = byteBuf.readableBytes();
            byte[] buff = new byte[1024];
            byteBuf.readBytes(buff, 0, length);
            System.out.println("current time: " + new String(buff, 0, length));
            ctx.close();
        } finally{ ReferenceCountUtil.release(msg); }}@Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.cause.printStackTrace(); ctx.close(); }}Copy the code

Start the server and client separately.

chatroom server

First, the information communicated between the client and the server is abstracted into an object, Message, and a utility class:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {
    private String username;
    private Date sentTime;
    private String msg;
}
Copy the code
public class Utils {
    public static String encodeMsg(Message message) {
        return message.getUsername() + "~" + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(message.getSentTime())) + "~" + message.getMsg();
    }

    public static String formatDateTime(Date time) {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
    }

    public static Date parseDateTime(String time) {
        try {
            return new SimpleDateFormat("yyyy-MM-dd Hh:mm:ss").parse(time);
        } catch (ParseException e) {
            return null; }}public static void printMsg(Message msg) {
        System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
        System.out.println("" + Utils.formatDateTime(msg.getSentTime()) + "");
        System.out.println(msg.getUsername() + ":" + msg.getMsg());
        System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ="); }}Copy the code

ServerTransferMsgHandler; ServerTransferMsgHandler; ServerTransferMsgHandler; ServerTransferMsgHandler; ServerTransferMsgHandler;

public class ServerTransferMsgHandler extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        String totalMsg = in.readCharSequence(in.readableBytes(), Charset.forName("utf-8")).toString();
        String[] content = totalMsg.split("~");
        out.add(new Message(content[0], Utils.parseDateTime(content[1]), content[2])); }}Copy the code

Next, write a Handler to handle the received message and print the message sent from the client. ServerMsgHandler:

public class ServerMsgHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Jsbintask-client enters the chat room.");

        Message message = new Message(Constants.SERVER, new Date(), "Hello, I'm jsbintask-server side.");
        ByteBuf buffer = ctx.alloc().buffer();
        String content = Utils.encodeMsg(message);
        buffer.writeBytes(content.getBytes());

        ctx.writeAndFlush(buffer);
    }


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg1) throws Exception {
        try {
            Message msg = (Message) msg1;
            Utils.printMsg(msg);
            Scanner scanner = new Scanner(System.in);
            System.out.print("jsbintask-server, please input msg: ");
            String reply = scanner.nextLine();


            Message message = new Message(Constants.SERVER, new Date(), reply);
            ctx.writeAndFlush(message);
        } finally{ ReferenceCountUtil.release(msg1); }}@Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); }}Copy the code

The channelActive method sends a Message to the client when the client links, and finally writes a handler that converts the server Message into a Byte Message.

public class MessageEncoder extends MessageToByteEncoder<Message> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Message message, ByteBuf out) throws Exception { ByteBuf buffer = ctx.alloc().buffer(); String content = Utils.encodeMsg(message); buffer.writeBytes(content.getBytes(StandardCharsets.UTF_8)); ctx.writeAndFlush(buffer); }}Copy the code

Finally, write the server side startup class, ChatroomServerApp:

public class ChatroomServerApp {
    public static void main(String[] args) throws Exception {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer() {
                        @Override
                        protected void initChannel(Channel ch) throws Exception {
                            ch.pipeline().addLast(new MessageEncoder(), new ServerTransferMsgHandler(), new ServerMsgHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 1024 * 10)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
            channelFuture.channel().closeFuture().sync();
        } finally{ bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); }}}Copy the code

Start the Server and continue writing ChatroomClient.

chatroom client

As with the server side, the key of the client is the handler, ClientMsgHandler is as follows:

public class ClientMsgHandler extends SimpleChannelInboundHandler<Message> {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Message msg) throws Exception {
        try {
            Utils.printMsg(msg);
            Scanner scanner = new Scanner(System.in);
            System.out.print("jsbintask-client, please input msg: ");
            String reply = scanner.nextLine();

            Message message = new Message(Constants.CLIENT, new Date(), reply);
            ByteBuf buffer = ctx.alloc().buffer();
            String content = message.getUsername() + "~" + Utils.formatDateTime(message.getSentTime()) + "~" + message.getMsg();
            buffer.writeBytes(content.getBytes(StandardCharsets.UTF_8));
            ctx.writeAndFlush(buffer);
        } finally{ ReferenceCountUtil.release(msg); }}}Copy the code

Then, there is the same converter that converts byte to Message, CliengMsgHandler:

public class ClientTransferMsgHandler extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        byte[] buff = new byte[2024];
        int length = in.readableBytes();
        in.readBytes(buff, 0, length);

        String totalMsg = new String(buff, 0, length, StandardCharsets.UTF_8);
        String[] content = totalMsg.split("~");
        out.add(new Message(content[0], Utils.parseDateTime(content[1]), content[2])); }}Copy the code

Finally, launch the class ChatroomClientApp:

public class ChatroomClientApp {
    public static void main(String[] args) throws Exception {
        NioEventLoopGroup workLoopGroup = new NioEventLoopGroup();

        try {
            Bootstrap clientBootstrap = new Bootstrap();
            clientBootstrap.group(workLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new ClientTransferMsgHandler(), new ClientMsgHandler());
                        }
                    })
                    .option(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture channelFuture = clientBootstrap.connect("localhost".8888).sync();

            channelFuture.channel().closeFuture().sync();
        } finally{ workLoopGroup.shutdownGracefully(); }}}Copy the code

Also start client and observe the console. First, the server prompts the client to enter the chat room, and the client sees the “hello” message sent by the server:

conclusion

This chapter, we opened the door to learn Netty, first introduced Netty, why to learn Netty, and through three cases step by step to achieve their own protocol, print, time, message conversion protocol, successfully stepped into the door of Netty, the next chapter, we will learn about the architecture of Netty!

Source: github.com/jsbintask22… .

Keep your eyes on me! This is dry stuff!