socket.io前后端实践及转发、多服务问题
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前后端实践及转发、多服务问题相关推荐
- 电商技术总结之SpringCloud+SpringBoot+mybatis+uniapp 前后端分离 b2b2c o2o 微服务商城电商之手机端首页模块设计分析
近期我参与了公司电子商务平台中"首页"模块设计, 电商平台首页功能大概分为几个区域,如下: 一.导航区域: 左上角定位入口 搜索功能入口 消息通知入口 商品分类入口 二.广告区域: ...
- protobuf前后端解析_Go语言微服务架构实战:第七节 Protobuf协议语法及原理
Protobuf 协议语法 message:Protobuf中定义一个数据结构需要用到关键字message,这一点和Java的class,Go语言中的struct类似. 标识号:在消息的定义中,每个字 ...
- 前后端分离的企业级微服务多租户系统架构,快速开发平台!
正文 大家好.我今天,推荐一个快速开发平台系统项目.猿哥第一次使用就有点上头,爱不释手,必须要推荐给大家. 上次是谁要的快速开发平台系统项目啊,猿哥帮你找到了. 这是我目前见过最好的快速开发平台系统项 ...
- Socket.io:有点意思
个人网站 欢迎品尝 edwardesire.com 下面页面就是使用Socket.io制作的口袋妖怪游戏(默认小屏下已隐藏,请切换到大分辨率查看).左边是游戏画面,右边是按键表和聊天室.画面达到红蓝版 ...
- websocket和socket.io
websocket 三次握手 ws协议和http协议的区别 http HTTP是单向的,客户端发送请求,服务器发送响应.每个请求都与一个对应的响应相关联,在发送响应后客户端与服务器的连接会被关闭.每个 ...
- 使用Node.js+Socket.IO搭建WebSocket实时应用
Web领域的实时推送技术,也被称作Realtime技术.这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新.它有着广泛的应用场景,比如在线聊天室.在线客服系统.评论系统.WebIM等. 作 ...
- java 同域名下怎么访问同事的项目_喜大普奔,两个开源的前后端分离项目可以在线体验了...
折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和V 部落上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了,以帮助 ...
- 前后端分离架构下CSRF防御机制
背景 1.什么是CSRF攻击? 这里不再介绍CSRF,已经了解CSRF原理的同学可以直接跳到:"3.前后端分离下有何不同?". 不太了解的同学可以看这两篇对CSRF介绍比较详细的参 ...
- nodejs socket.io 聊天室
阅读目录 需求分析 Node.js Socket.IO 安装Node.js 搭建WebSocket服务端 服务端代码实现 客户端代码实现 Web领域的实时推送技术,也被称作Realtime技术.这种技 ...
最新文章
- Wireshark数据包分析之TCP协议包解读
- ZCF提出解决零确认交易安全问题新方案
- Linux 中文件管理常用的工具
- hdu 1081To The Max
- Spring boot 打包jar 将配置文件分离
- 遇到指针别害怕!先把这篇笔记看一遍~
- 虹软免费人脸识别SDK注册指南
- css中auto啥意思,CSS中各种auto值的作用总结
- 《C Primer Plus》读书笔记
- C语言 SDK编程之通用控件的使用--ListView
- centos 6.5 编译php mysql5.6_CentOS6.5 编译安装PHP5.6(apache模块)
- 2019年美赛D题翻译与思路详解
- SVN下载安装、SVN使用详细教程(Window+Linux)
- 计算机硬件与哪些部分组成部分,计算机硬件组成及各部分功能有哪些?
- Linux系统中查看LWP(轻量级进程)、进程 、 线程的ID的方法
- 醉林疯的PTA 7-2 换硬币 (20分)
- 电脑连接wifi找不到服务器,为什么电脑连不上wifi显示没有有效的ip配置
- cordova获取手机IMEI
- 欧美slots游戏 源码(完整)
- SSM框架的基本概念(什么是ssm框架?)
热门文章
- 用Google来翻译你的网页
- 安卓Native逆向之MOO音乐解密( .bkcflac,bkcmp3文件解密)
- uniapp将聊天页面定位始终定位到最底部展示
- 5s解决报错:Unbound pointcut parameter ‘xxxxxx‘
- python加法例子_用python给小孩随机生成一组10以内加减法
- HTML做一个简单漂亮的宠物网页(纯html代码)宠物 5页(二级菜单)
- 贵金属实时行情看盘软件排行榜(top 10)
- 把pdf转换成ppt的软件
- 浙大计算机专业博士后,博士后 - 浙江大学系统医学研究中心
- 谈一谈|如何理解马云4月14日再谈996