Как измерить скорость загрузки при использовании HttpURLConnection во время загрузки без установки chunkedStreamingMode? - PullRequest
1 голос
/ 11 июня 2019

Я пытаюсь измерить скорость загрузки создаваемого клиентского приложения.Аналогично на другой вопрос У меня была скорость загрузки, я пытаюсь это сделать, пока идет загрузка, и обновить среднюю скорость загрузки в графическом интерфейсе.

Я пытался измеритьскорость, с которой данные записываются в выходной поток 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
    }
}
...