spring中使用netty-socketio部署到服务器(SSL、nginx转发)

spring中使用netty-socketio部署到服务器(SSL、nginx转发)

本文实现前端socket.io-client连接后端netty-socketio,并且部署到服务器上的示例,以及说明一些实现过程中可能遇到的错误。

socketio默认基于的路径是/socket.io

传输方式有三种分别是:

1、polling

polling是最传统的基于http的方式、兼容性强,定期向服务端循环是否还保持链接,这种方式也是websocket无法使用时的降级选择。

2、websocket

全双工通信、维持客户端和服务端之间的持久连接,它比轮询更有效,因为它消除了HTTP请求/响应周期。浏览器基本上都支持。

3、webtransport

基于HTTP/3和QUIC的较新的传输协议、提供低延迟、双向、多路通信、支持可靠(类似tcp)和不可靠(类似udp)的数据传输、专为高性能应用程序,如游戏和视频流、对比WebSockets有更好的拥塞控制和连接迁移,但它还没有像WebSockets那样被广泛支持。

如果你选择polling模式,可以使用extraHeaders,因为它基于http,但是使用websocket模式时,extraHeaders无效,但是也有替换方案,websocket可以把参数传到url里面中。

坑① :用polling模式本地连接正常,但是部署到服务器出现PollingTransport:167 - <sessionId> is not registered. Closing connection,一致没搞懂为什么。

由于坑①,前端指定使用websocket模式连接,假设后端netty-socketio开放9000端口,首先编写一个socket实例

js 复制代码
const socket = *ref*<Socket | null>(null);
socket.value = io("http://127.0.0.1:9000",
  {
    path: "/socket.io",// 默认是socket.io
    withCredentials: true,// 允许跨域
    transports: ["websocket"],// 指定传输模式
  }
);

path路径默认是socket.io,当然可以后端通过配置修改

后端基于spring编写一个bean

java 复制代码
@Bean
public SocketIOServer socketIOServer() {
    Configuration config = new Configuration();
    config.setPort(9000);
    config.setHostname("0.0.0.0");
    config.setJsonSupport(new JacksonJsonSupport());
    config.setExceptionListener(new DefaultExceptionListener());
    config.setHttpCompression(false);
    config.setWebsocketCompression(false);
    config.setKeyStore(getCertJks());// 如果你有证书这样配置
    config.setKeyStorePassword("8di1g1a8");// 证书密码,没有可以删除这两行
    return new SocketIOServer(config);
}

坑②:如果你使用的是polling模式,传了请求头,那么后端必须在配置中允许该请求头,否则接收不到

复制代码
config.setAllowHeaders(tokenName);

再写个启动服务类

java 复制代码
@Component
@Slf4j
public class SocketIOEventListener {

    private final SocketIOServer socketIOServer;
    private final WebSocketService webSocketService;

    @Autowired
    public SocketIOEventListener(SocketIOServer socketIOServer, WebSocketService webSocketService) {
        this.socketIOServer = socketIOServer;
        this.webSocketService = webSocketService;
    }

    @PostConstruct
    private void init() {
        socketIOServer.addConnectListener(client -> {
            webSocketService.connect(client);
        });
        socketIOServer.addDisconnectListener(client -> {
            webSocketService.disconnect(client);
        });
        socketIOServer.start();
    }

    @PreDestroy
    private void destroy() {
        // 关闭服务器
        socketIOServer.stop();
    }
}

WebSocketService.class如下

java 复制代码
@Service
@Slf4j
@RequiredArgsConstructor
public class WebSocketService {
    public void connect(SocketIOClient client) {
        //....连接成功
    }
    public void disconnect(SocketIOClient client) {
        //....断开成功
    }
}

前端本地测试成功连通,之后尝试部署服务器,我有一个域名xxx.cn服务器,其中nginx在这个服务器上,我的后端服务在另一个服务器上,我需要使用xxx.cn/socket把服务转发到另一个后端服务器上去,于是编写nginx配置

sh 复制代码
location /socket{
        proxy_pass http://120.11.111.11:9000;# 随便编的IP
        rewrite "^/socket/(.*)$" /$1 break;
}

因为要通过socket转发,前端socket我这样写

js 复制代码
socket.value = io("https://www.guetzjb.cn/socket",
  {
    path: "/socket/socket.io",
    withCredentials: true,
    transports: ["websocket"],
  }
);

看起来很合理,结果现实马上就能给你当头一棒

后端一连接上就立马断开了,秒断

js 复制代码
5247fb7d-804b-403f-8ca7-84d876fb017c连接成功,当前在线人数1
5247fb7d-804b-403f-8ca7-84d876fb017c断开成功,当前在线人数0

前端socket数据是这样的

js 复制代码
0{"sid":"5247fb7d-804b-403f-8ca7-84d876fb017c","upgrades":[],"pingInterval":25000,"pingTimeout":60000}
40/socket
44/draw-socket,"Invalid namespace"

收集到关键词Invalid namespace,经过服务器得靠socket转发,这是识别成namespace了?

正确姿势👇

修改启动类新增namespace

java 复制代码
@Component
@Slf4j
public class SocketIOEventListener {
    //........
    @PostConstruct
    private void init() {
        //其他代码
        SocketIONamespace ns = socketIOServer.addNamespace("/socket");// 必须斜杠开头
        ns.addConnectListener(client -> {
		    // 之后的监听器需要网该命名空间上加
		});
        //其他代码
    }
    //.........
}

注意新增命名空间必须是 / 开头,否则无效,默认明明空间就是/,但是加入你这样做

java 复制代码
SocketIONamespace ns = socketIOServer.addNamespace("/");
ns.addConnectListener(client -> {
    // 无效做法,连不上
});

坑③ :上面这也连不上,默认命名空间必须使用socketIOServer.addConnectListener

当然,使用/socket就可以正常连接了

此时本地代码和线上代码都使用一个namespace就可以了

js 复制代码
socket.value = io("http://127.0.0.1:9000/socket",//使用socket namespace
  {
    path: "/socket.io",
    withCredentials: true,
    transports: ["websocket"],
  }
);

部署到线上需要通过socket转发

所以线上的path需要这样

js 复制代码
socket.value = io("http://127.0.0.1:9000/socket",//使用socket namespace
  {
    path: "/socket/socket.io",
    withCredentials: true,
    transports: ["websocket"],
  }
);

配置上nginx

sh 复制代码
location /socket/socket.io{
        proxy_pass http://120.11.111.11:9000;
        rewrite "^/socket/(.*)$" /$1 break;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
}

socket.io需要升级为长链接,原先的/socket正常转发

再次部署

本地和线上都跑起来啦!

测试是不用设置setKeyStore的,当然你也可以设置上👇

java 复制代码
@Bean
public SocketIOServer socketIOServer() {
    Configuration config = new Configuration();
    // 其他配置
//        config.setKeyStore(getCertJks());
//        config.setKeyStorePassword("zfs2k32p");
    return new SocketIOServer(config);
}

private InputStream getCertJks() {
    ClassPathResource classPathResource = new ClassPathResource("xxxx.cn.jks");
    return classPathResource.getStream();
}
相关推荐
hqxstudying14 分钟前
java依赖注入方法
java·spring·log4j·ioc·依赖
春生野草41 分钟前
关于SpringMVC的整理
spring
Bug退退退1231 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠1 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
hello早上好3 小时前
CGLIB代理核心原理
java·spring
ddfa12343 小时前
XML 笔记
xml·服务器
海外空间恒创科技3 小时前
一台香港原生ip站群服务器多少钱?
服务器·网络协议·tcp/ip
Charlene Fung3 小时前
vs code远程自动登录服务器,无需手动输入密码的终极方案(windows版)
运维·服务器·vscode·ssh
碣石潇湘无限路3 小时前
【部署与总结】从本地运行到公网服务器的全过程
运维·服务器
linux修理工4 小时前
ipmitool 使用简介(ipmitool sel list & ipmitool sensor list)
运维·服务器