首页 > 解决方案 > 使用 Files.copy 功能保存数据时,如何提高数据在网络上的传输速度?

问题描述

我有一个客户端应用程序将文件发送到服务器应用程序,反之亦然。传输速度真的很慢(100KB/s),我正在尝试找出问题所在。我的连接速度大约是 100Mbps 下行/12Mbps 上行,并且通过其他应用程序将文件传输到服务器具有更高的吞吐量。

下面是服务器应用程序上的类,它基本上读取数据并将其保存在磁盘上:

class AcceptFileResponseCreator: HttpResponseCreator {

    private val response = JSONObject()

    override fun computeResponse(httpExchange: HttpExchange): Triple<BufferedInputStream, Int, HashMap<String, String>> {

        try {

            logger.debug("Start accepting the request of uploading a file to the server...")

            val fileSize = httpExchange.requestHeaders.getFirst("Content-length").toLong()
            val md5OfTransferredFile = httpExchange.requestHeaders.getFirst(JSONFieldNames.API_HEADER_MD5)!!
            val targetId = httpExchange.requestHeaders.getFirst(JSONFieldNames.API_HEADER_TARGET_ID)!!
            val clientId = httpExchange.requestHeaders.getFirst(JSONFieldNames.API_HEADER_CLIENT_ID)!!
            val clientAuthentication = httpExchange.requestHeaders.getFirst(JSONFieldNames.API_HEADER_CLIENT_PASS)!!
            val fileName = decodeHTTPHeader(httpExchange.requestHeaders.getFirst(JSONFieldNames.API_HEADER_CLIENT_FILENAME)!!)
            val fileRelativePath = decodeHTTPHeader(httpExchange.requestHeaders.getFirst(JSONFieldNames.API_HEADER_CLIENT_RELATIVE_PATH)!!)
            val fileHash = httpExchange.requestHeaders.getFirst(JSONFieldNames.API_HEADER_CLIENT_FILE_HASH)!!


            val file = Manager.getEmptyFile()


            if (!file.parentFile.exists()) {
                file.parentFile.mkdirs()
            }
            logger.debug("Start saving the file on the server...")
            val amountOfBytesWritten = Files.copy(httpExchange.requestBody, file.toPath())
            Manager.addFileAsFileTransfer(fileName, fileRelativePath, fileHash, targetId, clientId, file)
            logger.debug("Wrote the file on the server at: ${file.absolutePath}")
            if (amountOfBytesWritten == fileSize) {
                return if (md5OfTransferredFile == file.md5()) {
                    logger.debug("File must have been written successfully! ($fileRelativePath/$fileName from client $clientId for $targetId)")
                    response.put(JSONFieldNames.REQUEST_ACCEPT, true)
                    Triple(response.toString().toByteArray().inputStream().buffered(), HttpURLConnection.HTTP_OK, HashMap())
                } else {
                    logger.debug("File md5 hash does not match! ($fileRelativePath/$fileName from client $clientId for $targetId)")
                    constructErrorResponse("File md5 hash uploaded on the server does not match!")
                    Triple(response.toString().toByteArray().inputStream().buffered(), HttpURLConnection.HTTP_INTERNAL_ERROR, HashMap())
                }
            }
            else {
                logger.debug("Amount of bytes written to the disk differ from the amount of the sender's data")
                constructErrorResponse("Amount of data received by the server is not correct.")
                return Triple(response.toString().toByteArray().inputStream().buffered(), HttpURLConnection.HTTP_INTERNAL_ERROR, HashMap())
            }

        }
        catch (e: Exception) {
            Manager.logger.debug("Couldn't parse as JSON", e)
            constructErrorResponse("Request could not be parsed as a JSON object.", e.toString()).toString()
            return Triple(response.toString().toByteArray().inputStream().buffered(), HttpURLConnection.HTTP_NOT_FOUND, HashMap())
        }

    }

    private fun constructErrorResponse(errorMessage: String = "Generic error", exceptionMessage:String = "") : JSONObject {
        response.put(HttpJSONFieldIdentifiers.REQUEST_ACCEPT, false)
        response.put(HttpJSONFieldIdentifiers.RESPONSE_ERROR, errorMessage)
        return response
    }

}

客户端应用程序上发送数据的方法是:


    /**
     * 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
     */
    fun sendRequestSynchronous(data: InputStream, uri: String = "/", requestMethod: String = "POST", headers: Map<String, String> = HashMap()) : Triple<Int, MutableMap<String, MutableList<String>>, InputStream> {
        val path = "https://$serverHostname:$serverPort$uri"
        val connection = if (path.startsWith("https://")) (URL(path).openConnection() as HttpsURLConnection) else URL(path).openConnection() as HttpURLConnection


        javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier { hostname, _ -> hostname == serverHostname }
        if (connection is HttpsURLConnection) {
            connection.sslSocketFactory = this.sslFactory
            connection.hostnameVerifier = CustomHostnameVerifier(serverHostname)
        }
        connection.doInput = true
        connection.doOutput = true
        connection.requestMethod = requestMethod
        headers.forEach {
            connection.addRequestProperty(it.key, it.value)
        }

        val totalAmountBytesToSend = data.available() // amount of bytes of the whole request
        var bytesSent = 0
        var bytesAmountToSent: Int // amount of bytes that will be sent in only one write call
        var amountOfBytesReadFromInput: Int
        while (bytesSent < totalAmountBytesToSend) {
            bytesAmountToSent = totalAmountBytesToSend - bytesSent
            if (bytesAmountToSent > ramUsagePerAction)
                bytesAmountToSent = ramUsagePerAction
            val bytes = ByteArray(bytesAmountToSent)
            amountOfBytesReadFromInput = data.read(bytes)
            if (amountOfBytesReadFromInput != bytesAmountToSent) {
                logger.error("Different amount of bytes read from input stream than expected! Read: $amountOfBytesReadFromInput, expected: $bytesAmountToSent")
            }
            connection.outputStream.write(bytes)
            bytesSent += bytesAmountToSent
        }

        connection.outputStream.flush()

        val code = connection.responseCode

        return Triple(code, connection.headerFields, connection.inputStream)
    }

有什么我可以改进的吗?如果您的更改不涉及服务器应用程序上的 Files.copy,那么如果它加快了速度,那也是非常受欢迎的。

标签: javafilesocketskotlinstream

解决方案


推荐阅读