Netty学习总结

概述

什么是Netty?

Netty是一个NIO客户端服务端框架,支持快速、简单地开发网络应用程序,可以迅速地构建起TCP和UDP服务程序。

为什么要使用Netty二不直接使用NIO?

以下摘自《Netty权威指南 第2版》

  1. NIO的类库和API繁杂,使用麻烦,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等;
  2. 需要具备其它的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程设计到Reactor模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序;
  3. 可靠性能力补齐,工作量和难度都非常大。例如客户端面临重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐的工作量和难度都非常大;
  4. JDK NIO的bug,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK1.6版本的update8修复该问题,但是直到JDK 1.7版本该问题仍旧存在,只不过该bug发生概率降低了一些而已,它并没有得到根本性的解决。

由于上述原因,在大多数场景下,不建议直接使用JDK的NIO类库,除非你精通NIO编程或有特殊的需求。在绝大多数的业务场景中,我们可以使用NIO框架Netty来进行NIO编程,它既可以作为客户端也可以作为服务端,同时支持UDP和异步文件传输,功能非常强大。

API使用

使用Netty创建服务端程序

以下内容介绍了如何使用Netty提供的API构建一个基本的服务端程序。

一、主体代码

    private void bind(int port) {
        // 初始化两个线程租
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            // ServerChannel的引导程序,为了使ServerChannel更加易用
            ServerBootstrap bootstrap = new ServerBootstrap();
            /* bossGroup与workerGroup是两个线程组,bossGroup用于接收客户端的请求,
                workerGroup用于处理接收到的SocketChannel的网络读写 */
            bootstrap.group(bossGroup, childGroup)
                    .channel(NioServerSocketChannel.class)// 设置需要启动的Channel实例
                    /* 设置NioServerSocketChannel的TCP参数,
                        backlog的解析请看这里 https://www.cnblogs.com/qiumingcheng/p/9492962.html */
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChildChannelHandler());// 设置处理接收到的请求的处理类
            ChannelFuture future = bootstrap.bind(port).sync();// 为ServerBootstrap绑定监听端口
            future.channel().closeFuture().sync();// 关闭NioServerSocketChannel
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放线程组
            bossGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }

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

二、childHandler

ServerBootstrap的childHandler函数要求传入一个实现了io.netty.channel.ChannelHandler的实例,childHandler是提供给childGroup中的线程的时间回调,从名字也可以看出这一点,我们传入的是一个ChildChannelHandler实例,这是我们自己实现的一个类,它并不是一个直接处理SocketChannel的类,而是继承了ChannelInitializer,用来组织更多的实际的处理类。

ChildChannelHandler代码:

    /**
     * ServerBootstrap中的childGroup中接收到的ChildChannel的处理类,
     * 用于处理接收到的请求
     */
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel socketChannel) {
            /* 这里的SocketChannel是Netty提供的,pipeline是一个ChildHandler的list,
                即我们可以使用多个Handler对接收到的SocketChannel进行处理 */
            socketChannel.pipeline()
                    .addLast(new ActualHandler1(), new ActualHandler2());
        }
    }

三、ActualHandler

上面说到了,ChildChannelHandler其实只是一个用来组织实际处理类的聚合管理类,实际的处理类是代码中的ActualHandler1和ActualHandler2,我们看下一个ActualHandler应该怎样实现。

    private class ActualHandler extends ChannelHandlerAdapter {

        /**
         * 有新建连接创建时,会首先回调此函数
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            System.out.println("有新建连接创建");
        }

        /**
         * 读取接收到的ServerChannel中的数据
         *
         * @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("接收到请求消息:" + reqMsg);
            ByteBuf respBuf = Unpooled.copiedBuffer("你好,我已经收到你的消息了".getBytes(StandardCharsets.UTF_8));
            ctx.write(respBuf);
        }

        /**
         * read完成后调用的函数,进行一些数据读取完毕后的操作
         *
         * @param ctx
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            System.out.println("读取完毕");
            ctx.flush();
        }

        /**
         * 出现异常时的回调函数
         *
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            System.out.println("出异常了!");
            cause.printStackTrace();
            ctx.close();
        }
    }

解析

ActualHandler就是真正用来处理接收到的SocketChannel的处理类,在这里我们可以进行消息的读写解码等等一些操作。

我们这里的ActualHandler继承了ChannelHandlerAdapter,ChannelHandlerAdapter继承了ChannelHandler,里面有很多各种各样的回调函数,可以根据自己的需求灵活选取。

使用Netty创建客户端程序

前面介绍了创建服务端程序的方法,客户端其实和服务端大同小异。

一、主体代码

    /**
     * 连接指定的远端服务,写法与Server端的bind类似
     *
     * @param host 远端服务ip
     * @param 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());
                        }
                    });
            // 发起异步连接操作
            ChannelFuture future = bootstrap.connect(host, port).sync();
            // 等待客户端链路关闭
            future.channel().closeFuture().sync();
        } catch (Throwable t) {
            t.printStackTrace();
            group.shutdownGracefully();
        }
    }

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

二、handler

服务端设置的是childHandler,客户端需要设置handler,原理与服务端一致。

这里我们直接使用了匿名内部类来初始化ChannelInitializer实例。

三、ActualHandler

原理与服务端一致。

private class ActualHandler extends ChannelHandlerAdapter {
    /**
     * 当连接成功时,会回调此函数,我们可以在此函数中向服务端发送消息
     *
     * @param ctx
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
            byte[] reqMsg = "你好~".getBytes(StandardCharsets.UTF_8);
            ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
            ctx.writeAndFlush(buf);
    }

    /**
     * 服务端回复消息时会回调此函数
     *
     * @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("收到服务端的返回消息:" + respMsg);
        ctx.close();
    }

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

使用Netty解决粘包问题

在网络编程中,粘包问题是往往是无法回避的问题,通常我们处理粘包有以下几种方式:

  1. 使用特定标识字符作为结束符;
  2. 固定消息长度;
  3. 将消息分为消息头与消息体,在消息头中声明消息总长度;

以前我们需要手动实现拆包的代码,繁杂而枯燥,而Netty为我们提供了几种现成的拆包处理类,使用非常简便。

  1. LineBasedFrameDecoder;
  2. DelimiterBasedFrameDecoder;
  3. FixedLengthFrameDecoder,使用定长字节数作为消息长度进行拆包;

LineBasedFrameDecoder

LineBasedFrameDecoder使用换行符作为结束符进行拆包

使用方法:

  1. 在接收消息的一方的SocketChannel.pipeline中加入LineBasedFrameDecoder,构造器中参数为每行消息最大长度,如果达到最大长度仍未接收到换行符,则会抛出异常。

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

    在LineBasedFrameDecoder后面我们还加入了StringDecoder,这个解码器很简单,就是将字节码转为字符串,这样我们收到的消息就不是ByteBuf,而直接是String了。

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String respMsg = (String) msg;// 接收到的直接是String
        System.out.println("当前的时间:" + respMsg);
        ctx.close();
    }
    
  2. 在消息发送的地方加入换行符作为结尾

    byte[] msg = ("我是消息" + System.getProperty("line.separator"))
        .getBytes(StandardCharsets.UTF_8);
    

完成这两步,基于换行符拆包就搞定了,是不是炒鸡简单,节省了我们大量的拆包工作。

DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder允许我们使用指定字符作为结束符进行拆包,其实换行符就是一种特殊的指定字符,所以DelimiterBasedFrameDecoder的用法与LineBasedFrameDecoder的用法完全一致,只需要发送方将换行符变更为其它指定字符即可。

byte[] msg = ("我是消息" + "$")// 以$作为结束符
    .getBytes(StandardCharsets.UTF_8);

FixedLengthFrameDecoder

FixedLengthFrameDecoder可以让我们使用定长字节数来拆分消息。

  1. 消息接收方:

    socketChannel.pipeline()
        .addLast(
        new FixedLengthFrameDecoder(39)// frameLength是一个完整消息的byte数据长度
        , new StringDecoder()
        , new FixedLengthTimeServerHandler());
    
  2. 消息发送方只要按照约定的字节数长度来发送消息即可。

使用Netty编写的简单时间服务

下面的程序是基于Netty编写的简单的时间服务,有服务端和客户端程序,因为将最基础的基于字节数据的方式和三种解码器的方式都整合在了一起,所以代码比较长,建议将代码获取到IDE中进行阅读,代码中有必要的注释。

此程序存放于Gitee,仓库地址:https://gitee.com/imdongrui/study-repo.git

仓库中的ioserver和ioclient工程

服务端程序

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;

/**
 * 基于Netty实现的简单时间服务
 */
public class SimpleTimeServer {

    private int counter = 0;

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

    private String mode = modes[3];

    /**
     * 初始化ServerBootstrap,并绑定指定的端口
     *
     * @param port 需要监听的端口
     */
    private void bind(int port) {
        // 初始化两个线程租
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            // ServerChannel的引导程序,为了使ServerChannel更加易用
            ServerBootstrap bootstrap = new ServerBootstrap();
            /* bossGroup与workerGroup是两个线程组,bossGroup用于接收客户端的请求,
                workerGroup用于处理接收到的SocketChannel的网络读写 */
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)// 设置需要启动的Channel实例
                    /* 设置NioServerSocketChannel的TCP参数,
                        backlog的解析请看这里 https://www.cnblogs.com/qiumingcheng/p/9492962.html */
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChildChannelHandler());// 设置处理接收到的请求的处理类
            ChannelFuture future = bootstrap.bind(port).sync();// 为ServerBootstrap绑定监听端口
            future.channel().closeFuture().sync();// 关闭NioServerSocketChannel
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放线程组
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    /**
     * ServerBootstrap中的childGroup中接收到的ChildChannel的处理类,
     * 用于处理接收到的请求
     */
    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel socketChannel) {
            /* 这里的SocketChannel是Netty提供的,pipeline是一个ChildHandler的list,
                即我们可以使用多个Handler对接收到的SocketChannel进行处理 */
            switch (mode) {
                case "simple":
                    socketChannel.pipeline()
                            .addLast(new SimpleTimeServerHandler());
                    break;
                case "lineBased":
                    socketChannel.pipeline()
                            // 使用LineBasedFrameDecoder可以以换行符为结束标识对消息进行拆分,构造器中参数为每行消息最大长度,如果达到最大长度仍未接收到换行符,则会抛出异常
                            .addLast(new LineBasedFrameDecoder(1024))
                            // StringDecoder可以将消息转码为String
                            .addLast(new StringDecoder())
                            .addLast(new LineBasedTimeServerHandler());
                    break;
                case "delimiterBased":
                    socketChannel.pipeline()
                            // 使用DelimiterBasedFrameDecoder可以以指定标识为结束标识对消息进行拆分
                            .addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8))))
                            // StringDecoder可以将消息转码为String
                            .addLast(new StringDecoder())
                            .addLast(new DelimiterBasedTimeServerHandler());
                    break;
                case "fixedLength":
                    socketChannel.pipeline()
                            // frameLength是一个完整消息的byte数据长度
                            .addLast(new FixedLengthFrameDecoder(39))
                            .addLast(new StringDecoder())
                            .addLast(new FixedLengthTimeServerHandler());
                    break;
            }
        }
    }

    /**
     * 实际处理时间服务的Handler,不对消息进行行处理,即未解决粘包问题
     */
    private class SimpleTimeServerHandler extends ChannelHandlerAdapter {

        /**
         * 有新建连接创建时,会首先回调此函数
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            System.out.println("有新建连接创建");
        }

        /**
         * 读取接收到的ServerChannel中的数据
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            // 不使用LineBasedFrameDecoder和StringDecoder处理粘包时的写法,接收到的msg是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("接收到请求消息:" + reqMsg);
            ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + "").getBytes(StandardCharsets.UTF_8));
            ctx.write(respBuf);
        }

        /**
         * read完成后调用的函数,进行一些数据读取完毕后的操作
         *
         * @param ctx
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            System.out.println("读取完毕");
            ctx.flush();
        }

        /**
         * 出现异常时的回调函数
         *
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }

    }

    /**
     * 实际处理时间服务的Handler,对消息进行行处理以解决粘包问题
     */
    private class LineBasedTimeServerHandler extends ChannelHandlerAdapter {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("有新建连接");
        }

        /**
         * 读取接收到的ServerChannel中的数据
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            // 使用LineBasedFrameDecoder和StringDecoder处理粘包时的写法,接收到的msg是String
            String reqMsg = (String) msg;
            System.out.println("counter: " + ++counter);
            System.out.println("接收到请求消息:" + reqMsg);
            // 当服务端使用了LineBasedFrameDecoder,那么每条消息结尾需要加上line.separator,否则服务端消息读取无法完成
            ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8));
            ctx.write(respBuf);
        }

        /**
         * read完成后调用的函数,进行一些数据读取完毕后的操作
         *
         * @param ctx
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            System.out.println("读取完毕");
            ctx.flush();
        }

        /**
         * 出现异常时的回调函数
         *
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }

    }

    private class DelimiterBasedTimeServerHandler extends ChannelHandlerAdapter {
        /**
         * 读取接收到的ServerChannel中的数据
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String reqMsg = (String) msg;
            System.out.println("counter: " + ++counter);
            System.out.println("接收到请求消息:" + reqMsg);
            ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + "$").getBytes(StandardCharsets.UTF_8));
            ctx.write(respBuf);
        }

        /**
         * read完成后调用的函数,进行一些数据读取完毕后的操作
         *
         * @param ctx
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            System.out.println("读取完毕");
            ctx.flush();
        }

        /**
         * 出现异常时的回调函数
         *
         * @param ctx
         * @param cause
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

    private class FixedLengthTimeServerHandler extends ChannelHandlerAdapter {
        /**
         * 读取接收到的ServerChannel中的数据
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String reqMsg = (String) msg;
            System.out.println("counter: " + ++counter);
            System.out.println("接收到请求消息:" + reqMsg);
            ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + "").getBytes(StandardCharsets.UTF_8));
            ctx.write(respBuf);
        }

        /**
         * read完成后调用的函数,进行一些数据读取完毕后的操作
         *
         * @param ctx
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            System.out.println("读取完毕");
            ctx.flush();
        }

        /**
         * 出现异常时的回调函数
         *
         * @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);
    }
}

客户端程序

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];

    /**
     * 连接指定的远端服务,写法与Server端的bind类似
     *
     * @param host 远端服务ip
     * @param 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()
                                            // 使用LineBasedFrameDecoder可以以换行符为结束标识对消息进行拆分,构造器中参数为每行消息最大长度,如果达到最大长度仍未接收到换行符,则会抛出异常
                                            .addLast(new LineBasedFrameDecoder(1024))
                                            // StringDecoder可以将消息转码为String
                                            .addLast(new StringDecoder())
                                            .addLast(new LineBasedTimeClientHandler());
                                    break;
                                case "delimiterBased":
                                    ch.pipeline()
                                            // 使用DelimiterBasedFrameDecoder可以以指定标识为结束标识对消息进行拆分
                                            .addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8))))
                                            // StringDecoder可以将消息转码为String
                                            .addLast(new StringDecoder())
                                            .addLast(new DelimiterBasedTimeClientHandler());
                                    break;
                                case "fixedLength":
                                    ch.pipeline()
                                            .addLast(new FixedLengthFrameDecoder(13))
                                            .addLast(new StringDecoder())
                                            .addLast(new FixedLengthTimeClientHandler());
                                    break;
                            }
                        }
                    });
            // 发起异步连接操作
            ChannelFuture future = bootstrap.connect(host, port).sync();
            // 等待客户端链路关闭
            future.channel().closeFuture().sync();
        } catch (Throwable t) {
            t.printStackTrace();
            group.shutdownGracefully();
        }

    }

    private class SimpleTimeClientHandler extends ChannelHandlerAdapter {
        /**
         * 当连接成功时,会回调此函数,我们可以在此函数中向服务端发送消息
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            for (int i = 0; i < 100; i++) {
                byte[] reqMsg = ("快告诉我时间,不然干掉你!" + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8);
                ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
                ctx.writeAndFlush(buf);
            }
        }

        /**
         * 服务端回复消息时会回调此函数
         *
         * @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("当前的时间:" + respMsg);
            ctx.close();
        }

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

    private class LineBasedTimeClientHandler extends ChannelHandlerAdapter {
        /**
         * 当连接成功时,会回调此函数,我们可以在此函数中向服务端发送消息
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            for (int i = 0; i < 100; i++) {
                // 当服务端使用了LineBasedFrameDecoder,那么每条消息结尾需要加上line.separator,否则服务端消息读取无法完成
                byte[] reqMsg = ("快告诉我时间,不然干掉你!" + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8);
                ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
                ctx.writeAndFlush(buf);
            }
        }

        /**
         * 服务端回复消息时会回调此函数
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String respMsg = (String) msg;
            System.out.println("当前的时间:" + respMsg);
            ctx.close();
        }

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

    private class DelimiterBasedTimeClientHandler extends ChannelHandlerAdapter {
        /**
         * 当连接成功时,会回调此函数,我们可以在此函数中向服务端发送消息
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            for (int i = 0; i < 100; i++) {
                // 当服务端使用了LineBasedFrameDecoder,那么每条消息结尾需要加上line.separator,否则服务端消息读取无法完成
                byte[] reqMsg = ("快告诉我时间,不然干掉你!" + "$").getBytes(StandardCharsets.UTF_8);
                ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
                ctx.writeAndFlush(buf);
            }
        }

        /**
         * 服务端回复消息时会回调此函数
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String respMsg = (String) msg;
            System.out.println("当前的时间:" + respMsg);
            ctx.close();
        }

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

    private class FixedLengthTimeClientHandler extends ChannelHandlerAdapter {
        /**
         * 当连接成功时,会回调此函数,我们可以在此函数中向服务端发送消息
         *
         * @param ctx
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            for (int i = 0; i < 100; i++) {
                // 当服务端使用了LineBasedFrameDecoder,那么每条消息结尾需要加上line.separator,否则服务端消息读取无法完成
                byte[] reqMsg = "快告诉我时间,不然干掉你!".getBytes(StandardCharsets.UTF_8);
                ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
                ctx.writeAndFlush(buf);
            }
        }

        /**
         * 服务端回复消息时会回调此函数
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            String respMsg = (String) msg;
            System.out.println("当前的时间:" + 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);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值