springCloudGateway+Nacos注册与转发Netty+WebSocket

功能介绍:使用nacos的网关来转发服务

使用场景:例如,在安卓壳中只有后端网关接口有做映射可以正常访问,其余地址都无法访问,所以minio的预览,消息模块websocket都需通过网关来转发。

1-minio通过后端接口调用,然后返回二进制来实现预览

2-websocket是通过将服务注册到nacos中,走网关转发来实现

pom配置

java 复制代码
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

nacos配置

java 复制代码
#v2.3.4_1
im:
  tcpsocket:
    port: 31111
  websocket:
    port: 31112
  application:
    # Netty应用名称
    name: mall-im-netty

3-java代码如下

开头配置

java 复制代码
    /**
     * Netty应用名称
     */
    @Value("${im.application.name}")
    private String nettyName;
    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;
    @Value("${spring.cloud.nacos.config.username}")
    private String nacosUsername;
    @Value("${spring.cloud.nacos.config.password}")
    private String nacosPassword;
    private Channel channel;

服务注册核心代码

java 复制代码
  /**
     * 将Netty服务注册进Nacos
     *
     * @param nettyName 服务名称
     * @param nettyPort 服务端口号
     */
    private void registerNamingService(String nettyName, Integer nettyPort) {
        try {
            Properties properties = new Properties();
            properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
            properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDiscoveryProperties.getNamespace());
            properties.setProperty(PropertyKeyConst.USERNAME, nacosUsername);
            properties.setProperty(PropertyKeyConst.PASSWORD, nacosPassword);
            NamingService namingService = NamingFactory.createNamingService(properties);
            InetAddress address = InetAddress.getLocalHost();
            namingService.registerInstance(nettyName, nacosDiscoveryProperties.getGroup(), address.getHostAddress(), nettyPort);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

完整的websorcet注册到nacos的代码如下

java 复制代码
package com.fjean.modules.imserver.netty.ws;
 
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.naming.NamingFactory;
import com.alibaba.nacos.api.naming.NamingService;
import com.fjean.modules.imserver.netty.IMChannelHandler;
import com.fjean.modules.imserver.netty.IMServer;
import com.fjean.modules.imserver.netty.ws.endecode.MessageProtocolDecoder;
import com.fjean.modules.imserver.netty.ws.endecode.MessageProtocolEncoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
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.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
 
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.net.ssl.SSLException;
import java.net.InetAddress;
import java.security.cert.CertificateException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
 
/**
 * WS服务器,用于连接网页的客户端,协议格式: 直接IMSendInfo的JSON序列化
 *
 * @author Blue
 * @date 2022-11-20
 */
@Slf4j
@Component
@ConditionalOnProperty(prefix = "websocket", value = "enable", havingValue = "true", matchIfMissing = true)
public class WebSocketServer implements IMServer {
 
    @Value("${im.websocket.port:8878}")
    private int port;
    /**
     * Netty应用名称
     */
    @Value("${im.application.name}")
    private String nettyName;
 
    @Value("${spring.cloud.nacos.config.username}")
    private String nacosUsername;
    @Value("${spring.cloud.nacos.config.password}")
    private String nacosPassword;
    private volatile boolean ready = false;
 
    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;
 
    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;
 
    private Channel channel;
 
 
    @Override
    public boolean isReady() {
        return ready;
    }
 
    @Async
    @Override
    public void start() {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bossGroup = new NioEventLoopGroup();
        workGroup = new NioEventLoopGroup();
        // 设置为主从线程模型
        bootstrap.group(bossGroup, workGroup)
                // 设置服务端NIO通信类型
                .channel(NioServerSocketChannel.class)
                // 使用本地地址,绑定端口号
                .localAddress(port)
                // 设置ChannelPipeline,也就是业务职责链,由处理的Handler串联而成,由从线程池处理
                .childHandler(new ChannelInitializer<Channel>() {
                    // 添加处理的Handler,通常包括消息编解码、业务处理,也可以是日志、权限、过滤等
                    @Override
                    protected void initChannel(Channel ch) throws CertificateException, SSLException {
                        // 获取职责链
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
                        pipeline.addLast("http-codec", new HttpServerCodec());
                        pipeline.addLast("aggregator", new HttpObjectAggregator(65535));
                        pipeline.addLast("http-chunked", new ChunkedWriteHandler());
                        pipeline.addLast(new WebSocketServerProtocolHandler("/im"));
                        pipeline.addLast("encode", new MessageProtocolEncoder());
                        pipeline.addLast("decode", new MessageProtocolDecoder());
                        pipeline.addLast("handler", new IMChannelHandler());
                    }
                })
                // bootstrap 还可以设置TCP参数,根据需要可以分别设置主线程池和从线程池参数,来优化服务端性能。
                // 其中主线程池使用option方法来设置,从线程池使用childOption方法设置。
                // backlog表示主线程池中在套接口排队的最大数量,队列由未连接队列(三次握手未完成的)和已连接队列
                .option(ChannelOption.SO_BACKLOG, 5)
                // 表示连接保活,相当于心跳机制,默认为7200s
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        // 把netty注册到nacos服务中
        registerNamingService(nettyName, port);
 
        try {
            // 绑定端口,启动select线程,轮询监听channel事件,监听到事件之后就会交给从线程池处理
            channel = bootstrap.bind(port).sync().channel();
            // 就绪标志
            this.ready = true;
            log.info("websocket server 初始化完成,端口:{}", port);
            // 等待服务端口关闭
            //channel.closeFuture().sync();
        } catch (InterruptedException e) {
            log.info("websocket server 初始化异常", e);
        }
    }
 
    @PreDestroy
    @Override
    public void stop() {
        if (bossGroup != null && !bossGroup.isShuttingDown() && !bossGroup.isShutdown()) {
            bossGroup.shutdownGracefully();
        }
        if (workGroup != null && !workGroup.isShuttingDown() && !workGroup.isShutdown()) {
            workGroup.shutdownGracefully();
        }
        this.ready = false;
        if (channel != null) {
            channel.close();
        }
        log.info("websocket server 停止");
    }
 
    /**
     * 将Netty服务注册进Nacos
     *
     * @param nettyName 服务名称
     * @param nettyPort 服务端口号
     */
    private void registerNamingService(String nettyName, Integer nettyPort) {
        try {
            Properties properties = new Properties();
            properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
            properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDiscoveryProperties.getNamespace());
            properties.setProperty(PropertyKeyConst.USERNAME, nacosUsername);
            properties.setProperty(PropertyKeyConst.PASSWORD, nacosPassword);
            NamingService namingService = NamingFactory.createNamingService(properties);
            InetAddress address = InetAddress.getLocalHost();
            namingService.registerInstance(nettyName, nacosDiscoveryProperties.getGroup(), address.getHostAddress(), nettyPort);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
 
}

需要维护的sql如下:

java 复制代码
-- 更新内容
-- 1.新增im的网关路由名为:eqds-im
-- 2.通过网关转发netty,实现零信任平台的websocket连接
-- 新增网关路由,备注:网关路由从数据库新增,需要登录管理员账号-在后端-运维中心-运行监控-路由网关,找到路由名为eqds-im,然后重新保存
-- 注意,一定要eqds-im一定要点击编辑,然后重新保存,才能生效!!!
INSERT INTO `sys_gateway_route` (`id`, `router_id`, `name`, `uri`, `predicates`, `filters`, `retryable`, `strip_prefix`, `persistable`, `show_api`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `sys_org_code`, `module_name`) VALUES ('1899728591772602370', 'eqds-im', 'eqds-im', 'lb:ws://mall-im-netty', '[{\"args\":[\"/im/**\"],\"name\":\"Path\"}]', '[]', NULL, 1, NULL, NULL, 1, 'owner', '2025-03-12 15:46:06', NULL, NULL, NULL, NULL);

参考网站:springCloudGateway+Nacos注册与转发Netty+WebSocket_netty注册到nacos-CSDN博客

相关推荐
小北方城市网28 分钟前
SpringBoot 集成 MinIO 实战(对象存储):实现高效文件管理
java·spring boot·redis·分布式·后端·python·缓存
曹轲恒1 小时前
SpringBoot配置文件(1)
java·spring boot·后端
码农水水1 小时前
得物Java面试被问:大规模数据的分布式排序和聚合
java·开发语言·spring boot·分布式·面试·php·wpf
Chan161 小时前
【 微服务SpringCloud | 模块拆分 】
java·数据结构·spring boot·微服务·云原生·架构·intellij-idea
guslegend1 小时前
SpringBoot 全局异常处理
spring boot
SenChien1 小时前
Java大模型应用开发day06-天机ai-学习笔记
java·spring boot·笔记·学习·大模型应用开发·springai
小北方城市网1 小时前
SpringBoot 安全认证实战(Spring Security + JWT):打造无状态安全接口体系
数据库·spring boot·后端·安全·spring·mybatis·restful
这儿有个昵称2 小时前
Java面试场景:从音视频到微服务的技术深挖
java·spring boot·spring cloud·微服务·面试·kafka·音视频
曹轲恒2 小时前
SpringBoot的热部署
java·spring boot·后端
Mcband3 小时前
Spring Boot 整合 ShedLock 处理定时任务重复执行的问题
java·spring boot·后端