Netty第一次使用

远程控制pdu系统

第一次使用 Netty 是在开发一个远程控制pdu系统的时候,刚开始是使用Java原生的socket api,使用的是BIO模式,系统作为一个服务端接收和发送pdu的数据,同时用户访问该系统操作pdu。采用定时任务接收数据,处理数据,保存到数据库中供用户访问。由于每次发送数据到pdu都会返回数据,其他时间只接收固定时间差的心跳检测的数据,所以开始有了使用Netty的想法。

使用的框架:springBoot、mybatisPlus、Netty、shiro、druid连接池
数据库:mysql
前端:jq、layui、bootstrap

Netty服务器

boss线程处理连接请求,将任务发送给work线程进行处理,
配置端口,其他参数等等

package com.pdu.device.netty;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;

@Component
@Slf4j
public class NettyServer {
    
    

    /**
     * boss 线程组用于处理连接工作
     */
    private EventLoopGroup boss = new NioEventLoopGroup();
    /**
     * work 线程组用于数据处理
     */
    private EventLoopGroup work = new NioEventLoopGroup();

    private Integer port=4600;

    /**
     * 启动Netty Server
     *
     * @throws InterruptedException
     */
    @PostConstruct
    public void start() throws InterruptedException {
    
    
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(boss, work)
                // 指定Channel
                .channel(NioServerSocketChannel.class)
                //使用指定的端口设置套接字地址
                .localAddress(new InetSocketAddress(port))

                //服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
                .option(ChannelOption.SO_BACKLOG, 1024)

                //设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                .childOption(ChannelOption.SO_KEEPALIVE, true)

                //将小的数据包包装成更大的帧进行传送,提高网络的负载
                .childOption(ChannelOption.TCP_NODELAY, true)

                .childHandler(new ServerChannelInitializer());
        ChannelFuture future = bootstrap.bind().sync();
        if (future.isSuccess()) {
    
    
            log.info("启动 Netty Server");
        }
    }

    @PreDestroy
    public void destory() throws InterruptedException {
    
    
        boss.shutdownGracefully().sync();
        work.shutdownGracefully().sync();
        log.info("关闭Netty");
    }
}

ServerChannel初始化

Netty解码、编码配置

package com.pdu.device.netty;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import org.apache.commons.lang3.CharSet;

import java.nio.charset.Charset;

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    
    
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
    
    
        //添加编解码
        socketChannel.pipeline().addLast("decoder", new StringDecoder(Charset.forName("GBK")));
        socketChannel.pipeline().addLast("encoder", new StringEncoder(Charset.forName("GBK")));
        socketChannel.pipeline().addLast(new NettyServerHandler());
    }
}

消息处理

连接,数据接收,连接断开消息处理及持久化,
保存连接的ChannelHandlerContext,便于发送数据,由于数据有时返回多行,所以将channelRead的Object o转化为inputStream按行遍历

package com.pdu.device.netty;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.pdu.device.MessageHandle.*;
import com.pdu.device.entity.TblDevice;
import com.pdu.device.service.ITblDeviceService;
import com.pdu.sys.common.SpringUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    
    private ITblDeviceService tblDeviceService = SpringUtil.getBean(ITblDeviceService.class);//线程获取service
    /**
     * 管理一个全局map,保存连接进服务端的通道数量
     */
    public static ConcurrentHashMap<ChannelId, ChannelHandlerContext> CHANNEL_MAP = new ConcurrentHashMap<>();
    /**
     * 管理一个全局map,保存连接进服务端的通道ChannelId
     */
    public static ConcurrentHashMap< String,ChannelId> DEVICE_ID_MAP = new ConcurrentHashMap<>();
    /**
     * 客户端连接会触发
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        log.info("Channel active......");
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();

        String clientIp = insocket.getAddress().getHostAddress();
        int clientPort = insocket.getPort();

        //获取连接通道唯一标识
        ChannelId channelId = ctx.channel().id();

        System.out.println();
        //如果map中不包含此连接,就保存连接
        if (CHANNEL_MAP.containsKey(channelId)) {
    
    
            log.info("客户端【" + channelId + "】是连接状态,连接通道数量: " + CHANNEL_MAP.size());
        } else {
    
    
            //保存连接
            CHANNEL_MAP.put(channelId, ctx);
            log.info("客户端【" + channelId + "】连接netty服务器[IP:" + clientIp + "--->PORT:" + clientPort + "]");
            log.info("连接通道数量: " + CHANNEL_MAP.size());
        }

    }

    /**
     * 客户端发消息会触发
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object o) throws Exception {
    
    
        log.info("服务器收到消息: {}", o.toString().trim());
        ChannelId channelId = ctx.channel().id();
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        try {
    
    
            InputStream inputStream = new ByteArrayInputStream(o.toString().getBytes());
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
            String msg;

                while ((msg = br.readLine()) != null) {
    
    
                    if ((msg.length() > 20) && msg.substring(0, 11).equals("START login")) {
    
    //登录
                        log.info("login消息处理开始");
                        LoginHandle loginHandle = new LoginHandle();
                        String deviceId = loginHandle.Handle(msg, clientIp, tblDeviceService);
                        DEVICE_ID_MAP.put(deviceId, channelId);
                        ctx.write("Login Successful");
                        ctx.flush();
                    } else if (msg.trim().equals("S")) {
    
    //10秒一次心跳检测
                        log.info("S消息处理开始");
                        ConnectHandle connectHandle = new ConnectHandle();
                        connectHandle.handle(clientIp, tblDeviceService);
                    } else {
    
    
                        log.info("other消息处理开始");
                        List<TblDevice> list = new ArrayList<>();
                        QueryWrapper<TblDevice> queryWrapper = new QueryWrapper<>();
                        queryWrapper.eq("public_network_ip", clientIp);//内网Ip
                        list = tblDeviceService.list(queryWrapper);
                        if (list.size() == 1) {
    
    
                            HandleMessage handleMessage = new HandleMessage();
                            handleMessage.handle(msg, list.get(0).getDeviceId(), tblDeviceService);
                        }
                    }
                }
            log.info("服务器处理消息完成");
        } catch (Exception e) {
    
    
            log.error("服务器处理消息失败");
        }
    }

    /**
     * @param ctx
     * @author xiongchuan on 2019/4/28 16:10
     * @DESCRIPTION: 有客户端终止连接服务器会触发此函数
     * @return: void
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
    
    

        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();

        String clientIp = insocket.getAddress().getHostAddress();

        ChannelId channelId = ctx.channel().id();

        //包含此客户端才去删除
        if (CHANNEL_MAP.containsKey(channelId)) {
    
    
            //删除连接
            CHANNEL_MAP.remove(channelId);

            System.out.println();
            log.info("客户端【" + channelId + "】退出netty服务器[IP:" + clientIp + "--->PORT:" + insocket.getPort() + "]");
            log.info("连接通道数量: " + CHANNEL_MAP.size());
        }
    }

    /**
     * 发生异常触发
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
        cause.printStackTrace();
        ctx.close();
    }
}

猜你喜欢

转载自blog.csdn.net/m0_38110240/article/details/105043908