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

为何InputStreamReader.read()会永久阻塞 安卓ServerSocket服务读取问题

问题根因

InputStream.read() 为默认阻塞方法,仅当连接主动断开、读到流末尾才会返回-1;如果客户端出现异常断网、进程闪退未发送FIN关闭包的情况,服务端会一直等待数据,进入永久阻塞状态。

解决方案
    1. 给Socket设置读取超时(核心修复)
      对客户端连接Socket设置SO_TIMEOUT参数,指定read()方法的最大等待时长,超时后会抛出SocketTimeoutException,你可以捕获该异常后主动释放无效连接。
      设置位置可以在accept拿到Socket后,或者ServerThread初始化阶段添加:
val socket: Socket = serverSocket!!.accept()
socket.reuseAddress = true
// 新增:设置读取超时30秒,单位毫秒
socket.soTimeout = 30 * 1000
ServerThread(socket).start()

注意该参数仅限制read()的等待时长,不会影响正常的连接传输。

    1. 开启TCP存活检测
      开启Socket的KeepAlive选项,系统底层会自动发送探测包校验客户端是否存活,如果连续探测无响应会自动断开连接,避免长期挂死:
socket.keepAlive = true

如果对断链感知灵敏度要求高,可以自己实现应用层心跳:客户端每隔10-30秒发送一个约定的心跳包,服务端如果连续2-3个周期没有收到任何数据,主动断开连接即可。

    1. 修复流解析逻辑
      当前你每读到一次数据就直接转字符串处理的逻辑不符合TCP流的特性,TCP是无边界的字节流,单次read返回的字节可能是半个消息、也可能是多个粘在一起的消息,会导致解析错误、或等待完整消息的过程中假死。
      建议约定应用层协议,比如固定前4个字节为消息长度,读取到对应长度的字节后再做业务处理,避免半包/粘包问题。
    1. 补充资源释放逻辑
      现有代码不管正常结束还是异常捕获,都没有关闭Socket和对应的输入输出流,长期运行会出现句柄泄漏、内存泄漏的问题,在ServerThread的run方法中添加finally块做资源释放:
override fun run() {
    try {
        // 原有业务逻辑
    } catch (timeoutEx: SocketTimeoutException) {
        // 超时可以判断为客户端无响应,主动关闭
        _log("客户端读取超时,断开无效连接")
    } catch (ex: Exception) {
        _log("Cannot read the message. Please try again")
        val spReq = Error()
        spReq.sendErrorMsg(writer, "Cannot read the message. Please try again")
    } finally {
        // 新增:统一释放资源
        runCatching { writer.close() }
        runCatching { client.getInputStream().close() }
        runCatching { client.close() }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 16:06:13