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

使用Kotlin协程+Jsoup连接URL时遭遇SocketException与BindException求助

问题分析与解决方案

你遇到的SocketException和BindException核心原因是无限制并发的网络请求耗尽了本地系统的TCP端口资源或连接缓冲区:

  • 协程默认会尽可能多的并行执行任务,加上你对每个书籍都发起网络请求,短时间内创建了大量TCP连接
  • Jsoup默认的连接池配置未优化时,会频繁新建连接,而系统可用的临时端口数量有限(一般为几千个)
  • 无限递归的重试逻辑会加剧问题,超时后不断重试会占用更多资源

下面是具体的修复方案:

1. 限制协程并发数

用limitedParallelism直接限制Dispatcher的并行度,或者用Semaphore控制同时运行的任务数,避免一次性发起过多请求:

方式一:使用limitedParallelism

runBlocking { 
    withContext(Dispatchers.IO.limitedParallelism(15)) { // 限制同时运行15个任务
        val updated = booklists.map { booklist ->
            async {
                booklist.forEach { book -> 
                    try {
                        book.sections = ru.parseBook(book)
                    } catch (e: Exception) {
                        println("解析书籍${book.id}失败: ${e.message}")
                    }
                }
                booklist
            }
        }
        updated.awaitAll().forEach { u -> bookRepository.saveAll(u) }
    }
}

方式二:使用Semaphore

runBlocking { 
    withContext(Dispatchers.IO) {
        val semaphore = Semaphore(permits = 15) // 允许15个并发许可
        val updated = booklists.map { booklist ->
            async {
                semaphore.withPermit { // 每次执行前获取许可,用完自动释放
                    booklist.forEach { book -> 
                        try {
                            book.sections = ru.parseBook(book)
                        } catch (e: Exception) {
                            println("解析书籍${book.id}失败: ${e.message}")
                        }
                    }
                    booklist
                }
            }
        }
        updated.awaitAll().forEach { u -> bookRepository.saveAll(u) }
    }
}

2. 优化Jsoup连接池与重试逻辑

复用TCP连接,限制重试次数,避免资源浪费:

// 初始化全局共享连接池,控制最大连接数和超时
private val jsoupPool = ConnectionPool(maxConnections = 15, timeoutMillis = 30000)

fun connect(url: String, maxRetries: Int = 3): Document {
    return try {
        Jsoup.connect(url)
            .connectionPool(jsoupPool) // 使用共享连接池
            .timeout(10000) // 设置单次请求超时时间
            .get()
    } catch (e: SocketTimeoutException) {
        if (maxRetries > 0) {
            println("重试请求 $url,剩余次数: ${maxRetries - 1}")
            connect(url, maxRetries - 1)
        } else {
            throw e // 重试耗尽后抛出异常,避免无限递归
        }
    }
}

3. 可选:调整系统参数(仅当代码优化后仍有问题时)

如果是系统层面的端口限制,可以临时调整:

  • Linux/macOS:修改临时端口范围和TCP回收参数
    sysctl -w net.ipv4.ip_local_port_range="1024 65535"
    sysctl -w net.ipv4.tcp_tw_reuse=1
    
  • Windows:通过注册表调整TCP参数(需管理员权限)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 11:30:55