package com.artfess.base.webSocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.concurrent.TimeUnit;

/**
 * @author 陈实
 * @Package com.artfess.base.webSocket
 * @date 2021/9/16 9:54
 * @Description:
 */
@Component
public class NettyServer {
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);
    /**
     * webSocket协议名
     */
    private static final String WEBSOCKET_PROTOCOL = "websocket";

    /**
     * 端口号：不能与主程序的端口重复
     */
    @Value("${webSocket.netty.port:8099}")
    private int port;

    /**
     * webSocket路径
     */
    @Value("${webSocket.netty.path:/websocket}")
    private String webSocketPath;

    @Autowired
    private WebSocketHandler webSocketHandler;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;

    /**
     * 启动
     *
     * @throws InterruptedException
     */
    private void start() throws InterruptedException {
        bossGroup = new NioEventLoopGroup();
        workGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
            bootstrap.group(bossGroup, workGroup);
            // 设置NIO类型的channel
            bootstrap.channel(NioServerSocketChannel.class);
            // 设置监听端口
            bootstrap.localAddress(new InetSocketAddress(port));
            // 连接到达时会创建一个通道
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                // 初始化器
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    // 流水线管理通道中的处理程序（Handler），用来处理业务
                    // webSocket协议本身是基于http协议的，所以这边也要使用http编解码器
                    ch.pipeline().addLast(new HttpServerCodec());
                    //以块的方式来写的处理器
                    ch.pipeline().addLast(new ChunkedWriteHandler());
                    //netty是基于分段请求的，HttpObjectAggregator的作用是将请求分段再聚合,参数是聚合字节的最大长度
                    ch.pipeline().addLast(new HttpObjectAggregator(1024 * 64));
                    //设置log监听器，并且日志级别为debug，方便观察运行流程
                    ch.pipeline().addLast(new LoggingHandler("info"));

                    ch.pipeline().addLast(new ObjectEncoder());

                    //针对客户端，若10s内无读事件则触发心跳处理方法HeartBeatHandler#userEventTriggered
                    //在该方法中去处理 IdleStateEvent(读空闲，写空闲，读写空闲)
                    ch.pipeline().addLast(new IdleStateHandler(7000, 7000, 11, TimeUnit.SECONDS));
                    //自定义空闲状态检测(自定义心跳检测handler)
                    ch.pipeline().addLast(new HeartBeatHandler());
                    /*
                    说明：
                    1、对应webSocket，它的数据是以帧（frame）的形式传递
                    2、浏览器请求时 ws://localhost:8090xxx 表示请求的uri
                    3、核心功能是将http协议升级为ws协议，保持长连接
                    */
                    ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
                    // 自定义的handler，处理业务逻辑
                    ch.pipeline().addLast(webSocketHandler);

                }
            });
            // 配置完成，开始绑定server，通过调用sync同步方法阻塞直到绑定成功
            ChannelFuture channelFuture = bootstrap.bind().sync();
            log.info("NettyServer started and listen on:{}", channelFuture.channel().localAddress());
            // 对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();//关闭主线程
            workGroup.shutdownGracefully();//关闭从线程
        }
    }

    /**
     * 释放资源
     *
     * @throws InterruptedException
     */
    @PreDestroy
    public void destroy() throws InterruptedException {
        if (bossGroup != null) {
            bossGroup.shutdownGracefully().sync();
        }
        if (workGroup != null) {
            workGroup.shutdownGracefully().sync();
        }
    }

    @PostConstruct()
    public void init() {
        //需要开启一个新的线程来执行netty server 服务器
        new Thread(() -> {
            try {
                start();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

    }
}
