Voice中对netty的处理

本文介绍了一个基于Spring框架的WebSocket配置示例,展示了如何配置Netty服务器、处理HTTP和WebSocket请求,以及如何管理和销毁WebSocket会话。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

@Configuration
public class WebSocketConfiguration {
    @Autowired
    private NettyServerProperties properties;

    @Autowired
    private ConfigConfiguration config;

    /**
     * 启动netty服务 在生成bean的时候调用该bean的start方法
     * @return
     */
    @Bean(initMethod = "start", destroyMethod = "stop")
    @ConditionalOnMissingBean  //用来检查容器中是否有该bean对象,如果有就不进行操作没有就初始化创建一个新的
    @ConditionalOnProperty(prefix = "netty.server", value = "enabled", havingValue = "true")
    NettyServer nettyServer() {
        return new NettyServer(properties);
    }

    /**
     * 后续的HTTP 访问完全依靠这个类生成 AsyncHttpClient 对象
     *
     * @param
     * @return
     */
    @Bean(destroyMethod = "close")
    public AsyncHttpClient asyncHttpClient(AsyncHttpClientConfig config) {
        return new DefaultAsyncHttpClient(config);
    }

    @Bean
    public AsyncHttpClientConfig clientConfig() {
        AsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
                .setWebSocketMaxFrameSize(properties.getMaxSize())
                .build();
        return config;
    }

    /**
     * 工作线程池
     * @return
     */
    @Bean(destroyMethod = "shutdown")
    public ExecutorService executorService() {
        return new TraceExecutorService(new ThreadPoolExecutor(config.getCorePoolSize(),
                config.getMaxPoolSize(),
                config.getIdleTime(),
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<Runnable>(config.getBlockingDequeSize()),
                new ThreadFactory() {
                    int threadId = 1;
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t = new Thread(new MDCContinueRunableDecorator(r));
                        t.setName("worker thread " + (threadId++));
                        t.setDaemon(true);

                        if (threadId > 100000)
                            threadId = 1;
                        return t;
                    }
                }));
    }

 
    //Netty服务配置
    public class NettyServer implements ApplicationContextAware {
    private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
    private NettyServerProperties properties;
    private Channel serverChannel;
    private ApplicationContext applicationContext;

    @Autowired
    private ConfigConfiguration config;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public NettyServer(NettyServerProperties properties) {
        this.properties = properties;
    }

    public void start() {
        //需要开启一个新的线程来执行netty server 服务器
        Thread t = new Thread(() -> startServer());
        //t.setDaemon(true);
        t.setName("netty thread");
        t.start();
    }

    public void stop() {
        if (serverChannel != null) {
            try {
                serverChannel.close().get(2L, TimeUnit.SECONDS);
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                logger.error("Interrupt connection abnormality... {}", e);
                throw new InternalSystemException(Constants.BOT_VOICE_CORE, "Interrupt connection abnormality...", e);
            }
            serverChannel = null;
        }
    }

    private void startServer() {
        //服务端需要2个线程组  boss处理客户端连接  work进行服务端连接之后的处理
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup work = new NioEventLoopGroup(config.getNettyWorkThreads());
        try {

            ServerBootstrap bootstrap = new ServerBootstrap();
            //服务器 配置
            bootstrap.group(boss,work).channel(NioServerSocketChannel.class)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    // HttpServerCodec:将请求和应答消息解码为HTTP消息
                    socketChannel.pipeline().addLast("http-codec",new HttpServerCodec());
                    // HttpObjectAggregator:将HTTP消息的多个部分合成一条完整的HTTP消息
                    socketChannel.pipeline().addLast("aggregator",new HttpObjectAggregator(properties.getMaxSize()));
                    // ChunkedWriteHandler:向客户端发送HTML5文件
                    socketChannel.pipeline().addLast("http-chunked",new ChunkedWriteHandler());
                    // 进行设置心跳检测
                    socketChannel.pipeline().addLast(new IdleStateHandler(properties.getIdleTime(),
                            properties.getIdleTime(), properties.getIdleTime()*10, TimeUnit.SECONDS));
                    // 配置通道处理  来进行业务处理增加ChannelPipeling
                    socketChannel.pipeline().addLast(applicationContext.getBean(WebSocketServerHandler.class));
                }

  //ChannelOption.SO_BACKLOG, 1024

       BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的 请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。

            }).option(ChannelOption.SO_BACKLOG,1024).childOption(ChannelOption.SO_KEEPALIVE,true);
            //绑定端口  开启事件驱动
            logger.info("【服务器启动成功========端口:" + properties.getPort() + "】");
            serverChannel = bootstrap.bind(properties.getPort()).sync().channel();
            serverChannel.closeFuture().sync();
        } catch (Exception e) {
            logger.error("Running voice server exception...{}", e);
            throw new InternalSystemException(Constants.BOT_VOICE_CORE, "Running voice server exception...", e);
        } finally {
            //关闭资源
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }
    }
}

 

在voice中通过websocke的入口将ChannelHandlyContext以及HttpFullRequest等放入线程局部变量中然后利用@ApplicationContextWarezhuru
  @Component
@Scope("prototype") // 必须是prototype!!! 实现SimpleChannelInboundHandler<Object>的channelRead0(ChannelHandlerContext ctx, Object msg)是netty的入口先在newWebSocketSession中将连接的ChannelHandlerContext以及
newWebSocketSession以及handShark存入线程局部变量中
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> implements ApplicationContextAware {
    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServerHandler.class);
    private ApplicationContext applicationContext;

    /**
     * 我们的 handler 是 prototype的,且处理的是websocket 因此不会有多个ChannelId
     * 废除原来的map, 直接用 handler 保留当前的handler, 避免浪费内存
     */
    // private Map<ChannelId, AbstractWebSocketSessionHandler> handlers = new ConcurrentHashMap<>();
    AbstractWebSocketSessionHandler sessionHandler;

    @Autowired
    NettyServerProperties properties;

    @Autowired
    private ConfigConfiguration configConfig;

    private WebSocketRequestAttributes attributes;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof FullHttpRequest) {
            // 处理http请求
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            // 处理websocket请求
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        try {
            // Handle a bad request.
            boolean iswebSocket = Constants.WEBSOCKET.equals(req.headers().get("Upgrade"));
            if (!req.decoderResult().isSuccess() || !iswebSocket) {
                sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
                return;
            }
            String uri = req.uri();

            String queryArgs = URI.create(uri).getQuery();
            if (!StringUtils.isEmpty(queryArgs)) {
                List<NameValuePair> params = URLEncodedUtils.parse(queryArgs, Charset.forName("utf-8"));
                NameValuePair imeiPair = params.stream()
                        .filter(param -> Objects.equals(param.getName(), "id")).findFirst().orElse(null);
                String imei = Optional.ofNullable(imeiPair).map(NameValuePair::getValue).orElse(null);
                MDC.put("imei", imei);
            }
            LOGGER.info("The uri form client is {}", uri);
            if (iswebSocket) {

//构造握手响应返回

// WebSocketServerHandshakerFactory ws = new WebSocketServerHandshakerFactory("", null, false);

 // handshaker = ws.newHandshaker(request); 

                WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(uri, null, true, properties.getMaxSize());
                WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);
                if (handshaker == null) {
                    WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
                } else {
                    req.setUri(getShortUri(uri) + "?" + uri.substring(uri.indexOf("?") + 1, uri.length()));
                    ChannelFuture channelFuture = handshaker.handshake(ctx.channel(), req);
                    channelFuture.addListener(f -> {
                        // 握手成功之后,业务逻辑
                        if (f.isSuccess()) {
                            newWebSocketSession(ctx, req, handshaker);
                        } else {
                            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
                        }
                    });
                    try {
                        channelFuture.await(0);
                    } catch (InterruptedException e) {
                        LOGGER.error("Connetioning to voice error {}",e);
                        throw new InternalSystemException(Constants.BOT_VOICE_CORE,"Connetioning to voice error ",e);
                    }
                }
            } else {
                processHttp(ctx, req);
            }
        } finally {
            MDC.clear();
        }
    }

    private void newWebSocketSession(ChannelHandlerContext ctx, FullHttpRequest req,
                                     WebSocketServerHandshaker handshaker) {
        URI uri = null;
        try {
            uri = new URI(req.uri());
        } catch (URISyntaxException e) {
            LOGGER.error("新建一个webscket session 连接失败 {}", e);
            throw new InternalSystemException(Constants.BOT_VOICE_CORE, "新建一个webscket session 连接", e);
        }

        //准备 websocket scope 的容器
        attributes = buildRequestAttributes(ctx, req, handshaker);
        WebSocketContextHolder.setRequestAttributes(attributes);

        try {
            LOGGER.info("The uri path is {}", uri.getPath());
            //现在可以直接创建 bean, 无需复杂的手工注入逻辑
            if (uri.getPath().startsWith(configConfig.getVoiceURI())) {
            //先将channel信息存入线程局部变量之后然后利用Websocket生命周期在容器中生成一个对象,在生命周期中是在线程局部变量中取然后从工厂生产,然后在VoiceSessionHandler中利用@AutoWird注解生成对象将连接的Channel等信息实例化到对象中
            每个连接都有一个对象,先把所有用到Channel的对象初始化返回数据的时候调用。
                sessionHandler = applicationContext.getBean(VoiceSessionHandler.class);
            } else {
                sessionHandler = applicationContext.getBean(DefaultSessionHandler.class);
            }
        } finally {
            //清除 websocket scope 的容器  初始化所有对象完成时清除线程局部变量中的内容
            WebSocketContextHolder.clearRequestAttributes();
        }

    }

    private WebSocketRequestAttributes buildRequestAttributes(ChannelHandlerContext ctx, FullHttpRequest httpRequest, WebSocketServerHandshaker handshaker) {
        return new WebSocketRequestAttributes(ctx, httpRequest, handshaker);
    }

    private void processHttp(ChannelHandlerContext ctx, FullHttpRequest req) {
        // 等待處理
        sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND));
    }

    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        if (frame instanceof CloseWebSocketFrame) {
            if (sessionHandler != null) {
                sessionHandler.closeSession((CloseWebSocketFrame) frame);
            }
            return;
        }
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
        } else {
            if (sessionHandler != null) {
                sessionHandler.processFrame(frame);
            } else {
                //非法的
                ctx.channel().writeAndFlush(new TextWebSocketFrame("{}"));
            }
        }

    }

    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
        if (res.status().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
            HttpUtil.setContentLength(res, res.content().readableBytes());
        }
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        sessionHandler = null;
        if(attributes != null) {
            LOGGER.info("客户端清除,handlerRemoved function runn,attributes is not null.");
            WebSocketContextHolder.clean(attributes);
            attributes = null;
        }
  
  定义一个webSocket的注解类似于Scope(“protype”)生成方式,但是这个是先从WebSocketContextHolder.currentRequestAttributes()的线程局部变量中取出来该线程的bean如果没有时由objectFactory工厂生成并且存入线程局部变量
  
  public class WebSocketScope implements Scope {

    public WebSocketScope() {

    }

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        //获取当前websocket 会话的 RequestAttributes, 即beans 的容器
        WebSocketRequestAttributes attributes = WebSocketContextHolder.currentRequestAttributes();
        if(attributes == null) { //如果线程局部变量中没有则抛出一个异常
            throw new IllegalStateException("No WebSocketRequestAttributes found: ");
        }
        Object scopedObject = attributes.getAttribute(name);
        if (scopedObject == null) {
            //如果这个对象还不存在,则通过objectFactory生成一个,然后存入RequestAttributes 中
            scopedObject = objectFactory.getObject();
            //如果bean 实现了 ScopedLifeCircle, 调用 initScope 实现init
            if(scopedObject instanceof ScopedLifeCircle) {
                ScopedLifeCircle circle = (ScopedLifeCircle)scopedObject;
                circle.initScope();
            }
            attributes.setAttribute(name, scopedObject);
        }
        return scopedObject;
    }

    @Override
    public Object remove(String name) {
        WebSocketRequestAttributes attributes = WebSocketContextHolder.currentRequestAttributes();
        return attributes == null ? null : attributes.remove(name);
    }

    @Override
    public void registerDestructionCallback(String s, Runnable runnable) {

    }

    @Override
    public Object resolveContextualObject(String s) {
        return null;
    }

    @Override
    @Nullable
    public String getConversationId() {
        return null;
    }
}


定义WebSocketRequestAttributes 管理WebSocket回话的ChannelHandlerContext等beans存储这里面依赖的一些bean对象 websocket的范围相当于Prototype多例但是如果A依赖B,B依赖C,C依赖A的话容器中就有两个A为了避免这种情况
将对象存入bean中管理起来保证始终只有一个对象
public class WebSocketRequestAttributes  {
    private ChannelHandlerContext ctx;
    private WebSocketServerHandshaker handshaker;
    private FullHttpRequest httpRequest;

    private Map<String, Object> beans;

    public WebSocketRequestAttributes(ChannelHandlerContext ctx, FullHttpRequest httpRequest, WebSocketServerHandshaker handshaker) {
        this.ctx = ctx;
        this.httpRequest = httpRequest.copy();
        this.handshaker = handshaker;
        this.beans = new LinkedHashMap<>();
    }

    public Object getAttribute(String name) {
        return beans.get(name);
    }

    public void setAttribute(String name, Object scopedObject) {
        Object old = beans.put(name, scopedObject) ;
        if(old != null && old instanceof ScopedLifeCircle) {
            ((ScopedLifeCircle)old).closeScope();
        }
    }

    public ChannelHandlerContext getCtx() {
        return ctx;
    }

    public WebSocketServerHandshaker getHandshaker() {
        return handshaker;
    }

    public FullHttpRequest getHttpRequest() {
        return httpRequest;
    }

    public void clearAll() {
        this.ctx = null;
        this.handshaker = null;
        if (this.httpRequest != null) {
            this.httpRequest.release();
            this.httpRequest = null;
        }

        if(beans != null) {
            beans.forEach((k, v) -> {
                //如果bean 实现了 ScopedLifeCircle, 调用 closeScope 实现清除资源
                if (v instanceof ScopedLifeCircle) {
                    ((ScopedLifeCircle) v).closeScope();
                }
            });
            beans.clear();
            beans = null;
        }
    }

    public Object remove(String name) {
        return beans.remove(name);
    }
}

将自定义的实现了Scope的生命周期对象注入Spring工厂中
**
 * 本类 加入 spring, 目标是注册一个 名称为 websocket 的 scope
 */
@Component
public class WebSocketRegister implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
        factory.registerScope("websocket", new WebSocketScope());
    }
}

@Component
@Scope("websocket") // 必须是websocket!!!  只有是webSocket时才能按照spring对象管理的方式生成。按照实现Scope接口的get获取对象
@Slf4j
public class VoiceSessionHandler extends AbstractWebSocketSessionHandler /*implements InitializingBean*/{

    /**
     * 从websocket 读取输入的服务
     */
    @Autowired
    private InteractInputService interactInputService;

    /**
     * 输出到 websocket 的服务
     */
    @Autowired
    private InteractOutputService interactOutputService;


    /**
     * 根据请求url取出后面的请求参数部分内容
     * @return
     */
    private String getQueryString() {
        String queryString = getRequestUri();
        int index = queryString.indexOf("?");
        if (index != -1) {
            queryString = queryString.substring(index + 1);
        } else {
            queryString = "";
        }
        return queryString;
    }

    @Override
    public void initScope() {
        super.initScope();
        //我们使用@Scope("websocket"), 依赖的对象已经可以自动注入了
        //不再需要复杂的手工注入逻辑了。
        //主要注意的是,现在那些 依赖的 bean不能再有循环引用了!!!!

        //握手
        String queryString = getQueryString();
        if (!interactInputService.processHandshake(queryString)) {
            log.error("Authority authentication failed...{}", queryString);
            close();
        }
        interactOutputService.processHandshake(queryString);
    }

    /**
     * 处理 websocket 每帧数据
     * @param frame
     */
    @Override
    public void processFrame(WebSocketFrame frame) {
        if (frame instanceof TextWebSocketFrame) {
            String text = ((TextWebSocketFrame) frame).text();
            //处理文本信息
            interactInputService.processTextMessage(text);
        } else if (frame instanceof BinaryWebSocketFrame) {
            ByteBuf buf = frame.content();
            byte[] content = new byte[buf.capacity()];
            buf.readBytes(content);
            //处理二进制信息
            interactInputService.processBinaryMessage(content);
        } else {
            log.error("客户端上传的不是文本信息,也不是二进制信息...");
        }
    }

    @Override
    public void clear() {
        super.clear();
        if(interactInputService != null) {
            interactInputService.onClose();
            interactInputService = null;
        }
        if(interactOutputService != null) {
            interactOutputService.onClose();
            interactOutputService = null;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值