Ktor服务器大文件传输字节数精准日志实现方案咨询
Ktor大文件传输字节数精准统计方案
你之前的方案依赖ByteReadChannel.totalBytesRead,问题出在这个值包含了通道内部缓冲的字节,未必是实际发送到客户端的量——尤其是反向代理场景下,代理会缓存部分数据,通道提前关闭但代理还没把所有数据发完,导致统计数值不准。以下是两种精准的实现方式:
方案一:自定义计数ByteReadChannel(通用场景)
通过包装原始的ByteReadChannel,在每次实际读取(即发送)数据时累加计数,确保统计的是真正发出去的字节:
class CountingByteReadChannel( private val delegate: ByteReadChannel, private val onCountUpdated: (Long) -> Unit ) : ByteReadChannel by delegate { private var totalBytesSent = 0L override suspend fun read(dst: ByteBuffer): Int { val bytesRead = delegate.read(dst) if (bytesRead > 0) { totalBytesSent += bytesRead onCountUpdated(totalBytesSent) } return bytesRead } override suspend fun readFully(dst: ByteBuffer): Int { val bytesRead = delegate.readFully(dst) if (bytesRead > 0) { totalBytesSent += bytesRead onCountUpdated(totalBytesSent) } return bytesRead } fun getTotalBytesSent(): Long = totalBytesSent }
在路由中使用,等待响应完成后记录最终数值:
route("/files") { get("/{fileName}") { val fileName = call.parameters["fileName"] ?: return@get call.respond(HttpStatusCode.BadRequest) val file = File("/path/to/your/files/$fileName") if (!file.exists()) return@get call.respond(HttpStatusCode.NotFound) var totalBytesSent = 0L val countingChannel = CountingByteReadChannel(file.readChannel()) { count -> totalBytesSent = count // 可选:实时打印传输进度 // println("已发送 $count 字节") } call.respond(object : OutgoingContent.ReadChannelContent() { override val channel: ByteReadChannel = countingChannel override val contentType: ContentType? = ContentType.Application.OctetStream override val contentLength: Long? = file.length() }) // 响应发送完成后执行统计逻辑 call.response.complete().invokeOnCompletion { println("请求 ${call.request.uri} 累计发送 $totalBytesSent 字节") addTraffic(device, totalBytesSent) } } }
方案二:扩展LocalFileContent(针对本地文件场景)
如果你习惯用LocalFileContent,可以直接扩展它,重写readFrom方法来统计实际写入响应通道的字节:
class CountingLocalFileContent( file: File, private val onComplete: (Long) -> Unit ) : LocalFileContent(file) { private var totalBytesSent = 0L override suspend fun readFrom(channel: ByteWriteChannel) { val countingSource = file.readChannel().let { source -> object : ByteReadChannel by source { override suspend fun read(dst: ByteBuffer): Int { val bytesRead = super.read(dst) if (bytesRead > 0) { totalBytesSent += bytesRead } return bytesRead } } } channel.writeFully(countingSource) onComplete(totalBytesSent) } }
路由中使用示例:
get("/{fileName}") { val fileName = call.parameters["fileName"] ?: return@get call.respond(HttpStatusCode.BadRequest) val file = File("/path/to/your/files/$fileName") if (!file.exists()) return@get call.respond(HttpStatusCode.NotFound) call.respond(CountingLocalFileContent(file) { totalBytes -> println("累计发送 $totalBytes 字节") addTraffic(device, totalBytes) }) }
这两种方案都是在数据实际写入响应通道时计数,完全不受缓冲或反向代理缓存的影响,统计结果精准。
内容的提问来源于stack exchange,提问作者Vodes
相关产品推荐
相关产品推荐

