You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OkHttp WebSocket连接后立即失败的技术求助

问题描述

我正尝试在Android应用中实现WebSocket功能,使用OkHttp WebSocket、Apache代理与Node.js服务器。目前已能建立连接,但连接后几秒内就会触发OkHttp的onFailure回调,Throwable无原因和消息,Response为null,且未触发onClose或onClosing回调。

相关代码与配置

SocketConnect.kt

class SocketConnect
  private val logger by taggedLogger("Call:SocketConnect")
  private val client = OkHttpClient()
  private val request = Request.Builder().url("https://myserverhere").build()
  private val ws = client.newWebSocket(request, SocketConnectListener())

  private inner class SocketConnectListener: WebSocketListener() {
    override fun onOpen(webSocket: WebSocket, response: Response) {
      logger.i { "SocketConnect onOpen" }
      super.onOpen(webSocket, response)
    }
    override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
      logger.i { "SocketConnect onClosed, code: $code, reason: $reason" }
    }
    override fun onMessage(webSocket: WebSocket, text: String) {
      logger.i { "SocketConnect onMessage $text" }
    }
    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response) {
      logger.i { "SocketConnect onFailure: cause ${t.cause}, message: ${t.message}" }
      logger.i { "SocketConnect onFailure: response $response" }
    }
    override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
      logger.i { "SocketConnect onClosing: code: $code, reason: $reason" }
    }
  }
}

Apache配置(转发升级请求至Node.js服务器)

<VirtualHost *:443>
  //...server name and whatnot
  RewriteCond %{HTTP:Upgrade} websocket [NC]
  RewriteCond %{HTTP:Connection} upgrade [NC]
  RewriteRule ^/?(.*) "ws://127.0.0.1:3005/$1" [P,L]

  RewriteCond %{HTTPS:Upgrade} websocket [NC]
  RewriteCond %{HTTPS:Connection} upgrade [NC]
  RewriteRule ^/?(.*) "ws://127.0.0.1:3005/$1" [P,L]
  
  //...ssl stuff
</VirtualHost>

Node.js服务器代码

const https = require('https')
const express = require('express')
const bodyParser = require('body-parser')
const webSocket = require('ws')
const port = 3005
const wss = new webSocket.Server({noServer: true})

const app = express()
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true})
app.get('/', (req, res) => {
  res.send("Server is up and running")
})

const server = app.listen(port, (err) => {
  if(error) return console.log(`Error: ${error}`)
  console.log(`Server running on port ${port}`)
})

server.on('upgrade', (request, socket, head) => {
  console.log('upgrade request received')
  wss.handleUpgrade(request, socket, head, (ws) => {
    wss.emit('connection', ws, request)
  })
})

wss.on('connection', (ws, req) => {
  console.log("connection event happened.")
  ws.on('message', (message) => {
    console.log("message received")
    console.log(message)
  })
  ws.on('close', () => {
    console.log("close event happened")
  })
})

日志信息

服务器日志

upgrade request received
connection event happened
close event happened

Android Logcat日志

(OkHttp https://myservernamehere/...:345) SocketConnect onOpen
I  Compiler allocated 4666KB to compile void android.view.ViewRootImpl.performTraversals()
E  (OkHttp https://myservernamehere/...:345) SocketConnect onFailure: cause null, message: null
(OkHttp https://myservernamehere/...:345) SocketConnect onFailure: response null

额外异常信息

java.io.EOFException 2025-03-03 13:40:41.128 30775-30817 System.err
com.mine.webrtc.sample W at okio.RealBufferedSource.require(RealBufferedSource.kt:202) 2025-03-03 13:40:41.128 30775-30817 System.err
com.mine.webrtc.sample W at okio.RealBufferedSource.readByte(RealBufferedSource.kt:212) 2025-03-03 13:40:41.128 30775-30817 System.err
com.mine.webrtc.sample W at okhttp3.internal.ws.WebSocketReader.readHeader(WebSocketReader.kt:119) 2025-03-03 13:40:41.128 30775-30817 System.err
com.mine.webrtc.sample W at okhttp3.internal.ws.WebSocketReader.processNextFrame(WebSocketReader.kt:102) 2025-03-03 13:40:41.128 30775-30817 System.err
com.mine.webrtc.sample W at okhttp3.internal.ws.RealWebSocket.loopReader(RealWebSocket.kt:293) 2025-03-03 13:40:41.128 30775-30817 System.err
com.mine.webrtc.sample W at okhttp3.internal.ws.RealWebSocket$connect$1.onResponse(RealWebSocket.kt:195) 2025-03-03 13:40:41.128 30775-30817 System.err
com.mine.webrtc.sample W at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519) 2025-03-03 13:40:41.129 30775-30817 System.err
com.mine.webrtc.sample W at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) 2025-03-03 13:40:41.129 30775-30817 System.err
com.mine.webrtc.sample W at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) 2025-03-03 13:40:41.129 30775-30817 System.err
com.mine.webrtc.sample W at java.lang.Thread.run(Thread.java:1012)

---

## 解决方案

### 1. 修复Apache代理配置错误
Apache配置中`%{HTTPS:Upgrade}`是无效的HTTP头字段,HTTP请求头不会带`HTTPS:`前缀,删除这组无效规则,保留正确的WebSocket升级判断:
```apache
<VirtualHost *:443>
  //...server name and whatnot
  RewriteCond %{HTTP:Upgrade} websocket [NC]
  RewriteCond %{HTTP:Connection} upgrade [NC]
  RewriteRule ^/?(.*) "ws://127.0.0.1:3005/$1" [P,L]
  
  //...ssl stuff
</VirtualHost>

同时必须启用Apache的mod_proxy和mod_proxy_wstunnel模块,这是WebSocket代理正常工作的前提,以Ubuntu为例可通过以下命令启用:

a2enmod proxy
a2enmod proxy_wstunnel
systemctl restart apache2

2. 修正Node.js代码中的语法错误

代码存在两处语法/逻辑错误,会导致服务器运行异常:

  • 补全bodyParser.urlencoded的闭合括号:
    app.use(bodyParser.urlencoded({extended: true}))
    
  • 修正启动回调的参数判断(err而非error):
    const server = app.listen(port, (err) => {
      if(err) return console.log(`Error: ${err}`)
      console.log(`Server running on port ${port}`)
    })
    

3. 为OkHttp添加心跳与超时配置

EOFException通常是连接因长时间无数据被主动断开,给OkHttpClient添加心跳和超时配置,维持连接活性:

private val client = OkHttpClient.Builder()
    .pingInterval(30, TimeUnit.SECONDS) // 每30秒发送一次WebSocket心跳帧
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(60, TimeUnit.SECONDS)
    .writeTimeout(60, TimeUnit.SECONDS)
    .build()

同时确保SocketConnect实例持有强引用(比如作为全局单例或Activity/Fragment的成员变量),避免被GC回收导致连接意外中断。

4. 增强Node.js服务器的错误日志

在WebSocket连接中添加错误监听,获取服务器端的断开原因,便于排查:

wss.on('connection', (ws, req) => {
  console.log("connection event happened.")
  ws.on('message', (message) => {
    console.log("message received")
    console.log(message)
  })
  ws.on('close', (code, reason) => {
    console.log(`close event happened, code: ${code}, reason: ${reason}`)
  })
  ws.on('error', (error) => {
    console.log(`websocket error: ${error.stack}`)
  })
})

5. 验证SSL证书兼容性

如果运行在Android 7.0以下设备,需确保服务器SSL证书受系统信任,或在测试环境中为OkHttp添加自定义证书信任(仅用于测试,生产环境禁用):

// 测试环境临时信任所有证书(生产环境禁止)
private val client = OkHttpClient.Builder()
    .sslSocketFactory(SSLSocketFactory(), object : X509TrustManager {
        override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
        override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
        override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
    })
    .hostnameVerifier { _, _ -> true }
    // 加上之前的心跳和超时配置
    .build()

内容的提问来源于stack exchange,提问作者Neglected Sanity

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:14:55