使用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
相关产品推荐
相关产品推荐

