也不知道说什么   也是记录一下所用到的东西     因为  我得数据板  发送得是   tcp 10进制数据   所以  在监听端口上传信息时   要进行一此转换  10 进制  转换  16进制    下面是  所用到得所有类

spring boot发送tcp协议 springboot接收tcp数据_spring boot发送tcp协议

 

spring boot发送tcp协议 springboot接收tcp数据_.net_02

 MyNettyServer 类
 

package com.sys_iot;

import com.sys_iot.system.netty.MyNettyChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @author lics
 * @version 1.0.0
 * @date 2023/6/1 13:12
 */
public class MyNettyServer {

    public void startNettyServer(int... ports) {

        /*
         * 配置服务端的NIO线程组
         * NioEventLoopGroup 是用来处理I/O操作的Reactor线程组
         * bossGroup:用来接收进来的连接,workerGroup:用来处理已经被接收的连接,进行socketChannel的网络读写,
         * bossGroup接收到连接后就会把连接信息注册到workerGroup
         * workerGroup的EventLoopGroup默认的线程数是CPU核数的二倍
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup(3);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // ServerBootstrap 是一个启动NIO服务的辅助启动类
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            // 设置group,将bossGroup, workerGroup线程组传递到ServerBootstrap
            serverBootstrap = serverBootstrap.group(bossGroup, workerGroup);

            // ServerSocketChannel是以NIO的selector为基础进行实现的,用来接收新的连接,这里告诉Channel通过NioServerSocketChannel获取新的连接
            serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);

            // 初始化通道,主要用于网络I/O事件,记录日志,编码、解码消息
            serverBootstrap = serverBootstrap.childHandler(new MyNettyChannelInitializer());

            // 绑定端口,同步等待成功
            ChannelFuture[] channelFutures;
            ChannelFuture futureTcp;
            if (ports.length > 0) {
                channelFutures = new ChannelFuture[ports.length];
                int i = 0;
                for (Integer port : ports) {
                    // 绑定端口,同步等待成功 绑定的服务器
                    futureTcp = serverBootstrap.bind(port).sync();
                    channelFutures[i++] = futureTcp;
                    futureTcp.addListener(future -> {
                        if (future.isSuccess()) {
                            System.out.println("netty server 启动成功!" + port);
                        } else {
                            System.out.println("netty server 启动失败!" + port);
                        }
                    });
                }
                for (ChannelFuture future : channelFutures) {
                    // 等待服务器监听端口关闭
                    future.channel().closeFuture().sync();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 退出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}


RuoYiApplication 启动类


package com.sys_iot;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.Async;

/**
 * 启动程序
 *
 * @author ruoyi
 */
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class RuoYiApplication implements CommandLineRunner
{
    public static void main(String[] args)
    {
        // System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(RuoYiApplication.class, args);
        System.out.println("(♥◠‿◠)ノ゙  若依启动成功   ლ(´ڡ`ლ)゙  \n" +
                " .-------.       ____     __        \n" +
                " |  _ _   \\      \\   \\   /  /    \n" +
                " | ( ' )  |       \\  _. /  '       \n" +
                " |(_ o _) /        _( )_ .'         \n" +
                " | (_,_).' __  ___(_ o _)'          \n" +
                " |  |\\ \\  |  ||   |(_,_)'         \n" +
                " |  | \\ `'   /|   `-'  /           \n" +
                " |  |  \\    /  \\      /           \n" +
                " ''-'   `'-'    `-..-'              ");
        SpringApplication app = new SpringApplication(RuoYiApplication.class);
        app.run(args);


    }
    @Async
    @Override
    public void run(String... args) {

        /*
         * 使用异步注解方式启动netty服务端服务
         * 8001   监听数据版 上传数据
         * 8002   监听屏幕设备数据链接
         * 8003   监听设备板子 发送控制
         */
        new MyNettyServer().startNettyServer(8003, 8001);
    }

}


Cpmpone 类


package com.sys_iot.system.netty;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/**
 *
 */

public class Cpmpone {

//    @Autowired
//    private  TcpRequestHandler tcpRequestHandlers;

//    @Autowired
//    public Cpmpone(TcpRequestHandler tcpRequestHandlers) {
//        this.tcpRequestHandlers = tcpRequestHandlers;
//    }


    public void ryMultipleParams(String command) {
        try {
             // tcpRequestHandlers  s是nukl
//            tcpRequestHandlers.distributionControl(command);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}


MyDecoder 类


package com.sys_iot.system.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

public class MyDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        String HEXES = "0123456789ABCDEF";
        byte[] req = new byte[msg.readableBytes()];
        msg.readBytes(req);
        final StringBuilder hex = new StringBuilder(2 * req.length);

        for (int i = 0; i < req.length; i++) {
            byte b = req[i];
            hex.append(HEXES.charAt((b & 0xF0) >> 4))
                    .append(HEXES.charAt((b & 0x0F)));
        }
        out.add(hex.toString());
    }

}


MyEncoder 类


package com.sys_iot.system.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * @ClassName NettyMessageEncoder
 * @Deacription TODO 自定义发送消息格式  发送16进制
 * @Author LiuDaGang
 * @Date 2021/3/11 19:19
 * @Version 1.0
 **/
public class MyEncoder  extends MessageToByteEncoder<String> {


    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, String s, ByteBuf byteBuf) throws Exception {
        //将16进制字符串转为数组
        byteBuf.writeBytes(hexString2Bytes(s));
    }

    /**

     * @Title:hexString2Bytes

     * @Description:16进制字符串转字节数组

     * @param src 16进制字符串

     * @return 字节数组

     */

    public static byte[] hexString2Bytes(String src) {

        int l = src.length() / 2;

        byte[] ret = new byte[l];

        for (int i = 0; i < l; i++) {

            ret[i] = (byte) Integer.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();

        }

        return ret;

    }

}


MyNettyChannelInitializer 类


package com.sys_iot.system.netty;

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;

/**
 * 通道初始化
 *
 * @author lics
 * @version 1.0.0
 * @date 2023/6/1 13:12
 */
public class MyNettyChannelInitializer extends ChannelInitializer<Channel> {

    @Override
    protected void initChannel(Channel channel) {
        /**
         * 处理TCP请求
         */
        // ChannelOutboundHandler,依照逆序执行
        channel.pipeline().addLast("encoder", new MyEncoder());
        // 属于ChannelInboundHandler,依照顺序执行
        channel.pipeline().addLast("decoder", new MyDecoder());
        // 自定义TCP请求的处理器
        channel.pipeline().addLast(new TcpRequestHandler());
    }

}


MyTools 类 这个类里面得 writeToClient() 方法就是我们后面 服务端 像客户端发送消息得方法


package com.sys_iot.system.netty;

import com.sys_iot.common.utils.StringUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;

import java.util.HashMap;
import java.util.Map;

public class MyTools {

    //十六进制字符转十进制
    public int covert(String content){
        int number=0;
        String [] HighLetter = {"A","B","C","D","E","F"};
        Map<String,Integer> map = new HashMap<>();
        for(int i = 0;i <= 9;i++){
            map.put(i+"",i);
        }
        for(int j= 10;j<HighLetter.length+10;j++){
            map.put(HighLetter[j-10],j);
        }
        String[]str = new String[content.length()];
        for(int i = 0; i < str.length; i++){
            str[i] = content.substring(i,i+1);
        }
        for(int i = 0; i < str.length; i++){
            number += map.get(str[i])*Math.pow(16,str.length-1-i);
        }
        return number;
    }

    public byte[] hexString2Bytes(String src) {
        int l = src.length() / 2;
        byte[] ret = new byte[l];
        for (int i = 0; i < l; i++) {
            ret[i] = (byte) Integer
                    .valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue();
        }
        return ret;
    }

    public void writeToClient(final String receiveStr, ChannelHandlerContext channel, final String mark) {
        try {
            ByteBuf bufff = Unpooled.buffer();//netty需要用ByteBuf传输
            bufff.writeBytes(hexString2Bytes(receiveStr));//对接需要16进制
            System.out.println(channel);
            channel.writeAndFlush(bufff).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    StringBuilder sb = new StringBuilder();
                    if(!StringUtils.isEmpty(mark)){
                        sb.append("【").append(mark).append("】");
                    }
                    if (future.isSuccess()) {
                        System.out.println(sb.toString()+"回写成功"+receiveStr);
                    } else {
                        System.out.println(sb.toString()+"回写失败"+receiveStr);

                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("调用通用writeToClient()异常"+e.getMessage());

        }
    }

}


NettyConfig 这个类 对你们来说 没有用 就是 我用来 声明bean用得 使用了@Configuration 注解


 然后  util   就是 所用的  工具类了    具体我记得当时  我是用来   在  TcpRequestHandler类   里面  获取其他类得bean  使用的   可以用   也可以不用   看自己 需要 

package com.sys_iot.system.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtil implements ApplicationContextAware {
    private static ApplicationContext ac;

    @Override
    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
        ac = arg0;
    }

    public static ApplicationContext getApplicationContext() {
        return ac;
    }

    /**
     * 通过class获取Bean
     */
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }


    /**
     * 如果BeanFactory包含所给名称匹配的bean返回true
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name) {
        return ac.containsBean(name);
    }

    /**
     * 判断注册的bean是singleton还是prototype。
     * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     * @param name
     * @return boolean
     */
    public static boolean isSingleton(String name) {
        return ac.isSingleton(name);
    }

    /**
     * @param name
     * @return Class 注册对象的类型
     */
    public static Class<?> getType(String name) {
        return ac.getType(name);
    }

}

tool

package com.sys_iot.system.util;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

/**
 * 工具类      ctrl + G    输入下方行数
 * 1.字符串转换成十六进制字符串                  25  行
 * 2.十六进制转换字符串                         49  行
 * 3.bytes转换成十六进制字符串                   70  行
 * 4.bytes字符串转换为Byte值                    88  行
 * 5.String的字符串转换成unicode的String        109  行
 * 6.unicode的String转换成String的字符串        134  行
 * 7.获取当前时间  返回字符串                    467  行
 */

public class Tool {

    /**
     * 字符串转换成十六进制字符串
     * @param String str 待转换的ASCII字符串
     * @return String 每个Byte之间空格分隔,如: [61 6C 6B]
     */
    public static String str2HexStr(String str)
    {

        char[] chars = "0123456789ABCDEF".toCharArray();
        StringBuilder sb = new StringBuilder("");
        byte[] bs = str.getBytes();
        int bit;

        for (int i = 0; i < bs.length; i++)
        {
            bit = (bs[i] & 0x0f0) >> 4;
            sb.append(chars[bit]);
            bit = bs[i] & 0x0f;
            sb.append(chars[bit]);
            sb.append(' ');
        }
        return sb.toString().trim();
    }

    /**
     * 十六进制转换字符串
     * @param String str Byte字符串(Byte之间无分隔符 如:[616C6B])
     * @return String 对应的字符串
     */
    public static String hexStr2Str(String hexStr)
    {
        String str = "0123456789ABCDEF";
        char[] hexs = hexStr.toCharArray();
        byte[] bytes = new byte[hexStr.length() / 2];
        int n;

        for (int i = 0; i < bytes.length; i++)
        {
            n = str.indexOf(hexs[2 * i]) * 16;
            n += str.indexOf(hexs[2 * i + 1]);
            bytes[i] = (byte) (n & 0xff);
        }
        return new String(bytes);
    }

    /**
     * bytes转换成十六进制字符串
     * @param byte[] b byte数组
     * @return String 每个Byte值之间空格分隔
     */
    public static String byte2HexStr(byte[] b)
    {
        String stmp="";
        StringBuilder sb = new StringBuilder("");
        for (int n=0;n<b.length;n++)
        {
            stmp = Integer.toHexString(b[n] & 0xFF);
            sb.append((stmp.length()==1)? "0"+stmp : stmp);
            sb.append(" ");
        }
        return sb.toString().toUpperCase().trim();
    }

    /**
     * bytes字符串转换为Byte值
     * @param String src Byte字符串,每个Byte之间没有分隔符
     * @return byte[]
     */
    public static byte[] hexStr2Bytes(String src)
    {
        int m=0,n=0;
        int l=src.length()/2;
        System.out.println(l);
        byte[] ret = new byte[l];
        for (int i = 0; i < l; i++)
        {
            m=i*2+1;
            n=m+1;
            ret[i] = Byte.decode("0x" + src.substring(i*2, m) + src.substring(m,n));
        }
        return ret;
    }

    /**
     * String的字符串转换成unicode的String
     * @param String strText 全角字符串
     * @return String 每个unicode之间无分隔符
     * @throws Exception
     */
    public static String strToUnicode(String strText)
            throws Exception
    {
        char c;
        StringBuilder str = new StringBuilder();
        int intAsc;
        String strHex;
        for (int i = 0; i < strText.length(); i++)
        {
            c = strText.charAt(i);
            intAsc = (int) c;
            strHex = Integer.toHexString(intAsc);
            if (intAsc > 128)
                str.append("\\u" + strHex);
            else // 低位在前面补00
                str.append("\\u00" + strHex);
        }
        return str.toString();
    }

    /**
     * unicode的String转换成String的字符串
     * @param String hex 16进制值字符串 (一个unicode为2byte)
     * @return String 全角字符串
     */
    public static String unicodeToString(String hex)
    {
        int t = hex.length() / 6;
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < t; i++)
        {
            String s = hex.substring(i * 6, (i + 1) * 6);
            // 高位需要补上00再转
            String s1 = s.substring(2, 4) + "00";
            // 低位直接转
            String s2 = s.substring(4);
            // 将16进制的string转为int
            int n = Integer.valueOf(s1, 16) + Integer.valueOf(s2, 16);
            // 将int转换为字符
            char[] chars = Character.toChars(n);
            str.append(new String(chars));
        }
        return str.toString();
    }

    /**
     * 获取当前时间  返回字符串
     */
    public static String time()
    {
        Date d = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss ");
        sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        return sdf.format(d);
    }




}

最后得 最后   就是  最最最主要的类了  TcpRequestHandler 类   我所 注释得地方  均为  我当前项目需要得    你们可以用来 参考  或者  自己写写

package com.sys_iot.system.netty;

import java.net.InetSocketAddress;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import com.sys_iot.system.service.IARoomScheduleService;
import com.sys_iot.system.service.impl.MyNettySeviceImpl;
import com.sys_iot.system.util.SpringUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * I/O数据读写处理类 TCP连接
 * <p>
 * tcp设备数据上传及下发数据
 *
 * @author lics
 * @version 1.0.0
 * @date 2023/6/1 13:12
 */
@ChannelHandler.Sharable
public class TcpRequestHandler extends ChannelInboundHandlerAdapter {

    // 这个东西有点坑  依赖注入 不成功  注意下方static 通过一个 spring工具类   获取bean才可以
//    @Autowired
//    private static IARoomScheduleService roomScheduleService;
//
//    @Autowired
//    private static MyNettySeviceImpl myNettySeviceImpl;
//
//    static {
//        roomScheduleService = SpringUtil.getBean(IARoomScheduleService.class);
//        myNettySeviceImpl = SpringUtil.getBean(MyNettySeviceImpl.class);
//    }

    private static ChannelHandlerContext ctx; // 使用静态变量存储

    /**
     * 从客户端收到新的数据时,这个方法会在收到消息时被调用
     * //  ctx.write(we);
     * //  msg  客户端发送数据
     * //  System.out.println("channelRead: " + msg.toString());
     *
     * @param ctx
     * @param msg
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        MyTools myTools = new MyTools();
        String masg = String.valueOf(msg);
        // clientIp客户端链接ip
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        System.out.println("客户端发送数据: " + msg.toString() + clientIp);
        System.out.println("客户端iP: " + clientIp);
        // 192.168.1.200  为  数据下发数据板   所有  之获取这一个静态变量  用作下放得下发
        if("192.168.1.200".equals(clientIp)){
            TcpRequestHandler.ctx = ctx; // 存储 ChannelHandlerContext
        }
        // 截取报头
//        String str =  String.valueOf(msg).substring(0,2);
//        if (!"12".equals(str) && !"27".equals(str)){
//            // 回应客户端 注册信息  返回27
//            myTools.writeToClient("2700AA0fA0005533221105", ctx, "回复客户端  27  ");
//            // 调用等待  TimeUnit  延迟  1秒 下发信息
//            this.JavaDelayExample();
//            // 下发信息    客户端获取控制列表
//            myTools.writeToClient("2A1A00AA00555533221105", ctx, "回复客户端获取控制列表");
//            // 调用等待  TimeUnit  延迟  1秒 下发信息
//            this.JavaDelayExample();
//            // 下发信息    回复客户端布防发送
//            myTools.writeToClient("2400AA00553322110500020001", ctx, "回复客户端布防发送");
//
//        }else if ("12".equals(str)){
//            // 下发信息    回复客户端布防发送
//            myNettySeviceImpl.transportVersionData(msg.toString(),clientIp);
//        }
    }

    /**
     * 客户端与服务端第一次建立连接时 执行
     *
     * @param ctx
     * @throws Exception
     */
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        // 获取客户端 ip
        ctx.channel().read();
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
//        //判断 ip不为空   去掉也可以  无所谓这块
//        if (clientIp != null) {
//            // 拿取当前 ip 去库里查询  是否存在此 ip  返回 0 则 无此ip   返回 1 则 有此 ip
//            String ye = roomScheduleService.selectQueryIp(clientIp);
//            if ("0".equals(ye)) {
//                // 无此 ip 则 新增此 ip
//                roomScheduleService.insertIp(clientIp);
//            }
//        }
        //此处不能使用ctx.close(),否则客户端始终无法与服务端建立连接 clientIp 客户端ip
        System.out.println("channelActive: " + clientIp + ctx.name());
    }


    /**
     * 向设备发送数据
     */
    public void distributionControl(String command) throws Exception {
        System.out.println("调用进来了~~~~");
        MyTools myTools = new MyTools();
        // 延迟一秒
        this.JavaDelayExample();
        // 获取适当的 ChannelHandlerContext 对象,例如通过构造函数或方法参数传递
        // 在其他方法中使用存储的 ChannelHandlerContext
        if (TcpRequestHandler.ctx != null) {
            myTools.writeToClient(command, TcpRequestHandler.ctx, "回复客户端");
        }
        System.out.println("正在发送设备命令:" + command);
    }

    /**
     * TimeUnit  延迟  1秒
     */
    public void JavaDelayExample() {

        try {

            System.out.println("Start..." + new Date());
            // delay 5 seconds
            TimeUnit.SECONDS.sleep(2);
            System.out.println("End..." + new Date());

        } catch (InterruptedException e) {
            System.err.format("IOException: %s%n", e);
        }

    }

    /**  JavaDelayExample
     * 下方 为暂时未使用方法
     * ---------------------------------------------------------------------------------------------
     */


    /**
     * 从客户端收到新的数据、读取完成时调用
     *
     * @param ctx
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        System.out.println("channel Read Complete");
        ctx.flush();
    }

    /**
     * 当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时
     *
     * @param ctx
     * @param cause
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        System.out.println("exceptionCaught");
        cause.printStackTrace();
        ctx.close();//抛出异常,断开与客户端的连接
    }

    /**
     * 客户端与服务端 断连时 执行
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        ctx.close(); //断开连接时,必须关闭,否则造成资源浪费,并发量很大情况下可能造成宕机
        System.out.println("channelInactive: " + clientIp);
    }

    /**
     * 服务端当read超时, 会调用这个方法
     *
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        ctx.close();//超时时断开连接
        System.out.println("userEventTriggered: " + clientIp);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) {
        System.out.println("channelRegistered");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) {
        System.out.println("channelUnregistered");
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) {
        System.out.println("channelWritabilityChanged");
    }

}