gin如何具体利用Server-Send-Events(SSE)实时推送技术实现消息推送
目录
业务场景
解决方案
1. 轮询
2. WebSocket
3. SSE(Server-Send-Events)
代码实现
总结
业务场景
在抖音、美团等APP中,我们经常会遇到APP内部的消息推送,如关注的人的动态消息推送、点赞评论互动消息推送以及算法推荐消息推送。这些场景都是服务端主动向客户端进行推送实时消息,可以很大程度上提高用户体验。
解决方案
1. 轮询
短轮询:客户端会定时向服务端发送请求,询问在上次询问到现在时刻是否有新的数据,如有则返回给客户端。
长轮询:是对短轮询的优化。每次询问,若有新数据则立刻返回给客户端;若没有新数据,则服务端会等待一段时间,若有新数据则返回给客户端,否则返回空数据。
总之,轮询实现起来最简单,但是无法保证实时性,延迟大且对服务端资源消耗高。
2. WebSocket
websocket是一种双向通信协议,同时支持服务端和客户端之间的实时交互。WebSocket 是基于 TCP 的长连接,和HTTP 协议相比,它能实现轻量级的、低延迟的数据传输,非常适合实时通信场景,主要用于交互性强的双向通信。
3. SSE(Server-Send-Events)
SSE(Server-Sent Events)是一种基于 HTTP 协议的推送技术。服务端可以使用 SSE 来向客户端推送数据,但客户端不能通过SSE向服务端发送数据。相较于 WebSocket,SSE 更简单、更轻量级,但只能实现单向通信。
针对这三种解决方案来看,SSE最贴合当前的业务场景。在消息推送中,只需要服务端对客户端进行单向通信,使用SSE在保证实时性的同时,比WebSocket更加轻量级。
代码实现
1. 我们通过两个接口来实现sse的一个简单demo:分别用来实现SSE的连接和消息推送的触发。
r.GET("/notification/socket-connection", SocketConnection) // 建立sse连接
r.GET("/notification/export-excel", ExportExcel) // 触发通知,发送消息
2. 建立sse连接:每一个客户端和服务端都需要有一个单独的通道去维持双方的连接,这样才能保证既可以推送广播类的消息,也可以推送定制化的消息。
所以,我们可以通过一个map来存储所有客户端和服务端之间的通道连接。
var channelsMap sync.Map
需要注意的是:这里会对map进行并发操作,所以这里可以使用sync.Map来保证在访问map时的并发安全。
为了保证连接的不丢失,当客户端建立SSE连接,我们就需要创建一个独属于该客户端(如userId作为唯一标识)的通道,然后通过遍历当前通道去保证主线程的阻塞,若通道中有信息则将信息推送到客户端,并继续等待消息。
同时开一个协程去监听客户端连接是否关闭,若关闭则关闭相应通道,结束请求。
func AddChannel(userEmail string, traceId string) {if !ifChannelsMapInit {channelsMap = sync.Map{}ifChannelsMapInit = true}newChannel := make(chan string)channelsMap.Store(userEmail+traceId, newChannel)log.Print("Build SSE connection for user = " + userEmail + ", trace id = " + traceId)
}
func BuildNotificationChannel(userEmail string, traceId string, c *gin.Context) {AddChannel(userEmail, traceId)c.Writer.Header().Set("Content-Type", "text/event-stream")c.Writer.Header().Set("Cache-Control", "no-cache")c.Writer.Header().Set("Connection", "keep-alive")// 获取http写入器并断言为flusher,让其将缓冲器的数据立即写入w := c.Writerflusher, _ := w.(http.Flusher)// 监听客户端通道是否被关闭closeNotify := c.Request.Context().Done()go func() {<-closeNotifychannelsMap.Delete(userEmail + traceId)log.Print("SSE close for user = " + userEmail + ", trace id = " + traceId)return}()curChan, _ := channelsMap.Load(userEmail + traceId)for msg := range curChan.(chan string) {fmt.Fprintf(w, "data:%s\n\n", msg)flusher.Flush()}
}
3. 触发消息推送
当发布系统消息、或者某个用户对该用户的文章进行了点赞,则需要遍历map找到对应的通道,向通道中推送消息。在sse请求中,正在遍历当前的channel,接收到通道的消息后,实时推送到客户端。
func SendNotification(userEmail string, messageBody string, actionType string) {log.Print("Send notification to user = " + userEmail)var msg = NotificationLog{MessageBody: messageBody,UserEmail: userEmail,Type: actionType,Status: "UNREAD",CreatTime: time.Now(),}msgBytes, _ := json.Marshal(msg)channelsMap.Range(func(key, value any) bool {k := key.(string)if strings.Contains(k, userEmail) {channel := value.(chan string)channel <- string(msgBytes)}return true})
}
4. 跨域
sse是不支持跨域的,所以我们可以加一个跨域中间件。
func CORSMiddleware() gin.HandlerFunc {return func(c *gin.Context) {method := c.Request.Method //请求方法origin := c.Request.Header.Get("Origin") //请求头部var headerKeys []string // 声明请求头keysfor k, _ := range c.Request.Header {headerKeys = append(headerKeys, k)}headerStr := strings.Join(headerKeys, ", ")if headerStr != "" {headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr)} else {headerStr = "access-control-allow-origin, access-control-allow-headers"}if origin != "" {c.Writer.Header().Set("Access-Control-Allow-Origin", "*")c.Header("Access-Control-Allow-Origin", "*") // 这是允许访问所有域c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") //服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求// header的类型c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")// 允许跨域设置 可以返回其他子段c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") // 跨域关键设置 让浏览器可以解析c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为truec.Set("content-type", "application/json") // 设置返回格式是json}//放行所有OPTIONS方法if method == "OPTIONS" {c.JSON(http.StatusOK, "Options Request!")}// 处理请求c.Next() // 处理请求}
}
总结
以上便是sse技术的实现原理,具体项目代码如下:https://github.com/ningzhaoxing/sse-demo
总之,SSE 技术是一种轻量级的实时推送技术,具有支持跨域、使用简单、支持自动重连等特点,使得其在实时消息推送等场景下广泛使用。另外,SSE 相对于 WebSocket 更加轻量级,如果需求场景不需要交互式动作,那么 SSE 是一个不错的选择!
相关文章:
gin如何具体利用Server-Send-Events(SSE)实时推送技术实现消息推送
目录 业务场景 解决方案 1. 轮询 2. WebSocket 3. SSE(Server-Send-Events) 代码实现 总结 业务场景 在抖音、美团等APP中,我们经常会遇到APP内部的消息推送,如关注的人的动态消息推送、点赞评论互动消息推送以及算法推荐消息推送。这些场景都是…...
写端口-tcp udp不同方式发包和接包
最近一直在学习网络编程,今天把 socket部分做一个总结。 Python 的socket库可以实现不同协议不同地址的发包和收包,无奈资料很少,官方例子有限,大神博客很少提及, 经过一番尝试后,总结以下几点用法以便大家…...
计算机的错误计算(一百二十)
摘要 探讨在许多应用中出现的函数 的计算精度问题。 例1. 考虑在许多应用中出现的函数 计算 不妨在Python下计算: 若用下列Rust代码在线计算: fn f(x: f64) -> f64 {(x.exp() - 1.0) / x }fn main() {let result f(0.9e-13);println!("…...
Spring Boot 中使用 JSON Schema 来校验复杂 JSON 数据
博客主页: 南来_北往 系列专栏:Spring Boot实战 在现代软件开发中,尤其是构建 RESTful API 时,处理 JSON 数据已成为一项基本任务。JSON(JavaScript Object Notation)因其轻量级和易于人类阅读的特点ÿ…...
QT实现Opencv图像处理
案例 基于QT的人脸识别 pro文件需要加以下代码 INCLUDEPATH E:/opencv/opencv3.4-qt-intall/install/include INCLUDEPATH E:/opencv/opencv3.4-qt-intall/install/include/opencv INCLUDEPATH E:/opencv/opencv3.4-qt-intall/install/include/opencv2 LIBS E:/opencv/o…...
刚转Mac的新手如何卸载不需要的应用程序
最开始转Mac系统的时候很是苦恼,到底该怎么卸载App啊,App直接拖到废纸篓真的能卸载干净吗,卸载App时会不会留下一些文件残留,慢慢的会不会占满内存,于是我找到了一个免费的卸载工具——XApp。 这是一款Mac应用程序卸载…...
Unity 3d 继承MonoBahaviour的单例
在使用Unity3d开发游戏或做客户端项目时,单例是最常见的模式之一,他简单了类的创建,在代码中可以直接调用。下面是两个例子,代码两种不同类型的单例,一个是基本类的单例基类,不是unity MonoBehaviour的类都…...
grafana version 11.1.0 设置Y轴刻度为1
grafana 版本 # /usr/share/grafana/bin/grafana --version grafana version 11.1.0设置轴 Axis 搜索 Standard options 在"Decimals"中输入0,确保只显示整数...
Elasticsearch的安装与配置
注意:elasticsearch 禁止安装在/root路径下! 1、创建用户组 groupadd elastic 2、创建用户 useradd es -d /home/es -g elastic echo es | passwd es --stdin 3、给新创建的用户进行授权 chown -R es:elastic /home/es chmod -R 775 /home/es 4…...
win0删除 Windows.old
参考:https://blog.csdn.net/xitongzhijia_abc/article/details/126270452 win10如下所示: 打开 设置–>系统—>存储...
常见IDE及其编译器的讲解
IDE 意思是:集成开发环境 常见的IDE有哪些? eg. devC,VS2022,xcode,codeblocks,clion常见编译器有哪些? eg.msvc,gcc,clang微软的底层编译器是msvc苹果的底层编译器是clang IDE编译器特点devC集成了gcc小巧,工具简单&…...
用SQLyog连接mysql提示2058错误
1)在cmd下(必须是这个,不能是gitbash) // step1:修改下数据库 C:\Users\elex>mysql -uroot -p Enter password: **** Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 97 Server version: 8.1.0 MySQL Community Server - GPLCopy…...
Web集群服务-Nginx
1. web服务 1. WEB服务:网站服务,部署并启动了这个服务,你就可以搭建一个网站 2. WEB中间件: 等同于WEB服务 3. 中间件:范围更加广泛,指的负载均衡之后的服务 4. 数据库中间件:数据库缓存,消息对列 2. 极速上手指南 nginx官网: nginx documentation 2.1 配置yum源 vim /etc/…...
获取时隔半个钟的三天
摘要: 今天遇到需求是配送时间,时隔半个钟的排线!所以需要拼接时间!例如2024-10-08 14:30,2024-10-08 15:00,2024-10-08 15:30 <el-form-item label"配送时间&a…...
构建可以ssh连接的容器镜像
构建可以ssh连接的容器镜像 构建可以通过ssh进行连接容器镜像,实现远程登录容器的目的。 ubuntu ssh容器镜像 你可以使用以下Dockerfile来构建一个可以SSH的容器镜像: FROM ubuntu:20.04MAINTAINER lldhsds# 配置apt国内源 COPY sources.list /etc/a…...
数据库中JOIN的用法?
在数据库中,JOIN 操作用于结合来自两个或多个表的数据,基于它们之间的相关列。JOIN 是关系数据库查询中一个非常重要的功能,允许你从多个表中提取相关的数据。以下是几种常见的 JOIN 类型及其用法: INNER JOIN: 返回两个表中匹配的…...
java项目之纺织品企业财务管理系统源码(springboot+vue+mysql)
风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的纺织品企业财务管理系统。项目源码以及部署相关请联系风歌,文末附上联系信息 。 项目简介: 基于spring boot…...
C语言 编程练习:解决五个有趣的问题
文章目录 编程练习:解决五个有趣的问题1. 出租车费用计算器1. 1 代码code 2. 成绩转换统计2. 1 代码code 3. 交错序列求和3. 1 代码code 4. 阶乘序列求和4. 1 代码code 5. 输出星号图案5. 1 代码code 编程练习:解决五个有趣的问题 在编程的世界里&#…...
二、安装vmtools
1、 介绍 vmtools 安装后,可以让我们在 windows 下更好的管理 vm 虚拟机。可以设置 windows 和 centos 的共享文件夹 当时当我们发现安装虚拟机工具位置是灰色的 右击打开终端 在终端输入命令 yum list installed | grep open-vm-*yum list installed 命令会列出…...
用echarts画天气预报
如图 上代码 <template><div id"temp15day"></div> </template><script setup> import * as echarts from "echarts"; const initChart () > {const machart echarts.init(document.getElementById("temp15day&q…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...
加密通信 + 行为分析:运营商行业安全防御体系重构
在数字经济蓬勃发展的时代,运营商作为信息通信网络的核心枢纽,承载着海量用户数据与关键业务传输,其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级,传统安全防护体系逐渐暴露出局限性&a…...
MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...
