1、什么是netty

Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

基于 I/O 复用模型

Netty springboot 服务端发送指令给客户端_后端

2、Springboot整合Netty

① 导入依赖

<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.50.Final</version>
        </dependency>
                <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

② Application启动类时启动Netty

public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
        NettyServer nettyServer = new NettyServer();
        nettyServer.start();
    }
}

③ 创建服务启动监听器NettyServer

/**
 * 服务启动监听器
 */
@Slf4j
public class NettyServer {
    @Value("${keystore.server.path}")
    private String SERVER_PATH;

    @Value("${keystore.server.password}")
    private String SERVER_PASSWORD;

    @Value("${keystore.ca-trust.path}")
    private String CA_TRUST_PATH;

    @Value("${keystore.ca-trust.password}")
    private String CA_TRUST_PASSWORD;

    /**
     * 启动netty
     */
    public void start() {
        EventLoopGroup rootGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            //创建密钥管理器工厂
            KeyManagerFactory kmf = SSLUtil.getKeyManagerFactory("D:\\jm\\keystore\\xx.keystore", "xxx");
            //创建信任管理器工厂
            TrustManagerFactory tmf = SSLUtil.getTrustManagerFactory("D:\\jm\\keystore\\xx.keystore", "xxx");
            SslContext sslCtx = SslContextBuilder.forServer(kmf).trustManager(tmf).build();
            ServerBootstrap sb = new ServerBootstrap();
            sb.group(rootGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ServerChannelInitializer(sslCtx))
                    .handler(new LoggingHandler(LogLevel.INFO))
                    //保持连接数
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    // 有数据立即发送
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    // 保持连接
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            //绑定长连接端口号
            ChannelFuture serverChannel = sb.bind(8089).sync();
            serverChannel.channel().closeFuture().sync();
        }catch (Exception e) {
            log.info("start: {}", "网络服务启动异常" + e.getMessage());
            e.printStackTrace();
        } finally {
            rootGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
            log.info("start: {}", "网络服务关闭");
        }
    }
}

④ 封装客户端信息NettyClient

/**
 * 封装客户端的信息
 */
@Data
public class NettyClient {

    /**
     * 客户端与服务器的连接
     */
    private SocketChannel channel;

    /**
     * ip地址
     */
    private String clientIp;

    public NettyClient(SocketChannel channel, String clientIp) {
        this.channel = channel;
        this.clientIp = clientIp;
    }
}

⑤ Netty服务初始化

/**
 * netty服务初始化器
 */
@Slf4j
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    private SslContext sslCtx;

    public ServerChannelInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    /**
     * netty 初始化
     *
     * @param ch
     * @throws Exception
     */
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast("proxyDecoder", new HAProxyMessageDecoder());
        pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc()));
        pipeline.addLast("decoder", new KYPNetDecoder());//解码
        pipeline.addLast("encoder", new KYPNetEncoder());//编码
        pipeline.addLast("handler", new NettyServerHandler());//处理器
        System.out.println("ChatClient:" + ch.remoteAddress() + "连接上");
    }
}

⑥ 当有新的客户端连接的时候,用于保存客户端信息

public class NettyChannelMap {
    /**
     * 保存客户端的Map
     */
    public static Map<String, NettyClient> map = new ConcurrentHashMap<>();

    /**
     * 添加客户端
     *
     * @param clientId 客户端地址
     * @param client
     */
    public static void add(String clientId, NettyClient client) {
        map.put(clientId, client);
    }

    /**
     * 获取客户端
     *
     * @param clientId 客户端地址
     * @return
     */
    public static NettyClient get(String clientId) {
        return map.get(clientId);
    }

    /**
     * 移除客户端
     *
     * @param socketChannel
     */
    public static void remove(SocketChannel socketChannel) {
        for (Map.Entry entry : map.entrySet()) {
            if (((NettyClient) entry.getValue()).getChannel() == socketChannel) {
                map.remove(entry.getKey());
            }
        }
    }
}

⑦ Netty服务端处理器

/**
 * netty服务端处理器
 */
@Slf4j
public class NettyServerHandler extends SimpleChannelInboundHandler {
    KYPConnectVerify connectVerify = new KYPConnectVerify();

    @Autowired
    private ParkingMessageInfoService parkingMessageInfoService;

    @Autowired
    private WebSocketServer webSocketServer;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // TODO Auto-generated method stub
        super.handlerRemoved(ctx);
    }


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object entity) {
        String clientIp = null;
        if (entity instanceof HAProxyMessage) {
            HAProxyMessage proxyMessage = (HAProxyMessage) entity;
            clientIp = proxyMessage.sourceAddress();// 原始IP
            NettyClient client = new NettyClient((SocketChannel) ctx.channel(), clientIp);
            NettyChannelMap.add(clientIp, client);
        } else {
            if (entity instanceof KYPResponseParam) {
                KYPResponseParam kypResponseParam = (KYPResponseParam) entity;
                log.info("KYPResponseParam: {}", JSONObject.toJSONString(kypResponseParam));
            } else {
                if (clientIp != null) {
                    handleRequest(ctx.channel(), (KYPRequestParam) entity, clientIp);
                }
            }
        }
    }

    /**
     * 接收数据
     *
     * @param ctx    上下文
     * @param entity 接收到的数据
     * @throws Exception
     */
//    @Override
//    protected void channelRead0(ChannelHandlerContext ctx, Object entity) throws Exception {
//
//    }

    /**
     * 有新连接时触发
     *
     * @param ctx
     */
    @Override
    public void channelActive(final ChannelHandlerContext ctx) {
        log.info("有新的客户端连接:{}", getClientIp(ctx.channel()));
//        String clientIp = getClientIp(ctx.channel());
//        NettyClient client = new NettyClient((SocketChannel) ctx.channel(), getClientIp(ctx.channel()));
//        NettyChannelMap.add(clientIp, client);
    }

    /**
     * 客户端断开时触发
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        Channel incoming = ctx.channel();
        log.info("客户端断开连接:{}", incoming.remoteAddress());
        NettyChannelMap.remove((SocketChannel) incoming);
    }

    /**
     * 抛出netty执行异常
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        log.info("抛出异常执行,包括客户端断开连接时,会抛出IO异常");
        // 当出现异常就关闭连接
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * 获取客户端IP
     *
     * @param channel
     * @return
     */
    private String getClientIp(Channel channel) {
        InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.remoteAddress();
        String clientIP = inetSocketAddress.getAddress().getHostAddress();
        return clientIP;
    }

    /**
     * 处理客户端请求
     *
     * @param channel
     * @param requestParam
     */
    private void handleRequest(Channel channel, KYPRequestParam requestParam, String clientIp) {
        log.info("DevID: {}", requestParam.getDevid());
        KYPResponseParam responseParam = null;

        if (requestParam.getProcessnum() == ProcessNums.KEEPLONGLIVE) {//建立长连接(定时请求服务心跳)
            responseParam = handleLongLiveConnect(requestParam);
        } else {
            responseParam = handle(requestParam.getProcessnum(), requestParam);
            log.info("Processnum: {}", requestParam.getProcessnum());
        }
        //不同服务
        if (responseParam != null) {//通过管道通信 回传
            ChannelFuture channelFuture = channel.writeAndFlush(responseParam);
            log.info("channelFuture: 回传状态 {}", channelFuture.isSuccess());
            //todo
            ParkingMessageInfo messageInfo = parkingMessageInfoService.lambdaQuery()
                    .eq(ParkingMessageInfo::getClientIp, clientIp)
                    .eq(ParkingMessageInfo::getStatus, "0")
                    .eq(ParkingMessageInfo::getProcessNum, responseParam.getProcessnum())
                    .one();
            if(ObjectUtil.isNotEmpty(messageInfo)){
                webSocketServer.sendOneMessage(messageInfo.getUserId(),JSONObject.toJSONString(responseParam));
                parkingMessageInfoService.lambdaUpdate() .eq(ParkingMessageInfo::getClientIp, clientIp)
                        .eq(ParkingMessageInfo::getStatus, "0")
                        .eq(ParkingMessageInfo::getProcessNum, responseParam.getProcessnum())
                        .set(ParkingMessageInfo::getStatus,"1").update();
            }
        }
    }

    /**
     * 处理请求
     *
     * @param process      业务代码
     * @param requestParam 请求参数
     * @return
     */
    public KYPResponseParam handle(int process, KYPRequestParam requestParam) {
        KYPResponseParam responseParam = new KYPResponseParam();
        KYPResponseBody body = new KYPResponseBody();
        if (!connectVerify.verifyRequest(requestParam)) {
            responseParam.setProcessnum((short) process);
            responseParam.setDevid(requestParam.getDevid());
            responseParam.setRequestno(requestParam.getRequestno());
            String sendTime = TextHelper.getDayTimeFormat().format(new Date());
            responseParam.setSendtime(sendTime);
            responseParam.setRetcode(-3);
            responseParam.setRetmsg("签名验证失败");
            body.setResultCode(-3);
            body.setResultMsg("签名验证失败");
        } else {
            responseParam.setProcessnum((short) process);
            responseParam.setDevid(requestParam.getDevid());
            responseParam.setRequestno(requestParam.getRequestno());
            String sendTime = TextHelper.getDayTimeFormat().format(new Date());
            responseParam.setSendtime(sendTime);
            responseParam.setRetcode(0);
            responseParam.setRetmsg("成功");
            body.setResultCode(0);
            body.setResultMsg("成功");
        }
        connectVerify.sign(responseParam);//签名

        if (process == ProcessNums.NETHEARTBEAT) {//心跳代码
            body.put("systemtime", TextHelper.getDayTimeFormat().format(new Date()));

        } else if ((process > 30 && process < 50) || (process >= 300 && process <= 399) || (process >= 400 && process <= 499)) {//业务
            log.info("业务请求参数: {}", JSONObject.toJSONString(requestParam));
            body.setResultCode(0);
            body.setResultMsg("业务忽略");

        } else {
            body.setResultCode(0);
            body.setResultMsg("业务忽略");

        }
        responseParam.setBody(body);
        return responseParam;
    }

    /**
     * 绑定长连接请求,回传数据
     *
     * @param requestParam
     * @return
     */
    public KYPResponseParam handleLongLiveConnect(KYPRequestParam requestParam) {
        KYPResponseParam responseParam = new KYPResponseParam();
        responseParam.setDevid(requestParam.getDevid());
        responseParam.setProcessnum(ProcessNums.KEEPLONGLIVE);
        responseParam.setRequestno(requestParam.getRequestno());
        String sendTime = TextHelper.getDayTimeFormat().format(new Date());
        responseParam.setSendtime(sendTime);
        connectVerify.sign(responseParam);
        JSONObject body = new JSONObject();
        if (!connectVerify.verifyRequest(requestParam)) {
            responseParam.setRetcode(DataProtocal.FAILCODEREADFAIL);
            responseParam.setRetmsg("签名验证失败");
            body.put("resultcode", -3);
            body.put("resultmsg", "签名验证失败");
        } else {
            responseParam.setRetcode(0);
            responseParam.setRetmsg("建立长连接成功");
        }
        return responseParam;
    }
}