下午好🌅🌅🌅
本答案参考通义千问
你遇到的 java.net.ProtocolException: unexpected end of stream 是一个常见的网络问题,尤其是在使用 OkHttp 与后端服务器(如 gunicorn + Flask)进行频繁请求时。这个问题通常表示 客户端在等待服务器响应时,连接被意外关闭。
✅ 一、问题分析
1. 问题可能出在哪里?
| 位置 | 可能原因 |
|------|----------|
| 客户端(Android + OkHttp) | 频繁请求导致连接复用或超时;未正确处理响应体;没有正确关闭连接;配置不当(如协议、keep-alive 等)。 |
| 服务端(gunicorn + Flask) | worker 超时;没有正确处理 keep-alive;没有正确读取请求体;没有正确返回响应;worker 数量不足或配置不当。 |
✅ 二、更可能是哪一方的问题?
结论:更可能是服务端的问题,但客户端也需要配合调整。
- 浏览器访问正常:说明接口本身是正确的。
- App 偶发报错:说明连接可能在某些情况下被服务器主动关闭,或者客户端没有正确处理响应。
- OkHttp 的默认行为:如果服务器提前关闭连接,OkHttp 会抛出
unexpected end of stream 异常。
✅ 三、解决方案
1. 服务端优化(gunicorn + Flask)
(1) 增加 gunicorn 的超时时间
gunicorn 默认有 --timeout 参数(默认 30 秒),如果你的请求需要超过这个时间,服务器可能会断开连接。
gunicorn -w 4 -b 0.0.0.0:8000 --timeout 60 your_app:app
解释:将超时时间设置为 60 秒,避免因为请求处理时间过长而被服务器中断。
(2) 增加 worker 数量
如果你的服务器负载较高,可以增加 worker 数量:
gunicorn -w 8 -b 0.0.0.0:8000 your_app:app
(3) 启用 keep-alive 支持
确保 gunicorn 和 Flask 正确支持 HTTP Keep-Alive:
# 在 Flask 应用中添加以下内容
from flask import Flask
import gunicorn
app = Flask(__name__)
@app.before_first_request
def set_keepalive():
# 设置 Flask 的连接保持
app.wsgi_app = gunicorn.app.base.WSGIApplication(app.wsgi_app)
注意:gunicorn 本身支持 keep-alive,但需要确认是否启用。
2. 客户端优化(OkHttp)
(1) 禁用 keep-alive(强制每次新建连接)
虽然效率低,但可以避免因连接复用导致的异常:
val request = Request.Builder()
.url("http://xxx.xxx.xxx.xxx:8000/status")
.get()
.header("Connection", "close") // 强制关闭连接
.build()
建议:如果你的请求频率高,不推荐长期使用此方式。
(2) 使用 connectionPool 时,合理设置参数
val connectionPool = ConnectionPool(5, 1, TimeUnit.MINUTES)
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.connectionPool(connectionPool)
.protocols(listOf(Protocol.HTTP_1_1))
.pingInterval(30, TimeUnit.SECONDS)
.build()
解释:使用合理的连接池,避免过多连接堆积。
(3) 确保响应体被完全读取
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw Exception("Unexpected code $response")
val body = response.body?.string() ?: return "fail"
val json = JSONObject(body)
return json.getString("status")
}
注意:确保 body.string() 被调用,否则服务器可能认为请求未完成,提前关闭连接。
(4) 添加重试逻辑(可选)
你可以使用 OkHttp 的 RetryInterceptor 或手动实现重试机制,例如:
class RetryInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var response: Response? = null
for (i in 0..2) { // 最多重试 2 次
try {
response = chain.proceed(chain.request())
if (response.isSuccessful) return response
} catch (e: IOException) {
Log.e("Retry", "Request failed, retrying...")
}
}
return response ?: throw IOException("Failed to get response after retries")
}
}
使用方法:
val client = OkHttpClient.Builder()
.addInterceptor(RetryInterceptor())
...
.build()
✅ 四、总结
| 问题类型 | 推荐做法 |
|----------|----------|
| 服务端问题 | 增加 gunicorn 的 timeout 和 worker 数量,确保 keep-alive 正确工作。 |
| 客户端问题 | 确保响应体被完全读取,适当禁用 keep-alive 或使用合理的连接池。 |
| 通用建议 | 在客户端添加重试逻辑,提升容错能力。 |
✅ 五、最终推荐配置(OkHttp)
val connectionPool = ConnectionPool(5, 1, TimeUnit.MINUTES)
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.connectionPool(connectionPool)
.protocols(listOf(Protocol.HTTP_1_1))
.pingInterval(30, TimeUnit.SECONDS)
.build()
重点:确保 response.body?.string() 被调用,防止服务器提前关闭连接。
如有更多细节(如服务器日志、请求体内容等),可以进一步排查问题根源。希望这些方案能帮助你解决 unexpected end of stream 问题!