netty使用

pom.xml

<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.25.Final</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.3</version>
        </dependency>

 

一、创建连接缓存对象

package com.example.demo.netty;

import cn.hutool.core.date.DateUtil;
import io.netty.channel.socket.SocketChannel;
import lombok.Data;

import java.sql.Timestamp;
import java.util.Date;

/**
 * netty缓存对象
 */
@Data
public class NettyCacheInfo {

    /**
     * ip:port
     */
    private String ipPort;
    /**
     * netty连接对象
     */
    private SocketChannel socketChannel;
    /**
     * 连接状态
     */
    private boolean connected;
    /**
     * 活动时间
     */
    private Timestamp activeTime;
    /**
     * 活动时间格式化
     */
    private String activeTimeFormat;


    public NettyCacheInfo() {
    }

    public void setActiveTime(Timestamp activeTime) {
        this.activeTime = activeTime;
        this.activeTimeFormat= DateUtil.format(new Date(activeTime.getTime()),"yyyy-MM-dd HH:mm:ss");
    }

}

 

二、创建netty客户端

package com.example.demo.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;

/**
 * netty客户端
 */
@Slf4j
public class NettyClient {

    private NettyClient(){}

    /**
     * Netty连接信息缓存
     * key-> host:port
     * value-> nettyInfo
     */
    public static Map<String,NettyCacheInfo> channels = new HashMap<>();
    /**
     * 失败的连接缓存
     */
    public static Map<String,NettyCacheInfo> failChannels = new HashMap<>();
    /**
     * netty连接池
     */
    static EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

    static Bootstrap bootstrap = null;
    /**
     * netty回调类
     */
    static NettyHandler nettyHandler;

    /**
     * 塞入回调处理类
     * @param handler
     */
    public static void setNettyHandler(NettyHandler handler){
        nettyHandler = handler;
    }

    /**
     * 初始化BootStrap
     * @param eventLoopGroup
     * @return
     */
    public static final Bootstrap getBootstrap(EventLoopGroup eventLoopGroup){
        if(bootstrap==null){
            bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(20480,20480,65536))
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5*1000)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline()
                                    .addLast("encoder", new RpcDecoder())
                                    .addLast(nettyHandler);
                        }
                    });
        }
        return bootstrap;
    }

    /**
     * 获取连接对象
     * @param ipPort
     */
    public static NettyCacheInfo getNettyCacheInfo(String ipPort){
        NettyCacheInfo nettyInfo = null;
        if(channels.containsKey(ipPort)){
            nettyInfo = channels.get(ipPort);
        }else{
            log.error("地址:【{}】暂无连接",ipPort);
        }
        return nettyInfo;
    }

    /**
     * 单独连接某一个装置
     * @param info
     */
    public static  void connect(NettyCacheInfo info){
        String ipPort = info.getIpPort();
        if(!channels.containsKey(ipPort)){
            //先添加缓存,方便handler中处理
            info.setConnected(false);
            String[] arr = ipPort.split(":");
            bootstrap.remoteAddress(arr[0],Integer.parseInt(arr[1]));
            //连接初始化监听
            bootstrap.connect().addListener((ChannelFutureListener) futureListener -> {
                if (futureListener.isSuccess()) {
                    log.info("监听:{}连接成功", ipPort);
                    info.setActiveTime(new Timestamp(System.currentTimeMillis()));
                    info.setSocketChannel((SocketChannel) futureListener.channel());
                    info.setConnected(true);
                    channels.put(ipPort,info);
                } else {
                    failChannels.put(ipPort,info);
                    log.info("监听:{}连接失败", ipPort);
                }
            });
        }else{
            log.info("地址:{}连接已存在",ipPort);
        }
    }
}

创建数据解码器

package com.example.demo.netty;

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

import java.util.List;

/**
 * Netty消息体解码器
 * 自定义
 */
public class RpcDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        int dataLength = in.readableBytes();
        byte[] data = new byte[dataLength];
        in.readBytes(data);
        String str = bytesToHex(data, dataLength);
        out.add(str);
    }

    /**
     * 16进制转String
     * @param bytes
     * @param counts
     * @return
     */
    private String bytesToHex(byte[] bytes, int counts){
        StringBuffer sbu = new StringBuffer();
        for (int i = 0; i < counts/* bytes.length */; i++) {
            int val = new Byte(bytes[i]).intValue();
            String str = Integer.toHexString(val & 0xff);
            if (str.length() == 1) {
                str = "0" + str;
            }
            sbu.append(str);
            if (i != counts - 1) {
                sbu.append(" ");
            }
        }
        return sbu.toString().toUpperCase();
    }
}

三、创建netty回调类

package com.example.demo.netty;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoop;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.sql.Timestamp;

/**
 * Netty回调类
 */
@Slf4j
@ChannelHandler.Sharable
@Component
public class NettyHandler extends SimpleChannelInboundHandler<String> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        try {
            //获取连接信息 ip,port
            InetSocketAddress address=(InetSocketAddress)ctx.channel().remoteAddress();
            String host = address.getHostString();
            int port = address.getPort();
            log.info("....客户端连接成功....连接地址为:"+host+":"+port);
            //获取netty缓存
            NettyCacheInfo cacheInfo = NettyClient.getNettyCacheInfo(host + ":" + port);
            if(cacheInfo!=null) {
                cacheInfo.setActiveTime(new Timestamp(System.currentTimeMillis()));
            }
            ctx.fireChannelActive();
        } catch (Exception e) {
            log.error("channelActive:"+e.getMessage());
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        try {
            //获取连接信息 ip,port
            InetSocketAddress address=(InetSocketAddress)ctx.channel().remoteAddress();
            String host = address.getHostString();
            int port = address.getPort();
            log.info("....客户端连接断开....连接地址为:"+host+":"+port);
            //获取netty缓存
            NettyCacheInfo cacheInfo = NettyClient.getNettyCacheInfo(host + ":" + port);
            if(cacheInfo!=null) {
                cacheInfo.setActiveTime(new Timestamp(System.currentTimeMillis()));
            }
            NettyClient.channels.remove(host+":"+port);
            NettyClient.failChannels.put(host+":"+port,cacheInfo);
            final EventLoop eventLoop = ctx.channel().eventLoop();
        } catch (Exception e) {
            log.error("channelInactive:"+e.getMessage());
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        try {
            //获取连接信息 ip,port
            InetSocketAddress address=(InetSocketAddress)ctx.channel().remoteAddress();
            String host = address.getHostString();
            int port = address.getPort();
            log.info("....客户端连接异常....连接地址为:{}:{}...异常信息为:{}",host,port,ctx.channel().id().asShortText());
            //获取netty缓存
            NettyCacheInfo cacheInfo = NettyClient.getNettyCacheInfo(host + ":" + port);
            if(cacheInfo!=null) {
                cacheInfo.setActiveTime(new Timestamp(System.currentTimeMillis()));
            }
            NettyClient.channels.remove(host + ":" + port);
            NettyClient.failChannels.put(host+":"+port,cacheInfo);
        } catch (Exception e) {
            log.error("exceptionCaught:"+e.getMessage());
        }
    }

    /**
     * 接受到消息后的方法
     * @param ctx
     * @param s
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
        try {
            //获取连接信息 ip,port
            InetSocketAddress address=(InetSocketAddress)ctx.channel().remoteAddress();
            String host = address.getHostString();
            int port = address.getPort();
            log.debug("接收到地址:【{}:{}】的数据:{}",host,port,s);
            //获取netty缓存
            NettyCacheInfo cacheInfo = NettyClient.getNettyCacheInfo(host + ":" + port);
            cacheInfo.setActiveTime(new Timestamp(System.currentTimeMillis()));
            //TODO:准备处理数据
        } catch (Exception e) {
            log.error("channelRead0:"+e.getMessage());
        }
    }
}

四、创建配置加载类,在项目启动时进行加载

package com.example.demo.netty;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Map;

/**
 *netty配置类
 */
@Slf4j
@Component
public class NettyConfig {

    @Resource
    NettyHandler nettyHandler;

    /**
     * 初始化加载时注入handler回调类
     */
    @PostConstruct
    public void init(){
        NettyClient.bootstrap =NettyClient.getBootstrap(NettyClient.eventLoopGroup);
        NettyClient.setNettyHandler(nettyHandler);
        log.info("进行Netty初始化加载");
    }

    /**
     * 初始化连接
     */
    @Scheduled(cron = "0 0/5 * * * ?")
    public void connectDevice(){
        log.info("删除长时间不活动的装置终端");
        long curl = System.currentTimeMillis();
        for(Map.Entry<String,NettyCacheInfo> entry:NettyClient.channels.entrySet()){
            String ipPort = entry.getKey();
            NettyCacheInfo info = entry.getValue();
            long actt = info.getActiveTime().getTime();
            log.info("地址信息:{},当前时间:{},活动时间{}",ipPort,curl,actt);
            //大于5分钟未响应
            if(curl-actt>5*60*1000){
                log.info("地址:{}连接5分钟无活动,剔除连接",ipPort);
                info.getSocketChannel().close();
                NettyClient.channels.remove(ipPort);
                info.setConnected(false);
                NettyClient.failChannels.put(ipPort,info);
            }
        }
    }


}

五、创建消息发送工具类

package com.example.demo.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.socket.SocketChannel;
import lombok.extern.slf4j.Slf4j;

/**
 * 通过netty对象发送数据
 */
@Slf4j
public class NettyWrite {

    /**
     * 发送数据-十六进制数据
     */
    public static void writeAndFlushBy(NettyCacheInfo cacheInfo, String hexString) {
        //获取连接对象
        SocketChannel channel = cacheInfo.getSocketChannel();
        if (channel == null ||
                !channel.isActive()
                || !channel.isOpen()
                || !channel.isWritable()) {
            // 获取不到串口,将任务停止删掉
            throw new NullPointerException("未获取到网口连接");
        }

        log.info("{} 开始下发报文数据!", cacheInfo.getIpPort());

        try {
            byte[] bytes = hexStrToBytes(hexString);
            ByteBuf buffer = channel.alloc().buffer();
            ByteBuf byteBuf = buffer.writeBytes(bytes);
            channel.writeAndFlush(byteBuf).addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    log.info("{} 下发报文成功!", cacheInfo.getIpPort());
                } else {
                    throw new Exception("下发报文失败");
                }
            });
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * 十六进制字符串转为数组
     * @param hexStr
     * @return
     */
    private static byte[] hexStrToBytes(String hexStr) {
        String[] hexs = splitString(hexStr);
        byte[] hexBytes = new byte[hexs.length];
        for (int i = 0; i < hexs.length; i++) {
            hexBytes[i] = hexToByte(hexs[i]);
        }
        return hexBytes;
    }

    private static byte hexToByte(String arg) {
        int val = Integer.valueOf(arg, 16).intValue();
        byte c = (byte) (val & 0xff);
        return c;
    }

    private static String[] splitString(String messageContent) {
        messageContent = messageContent.replace(" ", "");
        int leng = messageContent.length() / 2;
        String[] messageContentArray = new String[leng];
        int beginIndex = 0;
        for (int i = 0; i < leng; i++) {
            int endIndex = beginIndex + 2;
            messageContentArray[i] = messageContent.substring(beginIndex,
                    endIndex);
            beginIndex += 2;
        }
        return messageContentArray;
    }
}

 

使用方法:

List<String> addressList = new ArrayList<>();
        addressList.add("192.168.0.0.1:111");
        addressList.add("192.168.0.0.2:111");
        addressList.add("192.168.0.0.3:111");
        addressList.add("192.168.0.0.4:111");
        addressList.add("192.168.0.0.5:111");
        addressList.add("192.168.0.0.6:111");
        for (String address:addressList) {
            //创建缓存对象
            NettyCacheInfo nettyCacheInfo = new NettyCacheInfo();
            nettyCacheInfo.setIpPort(address);
            nettyCacheInfo.setConnected(false);
            //进行连接
            NettyClient.connect(nettyCacheInfo);
        }
        //发送数据
        String hexString = "01 A1 00 B2";
        NettyWrite.writeAndFlushBy(NettyClient.getNettyCacheInfo("192.168.0.0.1:111"),hexString);

 

posted @ 2022-10-05 10:32  雨梦大木  阅读(293)  评论(0编辑  收藏  举报