服務端推送技術 Server-sent Events springBoot程式碼示例
SSE推送技術
SSE全稱Server-sent Events,是HTML 5 規範的一個組成部分,具體去MDN網站檢視相關文件。該規範十分簡單,
SSE推送技術是伺服器端與瀏覽器端之間的通訊協議,通訊協議是基於純文字的簡單協議。
伺服器端的響應的內容型別是“text/event-stream”。響應文字的內容可以看成是一個事件流,由不同的事件所組成。每個事件由型別和資料兩部分組成,同時每個事件可以有一個可選的識別符號。不同事件的內容之間通過僅包含回車符和換行符的空行(“rn”)來分隔。每個事件的資料可能由多行組成。
如上圖所示,每個事件之間通過空行來分隔。每一行都是由鍵值對組成。如果鍵為空則表示該行為註釋,會在處理時被忽略。例如第10行。第1行表示一個只包含資料的事件。會按照預設事件走(message事件)。第3-4代表一個附帶eventID的事件。第6-8代表一個自定義事件。第10-14代表一個多行資料事件,多行資料由換行符連結
key定義有以下幾種:
- data,表示該行包含的是資料。以 data 開頭的行可以出現多次。所有這些行都是該事件的資料。
- 型別為 event,表示該行用來宣告事件的型別。瀏覽器在收到資料時,會產生對應型別的事件。預設提供三個標準事件(當然你可以自定義):
- id,表示該行用來宣告事件的識別符號。伺服器端返回的資料中包含了事件的識別符號,瀏覽器會記錄最近一次接收到的事件的識別符號。如果與伺服器端的連線中斷,當瀏覽器端再次進行連線時,會通過 HTTP 頭“Last-Event-ID”來宣告最後一次接收到的事件的識別符號。伺服器端可以通過瀏覽器端傳送的事件識別符號來確定從哪個事件開始來繼續連線。
- retry,表示該行用來宣告瀏覽器在連線斷開之後進行再次連線之前的等待時間。
SSE只適用於高階瀏覽器,但是注意IE不直接支援。IE上的XMLHttpRequest物件不支援獲取部分的響應內容,所以不支援。每次總有IE怪不得快被淘汰了。
SSE VS Websocket
- SSE 只能Server到Client單項,而Websocket是雙向通訊。
- SSE 比 Websocket 輕量。當然功能要簡單的多。開發便利,不牽涉協議升級問題。
- SSE 天然支援斷線重連
服務端程式碼示例
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.hxtx.spacedata.common.domain.ResponseDTO;
import com.hxtx.spacedata.domain.entity.task.TaskInfoEntity;
import com.hxtx.spacedata.enums.task.TaskInfoStatusEnum;
import com.hxtx.spacedata.mapper.task.TaskInfoDao;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 服務端推送技術 server-sent events
* @description
* @author tarzan Liu
* @version 1.0.0
* @date 2020/10/27
*/
@RestController
@Slf4j
public class SSEController {
@Autowired
private TaskInfoDao taskInfoDao;
private static ConcurrentHashMap<String,Long> ssePushUsers = new ConcurrentHashMap<>();
/**
* 如果沒有客戶端,則直接修改訊息已傳送 (2分鐘執行一次)
* @author sunboqiang
* @date 2020/11/3
*/
@Scheduled(cron = "0 0/2 * * * ?")
public void finishSend() {
if(ssePushUsers.size()==0){
QueryWrapper<TaskInfoEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(TaskInfoEntity::getStatus, TaskInfoStatusEnum.SUCCESS.getStatus());
queryWrapper.lambda().eq(TaskInfoEntity::getSendStatus,0);
List<TaskInfoEntity> list = taskInfoDao.selectList(queryWrapper);
if(CollectionUtils.isNotEmpty(list)){
taskInfoDao.updateSendStatusByIds(list.stream().map(TaskInfoEntity::getId).collect(Collectors.toList()), 2);
}
}
}
/**
* 剔除關閉的客戶端
* @author sunboqiang
* @date 2020/11/3
*/
@Scheduled(cron = "0/2 * * * * ?") // 2S執行一次
public void clear() {
//2秒執行一次,時間差>5S 說明客戶端關閉了,直接剔除
long now = System.currentTimeMillis();
for (Iterator<Map.Entry<String, Long>> it = ssePushUsers.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Long> item = it.next();
long time = item.getValue();
//log.info(item.getKey()+"註冊時間差:"+(now - time)/1000);
if(now - time > 5000){
//5 秒
it.remove();
log.info("剔除客戶端:"+item.getKey());
}
}
}
@GetMapping(value="/sse/push/version/get")
public String getVersion(HttpServletRequest request){
HttpSession session = request.getSession();
if(null != session){
return session.getId();
}
return null;
}
/**
* 推送C++ json檔案編譯情況資訊
* @author sunboqiang
* @date 2020/10/29
*/
@GetMapping(value="/sse/push/{version}",produces="text/event-stream;charset=utf-8")
public String push(@PathVariable("version") String version) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
QueryWrapper<TaskInfoEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(TaskInfoEntity::getStatus, TaskInfoStatusEnum.SUCCESS.getStatus());
queryWrapper.lambda().eq(TaskInfoEntity::getSendStatus,0);
List<TaskInfoEntity> list = taskInfoDao.selectList(queryWrapper);
String data = "";
if(CollectionUtils.isEmpty(list)){
//還沒有訊息,收集等待推送的客戶端
ssePushUsers.put(version,System.currentTimeMillis());
//data = "data:沒有編譯訊息,當前開啟客戶端數量:"+ ssePushUsers.size()+"個;" +"\n\n";
} else {
List<Long> drawingIds = list.stream().map(TaskInfoEntity::getDrawingId).distinct().collect(Collectors.toList());
//編譯成功,推送訊息
if(ssePushUsers.size()>0){
//存在接收客戶端
ResponseDTO result = new ResponseDTO();
result.setCode(1);
result.setMsg("有新的編譯");
result.setSuccess(true);
result.setData("drawingIds="+drawingIds);
data = "data:"+ JSONObject.toJSONString(result) +"\n\n";
ssePushUsers.remove(version);
if(ssePushUsers.size() == 0){
//最後一個客戶端推送完成
taskInfoDao.updateSendStatusByIds(list.stream().map(TaskInfoEntity::getId).collect(Collectors.toList()), 1);
}
} else {
//沒有客戶端,直接推送成功
taskInfoDao.updateSendStatusByIds(list.stream().map(TaskInfoEntity::getId).collect(Collectors.toList()), 1);
}
}
return data;
}
}
相關文章
- 服務端推送技術 Server-sent Events 快速上手服務端Server
- 深入淺出 Server-sent events 技術Server
- 服務端主動推送技術☞WebSocket服務端Web
- Server-Sent Events 教程Server
- vue:服務端渲染技術Vue服務端
- Vue 服務端渲染技術Vue服務端
- Server-sent Events 介面壓測Server
- IOS 推送訊息 php做推送服務端iOSPHP服務端
- 服務端技術方案模板參考服務端
- java WebSocket 服務端程式碼JavaWeb服務端
- 極光推送-服務端端智慧人社訊息推送方式服務端
- 微信小程式服務推送微信小程式
- 凹凸技術揭祕 · 基礎服務體系 · 構築服務端技術中樞服務端
- springboot2整合websocket,實現服務端推送訊息到客戶端Spring BootWeb服務端客戶端
- NodeJS Express 中建立html5的server-sent event服務端NodeJSExpressHTMLServer服務端
- Fabric 1.0原始碼分析(13)events(事件服務)原始碼事件
- APP訊息推送 極光推送 示例程式碼APP
- Nacos(二)原始碼分析Nacos服務端註冊示例流程原始碼服務端
- 利用WebSocket和EventSource實現服務端推送Web服務端
- .Net Remoting服務端與客戶端呼叫示例REM服務端客戶端
- Rest Post示例(java服務端、python客戶端)RESTJava服務端Python客戶端
- Oracle 服務端程式Oracle服務端
- springboot整合eureka,服務相互呼叫簡單示例Spring Boot
- 基於Netty實現海量接入的推送服務技術要點Netty
- 訊息推送服務的技術挑戰難度在哪裡,線上直播原始碼告訴你原始碼
- Dart編譯技術在服務端的探索和應用Dart編譯服務端
- 2018服務端架構師技術圖譜服務端架構
- 「iOS」行車服務app 「客戶端、後端思路+程式碼」iOSAPP客戶端後端
- 關於如何提高Web服務端併發效率的非同步程式設計技術Web服務端非同步程式設計
- 逐句回答,流式返回,ChatGPT採用的Server-sent events後端實時推送協議Python3.10實現,基於Tornado6.1ChatGPTServer後端協議Python
- Kotlin + SpringBoot + JPA 服務端開發KotlinSpring Boot服務端
- 【技術乾貨】程式碼示例:使用 Apache Spark 連線 TDengineApacheSpark
- 金武盟(NFT)系統程式設計開發技術(程式碼示例)程式設計
- WebSocket實現服務端推送訊息和聊天會話Web服務端會話
- 分散式服務框架之遠端通訊技術及原理分析分散式框架
- 「PHP」行車服務app後端程式碼簡析PHPAPP後端
- Git強制推送程式碼到遠端Git
- 【技術乾貨】程式碼示例:使用 Apache Flink 連線 TDengineApache