首页 > 解决方案 > 在没有设置 chunkedStreamingMode 的情况下,在上传过程中使用 HttpURLConnection 时如何测量上传速度?

问题描述

我正在尝试测量我正在创建的客户端应用程序的上传速度。与我对下载速度的另一个问题类似,我正在尝试在上传过程中这样做,并在 GUI 上更新平均上传速度。

我尝试测量数据写入 HttpsURLConnection 的输出流的速度,但最终我意识到 HttpsURLConnection 默认情况下会缓冲一些数据,并且在方法getResponseCode()或被getInputStream()调用之前它不会发送它。

调用和设置setChunkedStreamingMode(int)有助于通过下面的示例更准确地测量速度,但不幸的是服务器应用程序不支持这一点,所以我试图找出是否有不同的方式或方法。

例子:

import com.sun.net.httpserver.*
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.net.HttpURLConnection
import java.net.InetSocketAddress
import java.net.URL
import java.util.*
import javax.net.ssl.HttpsURLConnection
import kotlin.system.measureNanoTime
import kotlin.system.measureTimeMillis

fun main(args: Array<String>) {

    val PORT = 49831

    val serverT = Thread {
        val s = HttpServer.create()
        s.bind(InetSocketAddress(PORT),0)
        s.executor = null
        s.createContext("/") {httpExchange ->
            val request = httpExchange.requestBody.readBytes()
            val response = "This is the response".toByteArray()
            val headers = httpExchange.getResponseHeaders()
            headers.add("Access-Control-Allow-Methods","OPTIONS,GET,POST")
            headers.add("Access-Control-Allow-Origin","*")
            httpExchange.sendResponseHeaders(200, response.size.toLong())
            val os = httpExchange.responseBody
            os.write(response)
            os.close()
            println("Server: Finished sending response.")
            s.stop(0)
        }
        s.start()
    }

    serverT.start()

    val client = TestHttpClient("127.0.0.1", PORT)
    val byteArr = ByteArray(1024*1024*100) {'a'.toByte()}
    client.sendRequestSynchronous(byteArr.inputStream(), requestMethod = "POST", measureSpeed = {it ->
        println("Avg speed: ${it.getOverallTransferRate()} MB/s")
    })
}

class TestHttpClient (val serverHostname: String, val serverPort: Int) {

    /**
     * Sends a request to the server with the content of the data InputStream, at the specified uri. You can specify the request method and add any headers on the request by using the respective parameters.
     * @param data The data input stream that will be sent to the server
     * @param uri The URI that the request will be sent to
     * @param requestMethod The type of the HTTP request
     * @param headers The headers of the request
     * @param measureSpeed A function which is being called to update the statistics of this request
     */
    fun sendRequestSynchronous(data: InputStream, uri: String = "/", requestMethod: String = "POST", headers: Map<String, String> = HashMap(), measureSpeed : (NetworkStatistics) -> Unit = { }) : Triple<Int, MutableMap<String, MutableList<String>>, InputStream> {
        val path = "http://$serverHostname:$serverPort$uri"
        val connection = if (path.startsWith("https://")) (URL(path).openConnection() as HttpsURLConnection) else URL(path).openConnection() as HttpURLConnection

        connection.doInput = true
        connection.doOutput = true
        connection.requestMethod = requestMethod
        //connection.setChunkedStreamingMode(64*1024) // Uncomment this line to have a better measurement of the speed
        headers.forEach {
            connection.addRequestProperty(it.key, it.value)
        }
        connection.connect()
        println("Client connected!")

        val totalAmountBytesToSend = data.available() // amount of bytes of the whole request
        val statistics = NetworkStatistics(totalAmountBytesToSend.toLong())
        val timeMeas = measureTimeMillis {
            copy(data, connection.outputStream, statistics, {it -> measureSpeed(it)} )
        }

        println("Time measured for upload: $timeMeas")

        val timeMeas2 = measureTimeMillis {
            connection.outputStream.flush()
        }

        println("Time measured for flushing output stream: $timeMeas2")

        var code = 200
        val timeMeas3 = measureTimeMillis {
            code = connection.responseCode
        }

        println("Time measured for reading the connection response code: $timeMeas3")
        return Triple(code, connection.headerFields, connection.inputStream)
    }

    private val BUFFER_SIZE = 8192

    @Throws(IOException::class)
    private fun copy(source: InputStream, sink: OutputStream, networkStatistics: NetworkStatistics, measureSpeed : (NetworkStatistics) -> Unit = { }): Long {
        var nread = 0L
        val buf = ByteArray(BUFFER_SIZE)
        var n: Int
        n = source.read(buf)
        while (n > 0) {
            val nano = measureNanoTime {
                sink.write(buf, 0, n)
                nread += n.toLong()
                n = source.read(buf)
            }
            networkStatistics.dataSent = nread
            networkStatistics.totalSendingTime += nano
            networkStatistics.lastPacketBytes = n.toLong()
            networkStatistics.lastPacketTime = nano
            measureSpeed(networkStatistics)
        }
        return nread
    }
}

class NetworkStatistics(var totalAmountData: Long, var dataSent: Long = 0, var totalSendingTime: Long = 0, var lastPacketBytes: Long = 0, var lastPacketTime: Long = 0) {

    private fun convertNanoToSeconds(nano: Long) : Double {
        return nano.toDouble() / 1000000000
    }

    private fun convertBytesToMegabytes(bytes: Long) : Double {
        return bytes.toDouble()/1048576
    }

    private fun convertBytesNanoToMegabytesSeconds(bytes: Long, nano: Long) : Double {
        return ( bytes.toDouble() / nano.toDouble() ) * 953.67431640625
    }

    /**
     * Returns the transfer rate of the last packet in MB per second.
     */
    fun getLastPacketRate() : Double {
        return if (lastPacketTime != 0L) {
            convertBytesToMegabytes(lastPacketBytes) / convertNanoToSeconds(lastPacketTime)
        }
        else
            0.0
    }

    /**
     * Returns the transfer rate that has been measured by this instance in MB per second.
     */
    fun getOverallTransferRate() : Double {
        return if (totalSendingTime != 0L) {
            convertBytesNanoToMegabytesSeconds(dataSent, totalSendingTime)
        }
        else
            0.0
    }

    /**
     * Returns the percent of the transfer that has been completed.
     */
    fun getTransferCompletePercent() : Double {
        return if (totalAmountData != 0L) {
            (dataSent.toDouble() / totalAmountData) * 100
        }
        else
            0.0
    }
}

标签: javaperformancekotlinhttpurlconnection

解决方案


推荐阅读