У меня есть клиентское приложение, которое отправляет файл на серверное приложение и наоборот. Скорость передачи очень низкая (100 КБ / с), и я пытаюсь определить проблему.
Моя скорость соединения составляет около 100 Мбит / с или 12 Мбит / с, а передача файлов на сервер через другие приложения имеет более высокую пропускную способность.
Ниже приведен класс серверного приложения, который в основном считывает данные и сохраняет их на диске:
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 в серверном приложении, это также приветствуется, если оно ускоряет процесс.