SSE协议 的全称是 Server-Sent Events服务器发送事件本质是基于 HTTP 协议的 “单向实时推送技术”——只有服务器能主动给客户端发消息除了发送订阅请求外客户端只能接收数据。SSE消息是纯文本格式SSE标准支持自动重连。2、SSE与WebSocket底层协议SSE底层使用http协议http/1.1 chunked编码传输或http/2DATA帧序列其消息格式为纯文本。WebSocket握手建立连接通过http实现连接建立后使用WebSocket协议底层使用tcp协议WebSocket协议帧头比http/1.1要小而且SSE消息使用文本格式WebSocket则可以为文本 / 二进制所以WebSocket通信效率要高。通信特点WebSocket支持双向通信SSE是单向通信。SSE标准支持自动重连WebSocket则需要自己实现自动重连功能。浏览器支持SSE、WebSocket都属于JS原生标准可以直接在浏览器里使用。开发运维SSE支持自动重连WebSocket则需要自己实现心跳、连接状态判断等SSE开发要比WebSocket简单。SSE使用http协议所以可以复用http生态端口、配置等而WebSocket需要单独配置ws/wss协议、Nginx 转发。适用场景SSE——系统通知、实时文本数据推送。WebSocket——实时聊天、实时请求-应答保持连接的请求-应答。3、SSE服务实现实现SSE服务的话推荐使用Spring MVC的SseEmitter适用于传统的 Spring Boot Web 项目或Spring WebFlux的FluxServerSentEvent响应式非阻塞编程适合高并发的场景。安全考虑使用HTTPS防止中间人攻击验证连接权限如JWT Token防止CSRF攻击监控与日志记录连接数、消息发送成功率4、nginx与SSENginx可能会开启缓冲Buffering比如试图收集足够多的数据凑满4KB 或 8KB后再进行转发这就破坏了 SSE 的实时性使用 Nginx 反向代理 SSE 服务必须显式关闭缓冲使用 Nginx 反向代理SSE服务的话调整read_timeout和proxy_read_timeout以禁用读取超时read_timeout、send_timeout是nginx读取、写入客户的超时proxy_read_timeout、proxy_send_timeout是nginx读取、写入后端服务的超时proxy_connect_timeout是nginx连接后端服务的超时。5、SSE数据包格式SSE消息由一个或多个message组成每个message由\n\n分隔。一个message可以由一个或多个filed组成每个filed由\n分隔filed有data、id、event、retry四种类型。data表示消息数据如data:value\n。id表示数据包编号当连接断开重连的时候客户端应该发送一个 HTTP 头里面包含一个特殊的Last-Event-ID头信息值为收到的最后一条数据的id。event表示自定义的事件类型。retry表示浏览器重新发起连接的间隔时间当时间间隔到期客户端应该重新发起连接。还可以有仅以冒号开头的行表示注释。通常服务器每隔一段时间就会向浏览器发送一个注释保持连接不中断。包含一条message的消息data: {username: bobby, time: 02:33:48}包含两条message的消息data:this is message A data:this is data:message B包含三条message的消息:explan text data:this is message A data:this is data:message B6、SSE客户端实现SSE向服务端发送订阅推送请求的话使用GET方式如果需要发送参数的话通过URL 查询参数或设置http请求头实现。#include thread #include functional size_t dataCallback(void* ptr, size_t size, size_t nmemb, void* param) { if (ptr NULL || size 0 || param NULL) return 0; int s size * nmemb; std::string strMsg((char*)ptr, s); CSSEClient* client static_castCSSEClient*(param); if (client client-onMessage) client-onMessage(strMsg); return s; } int progressCallback(void* param, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { CSSEClient* client static_castCSSEClient*(param); return client-isExit() ? 1 : 0; // 返回非0值会中断curl_easy_perform() } class CSSEClient { public: CSSEClient(const std::string strURL) { curl_global_init(CURL_GLOBAL_DEFAULT); _strURL strURL; _thread std::thread(CSSEClient::run, this); } virtual ~CSSEClient() { _bExit true; _thread.join(); curl_global_cleanup(); } bool isExit() { return _bExit; } std::functionvoid(const std::string) onMessage; private: void run() { while (!_bExit) { subscribeSSE(); if (_bExit) break; std::this_thread::sleep_for(std::chrono::seconds(1)); } } void subscribeSSE() { CURL* curl curl_easy_init(); if (!curl) return; struct curl_slist* headers NULL; headers curl_slist_append(headers, Accept: text/event-stream); headers curl_slist_append(headers, Cache-Control: no-cache); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_URL, _strURL.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dataCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); //开启TCP心跳 curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 60L); //空闲60秒后探测 curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 10L); //探测间隔10秒 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); // 开启传输进度已下载/上传数据量、总数据量等回调 curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progressCallback); //设置传输进度回调方法即使没有数据传输该方法也会被调用通常每秒1次所以可以在该方法中判断是否需要退出 curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this); //设置传输回调方法的第一个参数 CURLcode res curl_easy_perform(curl); if (res CURLE_ABORTED_BY_CALLBACK) { //SSE connection aborted by user; } else { //error }