socket.io官网地址

案例

前端代码(socket.io.js)

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Document</title><style>input {background-color: #fff;background-image: none;border-radius: 4px;border: 1px solid #bfcbd9;box-sizing: border-box;color: #1f2d3d;font-size: inherit;height: 40px;line-height: 1;outline: 0;padding: 3px 10px;}.el-button--primary {color: #fff;background-color: #20a0ff;border-color: #20a0ff;}.el-button {display: inline-block;line-height: 1;white-space: nowrap;cursor: pointer;background: #00aac5;border: 1px solid #c4c4c4;color: #fff;margin: 0;padding: 10px 15px;border-radius: 4px;outline: 0;text-align: center;}</style>
</head>
<body><div><div id="content"></div></div><div><input type="text" id="userId" value="1"/><input type="text" id="input"><button class="el-button el-button--primary el-button--large" type="button" onclick="connect()"><span>建立连接</span></button></div><script src="./socket.io.js"></script><script>var socket = null;// 建立连接function connect() {let userId = document.getElementById("userId").value;socket = io.connect('http://localhost:8088?accessToken=xxxxxx', {path: '/socket.io'});socket.emit('setId', userId);// 监听 message 会话socket.on('message', function (data) {let html = document.createElement('p')html.innerHTML = '系统消息:<span>'+ data +'</span>'document.getElementById('content').appendChild(html)console.log(data);});}</script>
</body>
</html>

前端通过后端提供的socket地址,进行授权连接,连接成功后发送用户ID

后端代码(Netty SocketIO Server)

maven依赖

<dependency><groupId>com.corundumstudio.socketio</groupId><artifactId>netty-socketio</artifactId><version>1.7.7</version>
</dependency>

SocketIO 服务

@Bean
public SocketIOServer socketIOServer() {SocketConfig socketConfig = new SocketConfig();socketConfig.setTcpNoDelay(true);socketConfig.setSoLinger(0);com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();config.setSocketConfig(socketConfig);BeanUtils.copyProperties(socketIOProperties, config);// 连接鉴权config.setAuthorizationListener(socketIOAuthorizationListener);return new SocketIOServer(config);
}

socketIOProperties设置SocketIOServer相关配置,例如:


# netty-socketio 配置
socketio:host: 0.0.0.0port: 8088# 设置最大每帧处理数据的长度,防止他人利用大数据来攻击服务器maxFramePayloadLength: 1048576# 设置http交互最大内容长度maxHttpContentLength: 1048576# socket连接数大小(如只监听一个端口boss线程组为1即可)bossCount: 1workCount: 100allowCustomRequests: true# 协议升级超时时间(毫秒),默认10秒。HTTP握手升级为ws协议超时时间upgradeTimeout: 1000000# Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件pingTimeout: 6000000# Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔pingInterval: 25000

socketIOAuthorizationListener对连接进行鉴权:

@Slf4j
@Component
public class SocketIOAuthorizationListener implements AuthorizationListener {@Autowiredprivate ServiceRedis serviceRedis;@Overridepublic boolean isAuthorized(HandshakeData handshakeData) {log.debug("SocketIO鉴权:{}", new Gson().toJson(handshakeData.getUrlParams()));String accessToken = handshakeData.getSingleUrlParam("accessToken");if (StrUtil.isEmpty(accessToken)) {return false;}try {// 鉴权return isSuccess;} catch (Exception e) {log.error("SocketIO鉴权发生异常:{}", accessToken, e);}return false;}
}

SocketIO服务类

@Service
public class SocketIORunner implements CommandLineRunner {/*** 存储已连接的客户端session*/public static Map<Long, SocketIOClient> clientMap = new ConcurrentHashMap<>();@Autowiredprivate SocketIOServer socketIOServer;@Autowiredprivate ServiceRedis serviceRedis;@Autowiredprivate MsgCenterProperties msgCenterProperties;@Overridepublic void run(String... args) throws Exception {log.info("启动web端socket服务器开始.......");socketIOServer.start();log.info("启动web端socket服务器完成.......");}/*** 添加connect事件** @param client*/@OnConnectpublic void onConnect(SocketIOClient client) {log.debug("未知用户" + client.getHandshakeData().getAddress().toString() + "连接到服务器" + DateUtils.format(new Date(), DateUtils.YYYY_MM_DD_HH_mm_SS));}/*** 添加@OnDisconnect事件,客户端断开连接时,刷新客户端信息** @param client*/@OnDisconnectpublic void onDisconnect(SocketIOClient client) {Long userId = getUserId(clientMap, client);if (userId != null) {log.debug("用户userId:{}断开服务器连接", userId);// 删除用户信息}}/*** 当客户端发起事件传递userId,存储session** @param client* @param request* @param data*/@OnEvent(value = "setId")public void getUserId(SocketIOClient client, AckRequest request, String data) {String userId = data;if (userId != null) {log.debug("用户userId:{}连接到服务器", userId);// 保存用户信息}}
}

在本地,这一切都看起来非常的顺利,但是部署到正式服,遇到如下两个问题。

问题

域名转发

正式服使用的是域名,且通过k8s进行服务部署,使用nginx官方的nginx-ingress-controller进行http转发。因为使用socket.io,默认的路径是/socket.io,所以针对这个路径进行转发配置,但这里存在一个问题,因为该域名已经用于服务,且/路径已经配置了转发规则且加了一个跨域头,那么此时配置/socket.io的转发规则,因为SocketIOServer会返回跨域头Access-Control-Allow-Origin,导致重复的头问题

尝试解决方案

想着能不能通过nginx.org/location-snippets中判断$request_uri来进行设置不同的头,发现这是不被允许的。

最终方案

新创建一个新的域名用于socket.io的转发,这样子只需要配置/路径转发就行,同时设置websocket升级请求头

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

多服务转发

socket.io使用polling:HTTP 长轮询传输在 Socket.IO 会话的生命周期内发送多个 HTTP 请求
如果这些Http请求被转发到不同的服务中,因为一个服务不具备其它服务中所建立的连接信息,所以此时消息发送会发生错误,

只使用websocket传输协议

const socket = io("https://io.yourhost.com", {// WARNING: in that case, there is no fallback to long-pollingtransports: [ "websocket" ] // or [ "websocket", "polling" ] (the order matters)
});

要想实现粘性会话,有如下两种解决方案:
(1)基于 cookie 路由客户端(推荐解决方案)
(2)根据客户端的原始地址路由客户端

您将在下面找到一些常见负载平衡解决方案的示例:
NginX(基于 IP)
Apache HTTPD(基于 cookie)
HAProxy(基于 cookie)
Traefik(基于 cookie)
Node.jscluster模块

nginx-ingress-controller:ngxin.org/lb-method

将该值配置成ip_hash即可。

nginx-ingress-controller:nginx.com/sticky-cookie-services

参考地址

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: cafe-ingress-with-session-persistenceannotations:nginx.com/sticky-cookie-services: "serviceName=coffee-svc srv_id expires=1h path=/coffee;serviceName=tea-svc srv_id expires=2h path=/tea"
spec:rules:- host: cafe.example.comhttp:paths:- path: /teapathType: Prefixbackend:service:name: tea-svcport:number: 80- path: /coffeepathType: Prefixbackend:service:name: coffee-svcport:number: 80

socket.io前后端实践及转发、多服务问题相关推荐

  1. 电商技术总结之SpringCloud+SpringBoot+mybatis+uniapp 前后端分离 b2b2c o2o 微服务商城电商之手机端首页模块设计分析

    近期我参与了公司电子商务平台中"首页"模块设计, 电商平台首页功能大概分为几个区域,如下: 一.导航区域: 左上角定位入口 搜索功能入口 消息通知入口 商品分类入口 二.广告区域: ...

  2. protobuf前后端解析_Go语言微服务架构实战:第七节 Protobuf协议语法及原理

    Protobuf 协议语法 message:Protobuf中定义一个数据结构需要用到关键字message,这一点和Java的class,Go语言中的struct类似. 标识号:在消息的定义中,每个字 ...

  3. 前后端分离的企业级微服务多租户系统架构,快速开发平台!

    正文 大家好.我今天,推荐一个快速开发平台系统项目.猿哥第一次使用就有点上头,爱不释手,必须要推荐给大家. 上次是谁要的快速开发平台系统项目啊,猿哥帮你找到了. 这是我目前见过最好的快速开发平台系统项 ...

  4. Socket.io:有点意思

    个人网站 欢迎品尝 edwardesire.com 下面页面就是使用Socket.io制作的口袋妖怪游戏(默认小屏下已隐藏,请切换到大分辨率查看).左边是游戏画面,右边是按键表和聊天室.画面达到红蓝版 ...

  5. websocket和socket.io

    websocket 三次握手 ws协议和http协议的区别 http HTTP是单向的,客户端发送请求,服务器发送响应.每个请求都与一个对应的响应相关联,在发送响应后客户端与服务器的连接会被关闭.每个 ...

  6. 使用Node.js+Socket.IO搭建WebSocket实时应用

    Web领域的实时推送技术,也被称作Realtime技术.这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新.它有着广泛的应用场景,比如在线聊天室.在线客服系统.评论系统.WebIM等. 作 ...

  7. java 同域名下怎么访问同事的项目_喜大普奔,两个开源的前后端分离项目可以在线体验了...

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和V 部落上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了,以帮助 ...

  8. 前后端分离架构下CSRF防御机制

    背景 1.什么是CSRF攻击? 这里不再介绍CSRF,已经了解CSRF原理的同学可以直接跳到:"3.前后端分离下有何不同?". 不太了解的同学可以看这两篇对CSRF介绍比较详细的参 ...

  9. nodejs socket.io 聊天室

    阅读目录 需求分析 Node.js Socket.IO 安装Node.js 搭建WebSocket服务端 服务端代码实现 客户端代码实现 Web领域的实时推送技术,也被称作Realtime技术.这种技 ...

最新文章

  1. 剑指offer:面试题05. 替换空格
  2. 2021年春季学期-信号与系统-第七次作业参考答案-第三小题
  3. 微信小程序-开心大转盘(圆盘指针)代码分析
  4. chips of wow
  5. 你的 Docker 应用是安全的吗?
  6. python ctime函数_Python time 模块
  7. 【转】linux图形界面编程基本知识
  8. 移动端ajax分页,移动端分页加载 - 花乐天的个人空间 - OSCHINA - 中文开源技术交流社区...
  9. c#使用 Newtonsoft.Json 将entity转json时,忽略为null的属性
  10. BZOJ1812: [Ioi2005]riv(树形dp)
  11. 限速器校验合格范围_限速
  12. JS如何改变元素内容?
  13. 在新版本WHM (64.0)中安装php5.3
  14. AD2019网络标号批量改名
  15. python基础 // 与 / % 的区别
  16. 华为机试---年终奖(动态规划)
  17. 日本一网友买煤炭自杀
  18. 软件研发效能的底层逻辑
  19. 新年签通用php,《转帖》个人制作 猎人TMW字符串 三系整合通用 新年快乐帖
  20. 关于快速方法SQL BAK Reader 小工具查看SQL Server备份文件内容(不需要打开SQL Server)很实用

热门文章

  1. http://localhost:8080/product/save找不到访问路径
  2. 新人学程序第一弹——Java程序实现九九乘法表
  3. 如何预估系统QPS?
  4. 关于我吹爆的buyvm机器的一次测评详情
  5. VCC、 VDD、VEE、VSS
  6. Xshell配色方案
  7. Hibernate中的方言
  8. 笑到最后的百度网盘将何去何从
  9. apache log4j漏洞复现
  10. 商女不知亡国恨,隔江犹吃炒腰花