Android: влияние сотовой сети на размер буфера чтения сокетов Wi-Fi соединений

Вопрос или проблема

Я исследую проблему, при которой передача данных по Wi-Fi между моим приложением для Android и GoPro несовместима в различных условиях мобильной сети. Сначала я заметил, что GoPro Quik (официальное приложение GoPro) мог эффективно передавать данные даже в сетях 2G, в то время как моё приложение не могло. После некоторого изучения их исходного кода я понял, что Quik переопределяет read_buffer_size в OkHttp. Как только я внес такую же корректировку в своём приложении, оно стало хорошо передавать данные в 2G, что улучшило производительность в этих случаях. Однако это решение не было идеальным. Иногда передача данных замедлялась даже на 4G, и я заметил, что GoPro Quik также испытывал медленные скорости передачи в этих случаях. Интересно, что скорости значительно увеличивались, как только мобильное соединение отключалось, что может указывать на то, что между мобильной и Wi-Fi сетями существует взаимодействие, которое влияет на производительность. Я ищу способы логирования размеров буферов, чтобы продолжить исследование, и рассматриваю, как Android или основной ядро Linux могут влиять на эти поведения.

Буду признателен за любые предложения или теории о том, как именно качество мобильной сети может оказать влияние.

Вот мой код для создания клиента OkHttp с пользовательской фабрикой сокетов, которая переопределяет read_buffer_size:

class CustomBufferSocketFactory(
    private val sendBufferSizeBytes: Int,
    private val receiveBufferSizeBytes: Int,
    private val tcpNoDelayEnabled: Boolean = false,
    private val socketFactory: SocketFactory = SocketFactory.getDefault()
) : SocketFactory() {

    override fun createSocket(): Socket {
        return configureSocket(socketFactory.createSocket())
    }

    override fun createSocket(host: String, port: Int): Socket {
        return configureSocket(socketFactory.createSocket(host, port))
    }

    override fun createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket {
        return configureSocket(socketFactory.createSocket(host, port, localHost, localPort))
    }

    override fun createSocket(address: InetAddress, port: Int): Socket {
        return configureSocket(socketFactory.createSocket(address, port))
    }

    override fun createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket {
        return configureSocket(socketFactory.createSocket(address, port, localAddress, localPort))
    }

    private fun configureSocket(socket: Socket): Socket {
        socket.sendBufferSize = sendBufferSizeBytes
        socket.receiveBufferSize = receiveBufferSizeBytes
        socket.tcpNoDelay = tcpNoDelayEnabled
        return socket
    }
}

suspend fun connectToLocalNetwork(
    context: Context,
    ssid: String,
    password: String
): OkHttpClient? {
    return withTimeoutOrNull(15000L) { // Таймаут через 15 секунд
        suspendCancellableCoroutine { continuation ->
            var isResumed = false

            val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

            // Используйте WifiNetworkSpecifier для создания запроса на желаемую Wi-Fi сеть
            val wifiSpecifier = WifiNetworkSpecifier.Builder()
                .setSsid(ssid)
                .setWpa2Passphrase(password)
                .build()

            // Создание запроса на сеть
            val networkRequest = NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .removeTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                .setNetworkSpecifier(wifiSpecifier)
                .build()

            // Определите обратный вызов сети
            val networkCallback = object : ConnectivityManager.NetworkCallback() {
                override fun onAvailable(network: Network) {
                    if (!isResumed && !continuation.isCancelled) {
                        isResumed = true

                        // Привязка сетевого трафика вашего приложения к конкретной сети
                        connectivityManager.bindProcessToNetwork(network)

                        Logger.getLogger(OkHttpClient::class.java.name).setLevel(Level.FINE)

                        // Используйте OkHttpClientFactory для загрузки больших файлов
                        val client =
                            OkHttpClient.Builder()
                                .connectTimeout(60, TimeUnit.SECONDS)
                                .readTimeout(60, TimeUnit.SECONDS)
                                .writeTimeout(60, TimeUnit.SECONDS)
                                .socketFactory(CustomBufferSocketFactory(
                                    524288, 524288, true, network.socketFactory))
                                .build()

                        continuation.resume(client)
                    }
                }

                override fun onUnavailable() {
                    if (!isResumed && !continuation.isCancelled) {
                        isResumed = true
                        continuation.resume(null)
                    }
                }

                override fun onLost(network: Network) {
                    if (!isResumed && !continuation.isCancelled) {
                        isResumed = true
                        continuation.resume(null)
                    }
                }
            }

            connectivityManager.requestNetwork(networkRequest, networkCallback)
        }
    }
}

Ответ или решение

Кажется, что у вас возникла проблематика, связанная с передачей данных между вашим приложением и GoPro на различных условиях сетевого соединения, особенно с Wi-Fi под влиянием сотовой сети. Давайте подробнее разберем возможные причины и предложим пути решения.

Причины

  1. Влияние сотовой сети: Когда ваше устройство подключено к Wi-Fi и одновременно имеет доступ к сотовой сети, Android может использовать как Wi-Fi, так и сотовую связь для передачи данных. Это может приводить к эффекту, когда Wi-Fi соединение становится нестабильным из-за того, что система пытается использовать сотовую связь в качестве резервного канала. В результате осыщающая письмогерия может повлиять на затраты по вашим буферам и скорость передачи.

  2. Настройки буферов сокета: Вы правильно определили, что изменение параметров read_buffer_size и send_buffer_size может повлиять на производительность. Однако, хотя вы заметили улучшение на 2G, это не исключает возможности, что оптимальные параметры могут различаться в зависимости от условий соединения (Wi-Fi, 3G, 4G и т.д.), что требует гибкой настройки.

  3. Текущая реализация Android: Android и его инфраструктура могут по-разному обрабатывать сетевые соединения. Например, некоторые версии Android могут использовать другие механизмы для управления сетевыми соединениями, что влияет на приоритеты и условия использования Wi-Fi или сотовой сети.

Решения

  1. Логирование состояния: Введите детализированное логирование событий, касающихся вашего сетевого соединения, включая значения sendBufferSize и receiveBufferSize перед и после соединения. Это позволит вам увидеть, как они изменяются в зависимости от условий сети.

  2. Динамическое изменение буферов: Рассмотрите возможность динамического изменения параметров буферов на основе текущего состояния сети (например, переключение между 2G, 3G, 4G и Wi-Fi). Это может потребовать наблюдения за условиями сети и соответствующей настройки значений серверных буферов.

  3. Ограничение использования сотовой сети: Приоритет выбора именно Wi-Fi соединения может помочь в избежании возможных конфликтов. Для этого можно изменить настройки в вашем NetworkRequest, чтобы исключить любую возможность подключения к сотовой сети (например, детальнее настраивать removeTransportType).

  4. Тестирование на различных устройствах и версиях Android: Разные устройства и различные версии прошивок Android могут иметь разные поведения в отношении сетевых соединений. Тестирование может выявить специфические проблемы и особенности на различных устройствах.

Пример логирования

Добавьте следующий код в свою реализацию для логирования буферов, чтобы вы могли следить за их изменением:

private fun configureSocket(socket: Socket): Socket {
    Log.d("SocketConfig", "Setting send buffer size to $sendBufferSizeBytes")
    Log.d("SocketConfig", "Setting receive buffer size to $receiveBufferSizeBytes")
    socket.sendBufferSize = sendBufferSizeBytes
    socket.receiveBufferSize = receiveBufferSizeBytes
    socket.tcpNoDelay = tcpNoDelayEnabled
    return socket
}

Заключение

Обратите внимание на вышеуказанные аспекты, и вы сможете лучше разобраться в проблемах передачи данных между вашим Android-приложением и GoPro на различных условиях сетевого соединения. Удачи вам в разработке!

Оцените материал
Добавить комментарий

Капча загружается...