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

