Android使用StompClient实现WebSocket连接即断并抛EOFException求助
Android中StompClient连接WebSocket后立即断开并抛出EOFException问题排查
问题背景
服务器拥有CA签名的证书,首次在Android中使用StompClient实现WebSocket功能,连接建立后立即断开,抛出如下异常:
java.io.EOFException at okio.RealBufferedSource.require(RealBufferedSource.kt:199) at okio.RealBufferedSource.readByte(RealBufferedSource.kt:209) at okhttp3.internal.ws.WebSocketReader.readHeader(WebSocketReader.kt:119) at okhttp3.internal.ws.WebSocketReader.processNextFrame(WebSocketReader.kt:102) at okhttp3.internal.ws.RealWebSocket.loopReader(RealWebSocket.kt:293) at okhttp3.internal.ws.RealWebSocket$connect$1.onResponse(RealWebSocket.kt:195) at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) at java.lang.Thread.run(Thread.java:920)
相关代码与依赖
依赖
com.github.NaikSoftware:StompProtocolAndroid:1.6.6
业务逻辑代码
private fun startWebSocketConnection() { lifecycleScope.launch( defaultDispatcher) { compositeDisposable.clear() // Web Socket // val url: String = "https://url.ca/guard-ws" val rawClient = OkHttpClient.Builder().build() val rawRequest = Request.Builder() .url(url) .addHeader("Authorization", "Bearer ${tokenInterceptor.token}") .build() rawClient.newWebSocket(rawRequest, object : WebSocketListener() { override fun onOpen(ws: WebSocket, response: Response) { Log.d("MyTag", "✅ onOpen: ${response.code}") ws.send("CONNECT\naccept-version:1.1,1.2\nheart-beat:0,0\n\n\u0000") } override fun onMessage(ws: WebSocket, text: String) { Log.d("MyTag", "📨 onMessage: $text") } override fun onClosing(ws: WebSocket, code: Int, reason: String) { Log.d("MyTag", "🔴 onClosing: $code / $reason") } override fun onFailure(ws: WebSocket, t: Throwable, response: Response?) { Log.e("MyTag", "❌ onFailure: ${t.message}", t) } }) webSocketVM.stompClient = Stomp .over( Stomp.ConnectionProvider.OKHTTP, url, null, webSocketVM.getOkHttpClient() ) .withServerHeartbeat(30000) .withClientHeartbeat(30000) val list = listOf(StompHeader("Authorization", "Bearer ${tokenInterceptor.token}")) webSocketVM.stompClient?.let { it.connect( list) val main: Disposable = it.lifecycle() .subscribe({ lifecycleEvent -> when (lifecycleEvent.type) { Type.OPENED -> { Log.d("MyTag", "intentando con: $url") Log.d("MyTag", "open") } Type.ERROR -> { Log.d("MyTag", "Error: ${lifecycleEvent.exception}") } Type.CLOSED -> { Log.d("MyTag", "closed") } Type.FAILED_SERVER_HEARTBEAT -> { Log.d("MyTag", "failed server heartbeat") } else -> {} } }){ error: Throwable? -> Log.e( "MyTag", "error: ${error?.message} : Connection")} compositeDisposable.add( main) } } }
WebSocket所用OkHttpClient配置
fun getOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .addNetworkInterceptor( HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.HEADERS } ).build() }
问题排查与解决方案
核心问题分析
- 重复WebSocket连接:代码中同时创建了两个独立的WebSocket连接(原始OkHttpClient连接 + StompClient连接),服务器端会因连接冲突直接断开连接,这是引发EOFException的主要原因。
- 心跳配置不匹配:原始连接发送
heart-beat:0,0,但StompClient设置30秒心跳间隔,两者不一致导致服务器无法识别心跳规则,触发断开。 - OkHttpClient缺少超时配置:未设置连接、读写超时,可能导致连接过程中因超时被强制断开。
- 日志级别不足:当前仅打印Headers,无法查看完整Stomp帧交互细节,难以定位协议层面问题。
具体修复步骤
- 删除重复的原始WebSocket连接代码:保留StompClient连接逻辑即可,StompProtocolAndroid会自动处理底层WebSocket握手和Stomp帧发送。
- 统一心跳配置:如果服务器要求禁用心跳(
heart-beat:0,0),修改StompClient配置:webSocketVM.stompClient = Stomp .over( Stomp.ConnectionProvider.OKHTTP, url, null, webSocketVM.getOkHttpClient() ) .withServerHeartbeat(0) .withClientHeartbeat(0) - 增强OkHttpClient配置:添加超时设置并提升日志级别:
fun getOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .addNetworkInterceptor( HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY // 打印完整请求响应,包括Stomp帧 } ) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .build() } - 验证Authorization头传递:通过日志确认StompHeader中的Bearer Token有效且格式正确。
- 添加订阅逻辑:连接成功后添加Stomp订阅,避免服务器因无活跃订阅断开连接:
// 在Type.OPENED分支中添加订阅 Type.OPENED -> { Log.d("MyTag", "intentando con: $url") Log.d("MyTag", "open") // 替换为实际需要订阅的主题 val subscription = it.topic("/your/topic") .subscribe { message -> Log.d("MyTag", "Received message: ${message.payload}") } compositeDisposable.add(subscription) }
验证方法
查看HttpLoggingInterceptor输出的日志,确认:
- WebSocket握手响应状态码为101 Switching Protocols
- Stomp CONNECT帧发送后,收到服务器返回的CONNECTED帧
- 无异常断开帧或错误响应
内容的提问来源于stack exchange,提问作者Cristian Alexis Torres Zavala
相关产品推荐
相关产品推荐

