
Продолжая серию статей, общения между процессами и между двумя приложениями, в заключительной части разберем примеры в пределах одной Wi-Fi‑сети.
Рассмотрим, как с помощью ServerSocket можно организовать взаимодействие между устройствами, будь то обмен данными, совместная работа или создание многопользовательских игр.
Если в предыдущих частях мы рассматривали конкретные способы создания сервера и общение с помощью разных протоколов, в этот раз покажу, как зарегистрировать свой сервер и находить чужие серверы в сети.
Сервис Network Service Discovery
Раньше, если нам нужно было подключиться к серверу внутри локальной сети, мы просто указывали его IP‑адрес — и это работало, так как он был известен заранее. Но что, если мы хотим обмениваться данными с незнакомыми устройствами в одной сети? Решение — сервис Network Service Discovery (NSD).
Network Service Discovery — стандартный механизм в Android, который упрощает процесс регистрации сервера и его поиска клиентскими приложениями.
С его помощью приложения могут:
Публиковать свой сервер в сети (сервер говорит: «Я здесь!»).
Находить доступные серверы (клиенты видят сервер, даже не зная его
IP).
Все это позволяет создавать приложения, которые автоматически находят друг друга в сети, экономя время пользователей и делая взаимодействие устройств более удобным и интуитивным.
Серверная часть
fun run() { // Получение IP-адреса устройства в локальной сети val ip = getIP() // Создаем cервер val server = ServerSocket(0,1,ip) // Задаем имя ПО для нашего сервера val serverName = “My Server” // Узнаем порт, на котором создался наш сервер val port = server.localPort // Регистрируем сервер в локальной сет�� val registeredServer = registerServerInDNS(serverName, port) // Дожидаемся подключения клиента val clientSocket = server.accept() // Начинаем общение runCommunication(clientSocket) }
Для начала общения нам необходимо получить текущий IP-адрес в локальной сети, сделать это можно при помощи ConnectivityManager сервиса:
val manager = context.getSystemService(ConnectivityManager::class.java) fun getIP(): String? { val properties = manager.getLinkProperties(manager.activeNetwork) return properties?.linkAddresses ?.first { adress -> adress.address.isSiteLocalAddress } ?.address?.hostAddress }
IP‑адрес может быть null, если устройство не подключено к сети. Нужно зарегистрировать коллбэк ConnectivityManager.NetworkCallback, чтобы обработать события подключения и отключения и при каждых изменениях в сети получать актуальный IP‑адрес.
val manager = context.getSystemService(ConnectivityManager::class.java) val networkStateCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: android.net.Network) { getIP() } } val request = NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build() manager.registerNetworkCallback(request, networkStateCallback)
Создание сервера:
val server = ServerSocket(0, 1, ip)
Указываем порт 0, чтобы получить первый незанятый порт в системе, а значением 1 указываем, сколько одновременно клиентов могут с нами работать. Последним аргументом передаем IP-адрес. Подробнее о создании можно почитать в прошлых частях.
Регистрация в сети: создаем объект NsdServiceInfo, который хранит информацию о сервере, который мы хотим зарегистрировать:
val manager = context.getSystemService(NsdManager::class.java) fun register(serviceName: String, port: Int) { val nsdServiceInfo = NsdServiceInfo() // Уникальное имя сервера в пределах сети nsdServiceInfo.serviceName = serviceName // Порт, который использует сервер nsdServiceInfo.port = port // Тип сервера nsdServiceInfo.serviceType = "_socket._tcp" manager.registerService(nsdServiceInfo, NsdManager.PROTOCOL_DNS_SD, listener) } val listener = object : NsdManager.RegistrationListener { // Коллбек успешной регистрации override fun onServiceRegistered(info: NsdServiceInfo) { val registeredName = info.serviceName } }
Строка socket.tcp описывает тип сервера, она состоит из протокола (_http, websocket) и транспортной схемы (tcp,_udp), используемой сервисом. Она может быть какой угодно — важно только, чтобы все клиентские приложения, которые должны найти наш сервис, искали его именно под этим именем.
При успешной регистрации сервера в сети отработает коллбек onServiceRegistered. В нем можно будет найти обновленное имя сервера в случае, если оно было занято кем-то другим.
Клиентская часть
На клиентской части для поиска серверов используется NsdManager, который сообщает о появлении или исчезновении серверов в сети:
// Найденные серверы будем хранить в списке val servers = mutableListOf<NsdServiceInfo>() val manager = context.getSystemService(NsdManager::class.java) fun findServices() { val listener = object : NsdManager.DiscoveryListener { // Получаем информацию о сервере override fun onServiceFound(service: NsdServiceInfo) { getInfo(service) } override fun onServiceLost(service: NsdServiceInfo) { // Когда сервер становится недоступным, удаляем его из списка servers.removeIf { it.serviceName == service.serviceName } } } // Запуск процесса обнаружения, указывая тип сервера, который мы ищем manager.discoverServices("_socket._tcp", NsdManager.PROTOCOL_DNS_SD, listener) } // Подключение fun connect() { val server = servers.first() val port = server.port val host = server.host.hostAdress createServer(host, port) }
Колбек onServiceFound сообщает, что сервер был найден и для получения IP-адреса, его порта и других параметров мы должны использовать метод resolveService, после чего можно установить обмен данными:
fun getInfo(service: NsdServiceInfo) { val manager = context.getSystemService(NsdManager::class.java) val callback = object : NsdManager.ResolveListener { override fun onServiceResolved(serviceWithInfo: NsdServiceInfo) { // При успехе добавляем новый сервер в список servers.add(serviceWithInfo) } } manager.resolveService(service, callback) }
Колбэк onServiceLost уведомляет о том, что сервер стал недоступным. В этом случае его можно удалить из списка и разорвать соединение, если обмен данными уже был установлен.
Метод discoverServices у NsdManager запускает процесс обнаружения сервисов в сети, а строка "_socket._tcp" указывает на тип сервиса. Это та самая строка, которую мы ранее использовали при регистрации сервера в сети в серверной части.
У объекта NsdServiceInfo доступны свойства host.hostAddress (IP-адрес) и port, которые нужно использовать для подключения к найденному сервису.
В итоге после добавления минимального UI получился компактный мессенджер для двух человек, который работает без интернета. Главное, чтобы устройства были подключены к общей точке доступа

Полноценный сервер на телефоне
Отмечу еще одну возможность — превращение мобильного устройства в полноценный сервер, обеспечивающий доступ различным клиентам, включая настольные компьютеры. Такой подход может показаться необычным, поскольку по умолчанию смартфоны и планшеты воспринимаются как клиентские устройства.
Для подключения к серверу, запущенному на мобильном устройстве, используется его внешний IP-адрес, который часто меняется динамически. Чтобы его узнать, нужно воспользоваться внешним сервисом, но лучше взять у своего провайдера статический IP-адрес или придумать механизм уведомления об изменении адреса сервера (отправлять сообщение в телеграм-бот и т. п.)
Будущие ограничения в Android
Local Network Protection (LNP) — новая функция в будущем Android, которая ограничит возможность приложений свободно работать в локальной сети. Теперь пользователи смогут сами контролировать, какие приложения получают доступ. В дальнейшем появится специальное Runtime Permission, но пока разработчики могут протестировать новое поведение с помощью shell-команд. Все это означает, что просто так поднять сервер без уведомления пользователя на его устройстве больше не получится.
Стоит упомянуть о Wi-Fi Direct и Wi-Fi Aware — это две технологии беспроводной связи, которые позволяют устройствам взаимодействовать без подключения к Wi-Fi-сети. Но они различаются по своему назначению и принципу работы.

Wi-Fi Direct создает прямое соединение между устройствами с высокой скоростью передачи данных. Технология уже применяется:
в принтерах для печати документов прямо с телефона;
в функциях типа Samsung Quick Share для быстрой передачи файлов;
в стриминге видео и музыки на телевизоры или проекторы.
Wi-Fi Aware позволяет устройствам обнаруживать друг друга и обмениваться данными в фоновом режиме и применяется:
в социальных приложениях, таких как Bumble или Facebook, для поиска пользователей поблизости;
в многопользовательских играх, таких как Spaceteam и BombSquad, для улучшения пользовательского опыта;
в умных домах для автоматического подключения устройств;
для отправки персонализированных сообщений, к примеру когда подходишь к витрине магазина или экспонату в музее.
Выводы
В этой серии статей я показал, как использовать ServerSocket для экзотического IPC на Android. Старался оставить рабочие примеры кода, которые представляют собой минимальную реализацию, готовую к использованию.
В зависимости от целей можно углубиться в изучение способов обезопасить свое общение с помощью шифрования, исследовать новые протоколы взаимодействия или изучить Wi-Fi Direct и Wi-Fi Aware.
Если остались вопросы или хотите поделиться своим опытом — добро пожаловать в комментарии!
