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

Ktor HttpClient(CIO)连接数超限致请求超时问题求助

问题场景代码

KtorBase2.kt

open class KtorBase2()
{
    private val _httpClient: HttpClient
    val httpClient get() = _httpClient

    init {
        _httpClient = createHttpClient()
    }

    private fun createHttpClient(): HttpClient {
        return HttpClient(CIO) {
            engine {
                // this: CIOEngineConfig
                // I don't know why Request timeout occurs when I try to request beyond the maxConnectionsCount or maxConnectionsPerRoute values.
                maxConnectionsCount = 12 // system default=1000
                endpoint {
                    // this: EndpointConfig
                    maxConnectionsPerRoute = 100 // system default=100
                    pipelineMaxSize = 20 // system default=20
                    keepAliveTime = 5000 // system default=5000
                    connectTimeout = 5000 // system default=5000
                    connectAttempts = 1 // system default=1
                }
            }
        }
    }
}

HomeDataAccesss.kt

suspend fun getTestHtml(ktorBase2: KtorBase2) : String {
    val response: HttpResponse =  ktorBase2.httpClient.get("http://ap2.shinwonsoft.com/test/test.html") {
    }

    return response.body()
}

HomeViewModel.kt

fun reqTestHtml(ktorBase2: KtorBase2, testCountForManyRequest: Int) {
    viewModelScope.launch {
        val str = HomeDataAccess.getTestHtml(ktorBase2)
        Log.d(logTag, "### reqTestHtml::html=$str, testCountForManyRequest=$testCountForManyRequest")
    }
}

HomeFragment.kt

class HomeFragment: Fragment {
    private var _testCountForManyRequest: Int = 0
    private val _testKtorForManyRequest: KtorBase2 = KtorBase2(URLProtocol.HTTP)

    ...

    private fun initViewEventListener() {
        binding.textHome.setOnClickListener {
            (it as TextView).text = "Home fragment: ${++_testCountForManyRequest}"

            _homeViewModel.reqTestHtml(_testKtorForManyRequest, _testCountForManyRequest)
        }
    }
}

控制台日志

19:05:33.635 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=1
19:06:52.385 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=2
19:07:28.888 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=3
19:07:29.653 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=4
19:07:30.231 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=5
19:07:30.726 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=6
19:07:31.266 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=7
19:07:32.122 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=8
19:07:33.303 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=9
19:11:11.393 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=10
19:11:30.733 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=11
19:11:36.508 HE_HomeViewModel         D  ### reqTestHtml::html=test..., testCountForManyRequest=12
19:11:48.195 android.ha...mpl.ranchu  D  threadLoop: entering standby, frames: 2233440
--------- beginning of crash
19:11:50.138 StatusBarIconController  D  ignoring old pipeline callbacks, because the new mobile icons are enabled
19:11:50.167 AndroidRuntime           E  FATAL EXCEPTION: main
    Process: kr.co.heavyequipment, PID: 4914
    io.ktor.client.network.sockets.ConnectTimeoutException: Connect timeout has expired [url=http://ap2.shinwonsoft.com/test/test.html, connect_timeout=unknown ms]
    at io.ktor.client.plugins.HttpTimeoutKt.ConnectTimeoutException(HttpTimeout.kt:213)
    at io.ktor.client.plugins.HttpTimeoutKt.ConnectTimeoutException$default(HttpTimeout.kt:210)
    at io.ktor.client.engine.cio.Endpoint.getTimeoutException(Endpoint.kt:268)
    at io.ktor.client.engine.cio.Endpoint.connect(Endpoint.kt:257)
    at io.ktor.client.engine.cio.Endpoint.access$connect(Endpoint.kt:25)
    at io.ktor.client.engine.cio.Endpoint$connect$1.invokeSuspend(Unknown Source:15)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.internal.ScopeCoroutine.afterResume(Scopes.kt:32)
    at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:102)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
    Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@5f7479, Dispatchers.Main.immediate]
19:11:50.173 DropBoxManagerService    I  add tag=data_app_crash isTagEnabled=true flags=0x2
19:11:50.175 ActivityTaskManager      W    Force finishing activity kr.co.he/.ui.main.MainActivity

问题分析与解决方案

1. 超时原因

CIO的连接池机制是:当请求数超过maxConnectionsCount或maxConnectionsPerRoute时,新请求会进入等待队列(由pipelineMaxSize控制队列长度)。如果队列占满,或者请求等待可用连接的时间超过connectTimeout设定值,就会触发ConnectTimeoutException。

你的代码仅配置了连接超时,未设置请求等待超时,短时间内大量请求会快速占满连接池和队列,后续请求因等待时间过长直接触发超时。

2. 修复方案

(1)添加请求超时配置

在HttpClient中安装HttpTimeout插件,明确设置各类超时参数:

return HttpClient(CIO) {
    install(HttpTimeout) {
        requestTimeoutMillis = 10000 // 整个请求的总超时
        connectTimeoutMillis = 5000 // 连接建立超时
        socketTimeoutMillis = 5000 // 读写数据超时
    }
    engine {
        maxConnectionsCount = 12
        endpoint {
            maxConnectionsPerRoute = 100
            pipelineMaxSize = 20
            keepAliveTime = 5000
            connectTimeout = 5000
            connectAttempts = 1
        }
    }
}

这样请求等待连接超过设定时间时,会抛出明确的超时异常,而非无限阻塞。

(2)合理调整连接池参数

  • 根据业务场景调大maxConnectionsCount和maxConnectionsPerRoute,但不要过度设置;
  • 调大pipelineMaxSize可增加等待队列长度,避免请求直接被拒绝,但队列过长会导致请求等待时间变长。

(3)确保连接正常释放

Ktor会自动管理连接复用,但如果请求出现未处理的异常或阻塞逻辑,可能导致连接无法及时回收到连接池。确保请求逻辑无阻塞,异常被正确捕获处理。

3. 无限调大连接数的影响

会给Android终端带来明显负载:

  • 每个TCP连接会占用内存、文件描述符等系统资源,Android对单APP的文件描述符数量有限制,过多连接会触发OOM或崩溃;
  • 大量并发请求会增加CPU和网络模块负载,导致APP卡顿、耗电增加,甚至被系统判定为异常进程杀死;
  • 服务器端也会限制并发连接数,过多请求可能被拒绝或限流。

建议根据业务需求设置合理值,移动端一般maxConnectionsCount在20-50之间即可应对大部分场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 15:24:52