当前位置: 首页 > news >正文

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中&#xff0c;我们经常会遇到APP内部的消息推送&#xff0c;如关注的人的动态消息推送、点赞评论互动消息推送以及算法推荐消息推送。这些场景都是…...

写端口-tcp udp不同方式发包和接包

最近一直在学习网络编程&#xff0c;今天把 socket部分做一个总结。 Python 的socket库可以实现不同协议不同地址的发包和收包&#xff0c;无奈资料很少&#xff0c;官方例子有限&#xff0c;大神博客很少提及&#xff0c; 经过一番尝试后&#xff0c;总结以下几点用法以便大家…...

计算机的错误计算(一百二十)

摘要 探讨在许多应用中出现的函数 的计算精度问题。 例1. 考虑在许多应用中出现的函数 计算 不妨在Python下计算&#xff1a; 若用下列Rust代码在线计算&#xff1a; fn f(x: f64) -> f64 {(x.exp() - 1.0) / x }fn main() {let result f(0.9e-13);println!("…...

Spring Boot 中使用 JSON Schema 来校验复杂 JSON 数据

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 在现代软件开发中&#xff0c;尤其是构建 RESTful API 时&#xff0c;处理 JSON 数据已成为一项基本任务。JSON&#xff08;JavaScript Object Notation&#xff09;因其轻量级和易于人类阅读的特点&#xff…...

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系统的时候很是苦恼&#xff0c;到底该怎么卸载App啊&#xff0c;App直接拖到废纸篓真的能卸载干净吗&#xff0c;卸载App时会不会留下一些文件残留&#xff0c;慢慢的会不会占满内存&#xff0c;于是我找到了一个免费的卸载工具——XApp。 这是一款Mac应用程序卸载…...

Unity 3d 继承MonoBahaviour的单例

在使用Unity3d开发游戏或做客户端项目时&#xff0c;单例是最常见的模式之一&#xff0c;他简单了类的创建&#xff0c;在代码中可以直接调用。下面是两个例子&#xff0c;代码两种不同类型的单例&#xff0c;一个是基本类的单例基类&#xff0c;不是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&#xff0c;确保只显示整数...

Elasticsearch的安装与配置

注意&#xff1a;elasticsearch 禁止安装在/root路径下&#xff01; 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

参考&#xff1a;https://blog.csdn.net/xitongzhijia_abc/article/details/126270452 win10如下所示&#xff1a; 打开 设置–>系统—>存储...

常见IDE及其编译器的讲解

IDE 意思是&#xff1a;集成开发环境 常见的IDE有哪些&#xff1f; eg. devC,VS2022,xcode,codeblocks,clion常见编译器有哪些&#xff1f; eg.msvc,gcc,clang微软的底层编译器是msvc苹果的底层编译器是clang IDE编译器特点devC集成了gcc小巧&#xff0c;工具简单&…...

用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/…...

获取时隔半个钟的三天

摘要&#xff1a; 今天遇到需求是配送时间&#xff0c;时隔半个钟的排线&#xff01;所以需要拼接时间&#xff01;例如2024-10-08 14&#xff1a;30&#xff0c;2024-10-08 15&#xff1a;00&#xff0c;2024-10-08 15&#xff1a;30 <el-form-item label"配送时间&a…...

构建可以ssh连接的容器镜像

构建可以ssh连接的容器镜像 构建可以通过ssh进行连接容器镜像&#xff0c;实现远程登录容器的目的。 ubuntu ssh容器镜像 你可以使用以下Dockerfile来构建一个可以SSH的容器镜像&#xff1a; FROM ubuntu:20.04MAINTAINER lldhsds# 配置apt国内源 COPY sources.list /etc/a…...

数据库中JOIN的用法?

在数据库中&#xff0c;JOIN 操作用于结合来自两个或多个表的数据&#xff0c;基于它们之间的相关列。JOIN 是关系数据库查询中一个非常重要的功能&#xff0c;允许你从多个表中提取相关的数据。以下是几种常见的 JOIN 类型及其用法&#xff1a; INNER JOIN: 返回两个表中匹配的…...

java项目之纺织品企业财务管理系统源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的纺织品企业财务管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于spring boot…...

C语言 编程练习:解决五个有趣的问题

文章目录 编程练习&#xff1a;解决五个有趣的问题1. 出租车费用计算器1. 1 代码code 2. 成绩转换统计2. 1 代码code 3. 交错序列求和3. 1 代码code 4. 阶乘序列求和4. 1 代码code 5. 输出星号图案5. 1 代码code 编程练习&#xff1a;解决五个有趣的问题 在编程的世界里&#…...

二、安装vmtools

1、 介绍 vmtools 安装后&#xff0c;可以让我们在 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:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;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&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看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模版&#xff0c;莫兰迪调色板清新简约工作汇报PPT模版&#xff0c;莫兰迪时尚风极简设计PPT模版&#xff0c;大学生毕业论文答辩PPT模版&#xff0c;莫兰迪配色总结计划简约商务通用PPT模版&#xff0c;莫兰迪商务汇报PPT模版&#xff0c;…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态

前言 在人工智能技术飞速发展的今天&#xff0c;深度学习与大模型技术已成为推动行业变革的核心驱动力&#xff0c;而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心&#xff0c;系统性地呈现了两部深度技术著作的精华&#xff1a;…...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...

MySQL体系架构解析(三):MySQL目录与启动配置全解析

MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录&#xff0c;这个目录下存放着许多可执行文件。与其他系统的可执行文件类似&#xff0c;这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中&#xff0c;用…...