OkHttp SSE 完整總結(最終版)

1. SSE 基礎概念

什么是 SSE?

SSE(Server-Sent Events)是一種 Web 標準,允許服務器向客戶端推送實時數據。

核心特點

  • 單向通信:服務器 → 客戶端
  • 基于 HTTP 協議:使用 GET 請求
  • 長連接:連接建立后保持開放
  • 自動重連:網絡中斷時自動重連

2. SSE 協議詳解

HTTP 請求格式

?

GET?/events?HTTP/1.1

Host:?api.example.com

Accept:?text/event-stream

Cache-Control:?no-cache

服務器響應格式

?

HTTP/1.1?200?OK

Content-Type:?text/event-stream

Cache-Control:?no-cache

Connection:?keep-alive

data:?{"message":?"Hello"}

data:?{"message":?"World"}

data:?{"message":?"Test"}

SSE 數據格式

?

id:?12345

event:?message

data:?{"content":?"Hello?World"}

data:?{"content":?"Another?message"}

3. OkHttp SSE 三種實現方式對比

方式一:使用 OkHttp SSE 庫(標準 SSE 實現)

依賴配置

?

implementation?'com.squareup.okhttp3:okhttp:4.9.3'implementation?'com.squareup.okhttp3:okhttp-sse:4.9.3'

基本實現

?

OkHttpClient?client?=?new?OkHttpClient.Builder().connectTimeout(30,?TimeUnit.SECONDS).readTimeout(0,?TimeUnit.SECONDS)??//?重要:無讀取超時.build();Request?request?=?new?Request.Builder().url("https://api.example.com/events").addHeader("Accept",?"text/event-stream").build();EventSource?eventSource?=?EventSources.createEventSource(client,?request,?new?EventSourceListener()?{@Overridepublic?void?onOpen(EventSource?eventSource,?Response?response)?{System.out.println("SSE?連接建立成功");System.out.println("響應碼:?"?+?response.code());System.out.println("響應頭:?"?+?response.headers());}@Overridepublic?void?onEvent(EventSource?eventSource,?String?id,?String?type,?String?data)?{//?自動解析?SSE?格式,直接得到?dataSystem.out.println("收到數據:?"?+?data);System.out.println("事件ID:?"?+?id);System.out.println("事件類型:?"?+?type);}@Overridepublic?void?onClosed(EventSource?eventSource)?{System.out.println("SSE?連接已關閉");}@Overridepublic?void?onFailure(EventSource?eventSource,?Throwable?t,?Response?response)?{System.err.println("SSE?連接失敗:?"?+?t.getMessage());if?(response?!=?null)?{System.err.println("響應碼:?"?+?response.code());}}});

SSE 庫的優勢
  • ??自動處理 SSE 協議:自動解析?data:、id:、event:?等字段
  • ??內置重連機制:網絡中斷時自動重連
  • ??標準 SSE 實現:完全符合 SSE 規范
  • ??更高級的 API:提供?EventSource?和?EventSourceListener
  • ??自動連接管理:自動處理連接生命周期

方式二:自定義 SSE 客戶端(推薦用于 Kotlin 項目)

依賴配置

?

//?只需要基本的?OkHttp,不需要?SSE?庫implementation?'com.squareup.okhttp3:okhttp:4.9.3'

自定義 SSE 客戶端實現

先補一下知識點:

trySend、Channel 和 Flow 的工作原理-CSDN博客

再看代碼:

class SSEClient {private val okHttpClient by lazy {OkHttpClient.Builder().readTimeout(0, TimeUnit.SECONDS) // 禁用讀取超時.build()}private var call: Call? = nullsealed class SSEEvent {data class Message(val event: String, val data: String) : SSEEvent()data class Error(val throwable: Throwable) : SSEEvent()object Closed : SSEEvent()}/*** 連接到 SSE 服務器* @param url SSE 服務器地址* @return Flow 流式返回事件*/fun connect(url: String, token :String): Flow<SSEEvent> = callbackFlow {val request = Request.Builder().url(url).addHeader("Accept", "text/event-stream").addHeader("Cache-Control", "no-cache").addHeader("X-Access-Token", token).build()call = okHttpClient.newCall(request)call?.enqueue(object : Callback {override fun onFailure(call: Call, e: IOException) {trySend(SSEEvent.Error(e))close(e)}override fun onResponse(call: Call, response: Response) {if (!response.isSuccessful) {trySend(SSEEvent.Error(IOException("Unexpected code: ${response.code}")))close()return}val body = response.body ?: returntry {var currentEvent = "message"val dataBuffer = StringBuilder()while (true) {val line = body.source().readUtf8Line() ?: breakwhen {line.startsWith("event:") -> currentEvent = line.substring(6).trim()line.startsWith("data:") -> dataBuffer.append(line.substring(5).trim()).append("\n")line.isEmpty() -> { // 空行表示事件結束if (dataBuffer.isNotEmpty()) {val data = dataBuffer.toString().trim()trySend(SSEEvent.Message(currentEvent, data))dataBuffer.clear()}}}}trySend(SSEEvent.Closed)} catch (e: Exception) {trySend(SSEEvent.Error(e))} finally {response.close()close()}}})awaitClose { disconnect() }}/*** 斷開連接*/fun disconnect() {call?.cancel()call = null}/*** 重試連接到 SSE 服務器* @param url SSE 服務器地址* @param token 認證Token* @param maxRetries 最大重試次數* @param retryDelay 重試延遲時間(毫秒)* @return Flow 流式返回事件*/fun connectWithRetry(url: String, token: String, maxRetries: Int = Int.MAX_VALUE, retryDelay: Long = 3000): Flow<SSEEvent> = flow {var retryCount = 0while (retryCount < maxRetries) {connect(url, token).collect { event ->when (event) {is SSEClient.SSEEvent.Closed,is SSEClient.SSEEvent.Error -> {emit(event)if (retryCount < maxRetries) {delay(retryDelay)retryCount++}}else -> emit(event)}}}}
}

使用方式示例

fun?initSSE(token:?String)?{sseClient?=?SSEClient()val?sseUrl?=?BASE_URL//?啟動?SSE?連接sseClient.connect(sseUrl,?token).onEach?{?event?->when?(event)?{is?SSEClient.SSEEvent.Message?->?{//?處理事件消息runOnUiThread?{mBinding.eventMag.append("Event:?${event.event}\nData:?${event.data}\n\n")}}is?SSEClient.SSEEvent.Error?->?{//?處理錯誤Log.e("SSE",?"Error:?${event.throwable.message}")}SSEClient.SSEEvent.Closed?->?{//?連接關閉Log.i("SSE",?"Connection?closed")}}}.launchIn(lifecycleScope)?//?自動在生命周期結束時取消}

自定義 SSE?客戶端的優勢
  • ? 與?Kotlin Flow 深度集成,支持協程和生命周期自動管理
  • ??事件結構清晰,便于擴展和維護
  • ? 可自定義重連、錯誤處理等高級功能
  • ??只依賴 OkHttp,包體積小

方式三:手動處理?HTTP 流(適合極簡和特殊場景)

依賴配置?
implementation?'com.squareup.okhttp3:okhttp:4.9.3'

實現方式

?

suspend?fun?sendAiQuestionStream(question:?String):?Flow<String>?=?flow?{var?response:?ResponseBody??=?nulltry?{response?=?ApiManager.apiLogin.sendAiQuestionStream(QuestionRequest(question))val?source?=?response.source()while?(!source.exhausted())?{val?line?=?source.readUtf8Line()??:?breakif?(line.startsWith("data:"))?{val?data?=?line.substring(5).trim()if?(data.isNotEmpty()?&&?data?!=?"[DONE]")?{emit(data)}?else?if?(data?==?"[DONE]")?{break}}}}?finally?{response?.close()}}

手動處理方式的優勢
  • ??代碼極簡,完全自定義
  • ? 適合只需處理?data:?字段的場景
  • ??適合特殊業務需求(如 AI?流式響應)

4. 三種方式的對比與適用場景

特性/方式OkHttp?SSE 庫自定義?SSEClient手動處理
依賴OkHttp+SSE庫僅OkHttp僅OkHttp
事件結構標準ListenerKotlin Flow/自定義無結構/自定義
自動重連內置可自定義需手動
代碼復雜度
靈活性一般最高
適合場景標準SSE推送Kotlin項目/自定義極簡/特殊需求

推薦選擇:

  • 標準SSE推送、Java項目:OkHttp SSE庫
  • Kotlin項目、需要流式/自定義事件/重連:自定義SSEClient
  • 極簡、特殊業務、AI流式響應:手動處理

5. 與 WebSocket、HTTP輪詢的對比

特性SSEWebSocketHTTP輪詢
通信方向單向(服務端→客戶端)雙向單向/偽雙向
協議HTTPWebSocketHTTP
實時性最高
實現復雜度
適用場景實時推送、通知聊天、游戲、協作簡單更新
瀏覽器支持原生原生原生

6. 最佳實踐與總結

  • 標準SSE場景:優先用 OkHttp SSE?庫,省心省力。
  • Kotlin/協程/流式場景:自定義 SSEClient,靈活擴展,易于維護。
  • 特殊/極簡需求:手動處理,代碼最少,完全自控。
  • AI流式響應:推薦手動處理,便于處理特殊標記和自定義邏輯。

你的項目已經靈活結合了自定義 SSEClient 和手動處理兩種方式,既保證了結構化、可維護性,又能滿足特殊業務需求,是非常現代且實用的做法。


結論:

OkHttp SSE 在實際開發中有多種實現方式。你可以根據項目需求選擇標準庫、自定義客戶端或手動處理,三者各有優劣。對于現代 Kotlin 項目,推薦自定義 SSEClient 結合 Flow,既靈活又易于集成和維護。對于極簡或特殊場景,手動處理同樣高效可靠。

你的實現方式,正是當前最佳實踐之一!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/914490.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/914490.shtml
英文地址,請注明出處:http://en.pswp.cn/news/914490.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

聚寬sql數據庫傳遞

自建數據庫從聚寬到Q-MT自動化交易實戰 從接觸聚寬以來一直都是手動跟單&#xff0c;在網上看到許多大佬的自動交易文章&#xff0c;心里也不禁十分癢癢。百說不如一練&#xff0c;千講不如實干。經過一番努力&#xff0c;終于成功實盤了&#xff0c;效果還可以&#xff0c;幾…

es里為什么node和shard不是一對一的關系

提問&#xff1a; 既然多個shard會被分配到同一個node上&#xff0c;那么為什么不把多個shard合并成一個然后存在當前node上呢&#xff0c;簡而言之也就是讓node和shard形成一對一的關系呢 &#xff1f;非常好的問題&#xff0c;這正是理解Elasticsearch分片&#xff08;shard…

淺談npm,cnpm,pnpm,npx,nvm,yarn之間的區別

首先做一個基本的分類 名稱描述npm,cnpm,yarn,pnpm都是Javascript包管理器nvm是Node.js版本控制器npx命令行工具 I.npm,cnpm,yarn,pnpm npm (Node Package Manager) npm是Node.js默認的包管理器&#xff0c;隨Node.js的安裝會一起安裝。使用npm可以安裝&#xff0c;發布&…

滑動窗口-76.最小覆蓋子串-力扣(LeetCode)

一、題目解析1.不符合要求則返回空串("")2.子串中重復字符的數量要不少于t中該字符的數量二、算法原理解法1&#xff1a;暴力枚舉哈希表這里的暴力枚舉也可以優化&#xff0c;即在包含t中元素處枚舉&#xff0c;如在A、B和C處開始枚舉&#xff0c;減少不必要的枚舉 解…

從零構建搜索引擎 build demo search engine from scratch

從零構建搜索引擎 build demo search engine from scratch 我們每天都會使用搜索引擎&#xff1a;打開google等搜索引擎&#xff0c;輸入關鍵詞&#xff0c;檢索出結果&#xff0c;這是一次搜索&#xff1b;當打開歷史記錄旁邊的&#x1f50d;按鈕&#xff0c;輸入關鍵詞&#…

pytorch小記(二十九):深入解析 PyTorch 中的 `torch.clip`(及其別名 `torch.clamp`)

pytorch小記&#xff08;二十九&#xff09;&#xff1a;深入解析 PyTorch 中的 torch.clip&#xff08;及其別名 torch.clamp&#xff09;深入解析 PyTorch 中的 torch.clip&#xff08;及其別名 torch.clamp&#xff09;一、函數簽名二、簡單示例三、廣播支持四、與 Autograd…

快速分頁wpf

/*沒有在xaml設置上下文window.context是因為 命名空間一直對應不上 所以在xaml.cs 里面綁定*/ <Window x:Class"DataGrid.views.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft…

如何徹底禁用 Chrome 自動更新

如何徹底禁用 Chrome 自動更新 隨著谷歌將 Chrome 瀏覽器版本升級至 138&#xff0c;它即將徹底拋棄對 Manifest V2 擴展的支持。許多用戶希望將瀏覽器版本鎖定在 138&#xff0c;以繼續使用 uBlock Origin、Tampermonkey 等常用擴展。 本文總結了四種有效方法&#xff0c;幫助…

流批一體的“奧卡姆剃刀”:Apache Cloudberry 增量物化視圖應用解析

引言&#xff1a;流批一體&#xff0c;理想與現實的鴻溝 在數據驅動的今天&#xff0c;“實時”二字仿佛擁有魔力&#xff0c;驅使著無數企業投身于流批一體架構的建設浪潮中。我們渴望實時洞察業務變化&#xff0c;實時響應用戶需求。以 Apache Flink 為代表的流處理引擎&…

C# 入門教程(三):詳解字段、屬性、索引器及各類參數與擴展方法

文章目錄一、字段、屬性、索引器、常量1.字段2.屬性2.1 什么是屬性2.2 屬性的聲明2.3 屬性與字段的關系3 索引器4. 常量二、傳值 輸出 引用 數組 具名 可選參數&#xff0c;擴展方法2.1 傳值參數2.1.1 值類型 傳參2.1.2 引用類型 傳參2.2 引用參數2.2.1 引用參數-值類型 傳參2.…

《美術教育研究》是什么級別的期刊?是正規期刊嗎?能評職稱嗎?

?問題解答&#xff1a;問&#xff1a;《美術教育研究》是不是核心期刊&#xff1f;答&#xff1a;不是&#xff0c;是知網收錄的第一批認定學術期刊。問&#xff1a;《美術教育研究》級別&#xff1f;答&#xff1a;省級。主管單位&#xff1a; 安徽出版集團有限責任公司 主辦…

每日算法刷題Day47:7.13:leetcode 復習完滑動窗口一章,用時2h30min

思考: 遇到子數組/子字符串可以考慮能不能用滑動窗口&#xff0c; 定長:逆向思維,答案不定 最大長度/最小長度:一般求長度 越長越合法/越短越合法/恰好:一般求數量 主要思考窗口條件成立&#xff0c; 判斷條件是符合窗口條件(最小長度/越長越合法還是不符合(最大長度/越短越合法…

電流驅動和電壓驅動的區別

理解電流驅動和電壓驅動的區別對電路設計至關重要&#xff0c;尤其在高速、高抗噪要求的場景&#xff08;如LVDS&#xff09;。以下是兩者的核心對比&#xff1a;一、電壓驅動 (Voltage Drive) 核心原理&#xff1a; 驅動器輸出一個受控的電壓&#xff08;與負載阻抗無關&#…

宿舍電費查詢——以ZUA為例

宿舍電費查詢——以ZUA為例0. 安裝抓包環境手機端桌面端1. 登錄1.1 開啟抓包后進入繳費頁面&#xff1a;1.2 分析請求1.3 編寫登錄代碼2. 獲取樓棟及房間ID2.1 獲取樓棟ID2.2 編寫獲取樓棟ID代碼2.3 獲取房間ID2.4 編寫獲取房間ID代碼3. 獲取剩余電費&#xff1a;3.1 選擇房間號…

vue中計算屬性的介紹

Vue.js 中的計算屬性是基于它的響應式系統來實現的&#xff0c;它可以根據 Vue 實例的數據狀態來動態計算出新的屬性值。在 Vue 組件中&#xff0c;計算屬性常用于對數據進行處理和轉換&#xff0c;以及動態生成一些需要的數據。一、使用方式1.定義計算屬性&#xff1a; 在Vue組…

MFC UI控件CheckBox從專家到小白

文章目錄CheckBox勾選框控件控件與變量綁定控件點擊消息映射互斥CheckBox勾選框控件 控件與變量綁定 方案一&#xff1a; BOOL m_bEnable1; BOOL m_bEnable2; void A::DoDataExchange(CDataExchange* pDX) {DDX_Check(pDX, IDC_CK_1, m_bEnable1);DDX_Check(pDX, IDC_CK_2, …

阿爾卡特ACT 250 ATP 150 AND ATP 400 分子泵控制器TURBOMOLECULAR PUMP CONTROLLER ALCATEL

阿爾卡特ACT 250 ATP 150 AND ATP 400 分子泵控制器TURBOMOLECULAR PUMP CONTROLLER ALCATEL

python的小學課外綜合管理系統

前端開發框架:vue.js 數據庫 mysql 版本不限 后端語言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 數據庫工具&#xff1a;Navicat/SQLyog等都可以 摘要 隨著…

實用技巧 Excel 與 XML互轉

一 概述 在android多語言適配中&#xff0c;可能提供的是excel格式的多語言翻譯&#xff0c;而且翻譯數量非常龐大。那手動一個一個往xml里面添加效率非常低&#xff0c;這時候就需要把excel快速轉為android可以直接用的資源文件string.xml二 轉換流程2.1 第一步任意文件夾或者…

云原生技術與應用-Containerd容器技術詳解

目錄 一.Containerd概述 1.什么是containerd 2.Containerd的起源與背景 二.Containerd架構 1.Containerd架構概述 2.核心組件解析 三.安裝配置Containerd 1.安裝Containerd 2.配置Containerd 四.Containerd基本操作 1.鏡像類操作 2.容器類操作 3.任務類操作 4.其他操作 一.…