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

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...