- Находки программиста
- среда, 6 ноября 2013 г.
- UDP в Android: как приложения ищут друг друга в сети?
- Android-er
- Wednesday, June 1, 2016
- Android Datagram/UDP Server example
- 5 comments:
- finnjohnsen / UDPListenerService
- This comment has been minimized.
- sudam-chavan commented Feb 27, 2013
- Сокеты в Android
- Сервер
- Клиент
Находки программиста
Решения конкретных задач программирования. Java, Android, JavaScript, Flex и прочее. Настройка софта под Linux, методики разработки и просто размышления.
среда, 6 ноября 2013 г.
UDP в Android: как приложения ищут друг друга в сети?
В большинстве случаев говря о передаче данных по сети мы имеем в виду TCP. Для большинства сетевых задач этот протокол лучший, но он вообще-то совсем не единственный. И есть вещи, которые с его помощью делать не удобно. Например: мы знаем порт, но не знаем IP-адреса получателя и нам нужно его найти. Опрашивать все адреса локальной сети по очереди? Жуть. Тут надо бы послать широковещательное сообщение, а для этого мы уже используем UDP. Этот протокол достаточно интересен. Например, мы можем слать сообщения и слушать на одном и том же проту одновременно. UDP при отправке широковещательных сообщений не создаёт соединения в привычном нам смысле. Мы не знаем, доставлено ли получателю наше UDP сообщение. Впрочем нам это и не нужно. Передавать данные мы будем уже по TCP. Итак как же экземплярам нашего приложения искать друг друга?
Кричим и слушаем одновременно
Этот класс — обычный поток, который открывает DatagramSocket и слушает его методом socket.receive(). При получении сообщения мы достаём текст и IP-адрес отправителя и передаём это методу onReceive() экземпляра класса BroadcastListener, который получили в конструкторе. Чтобы отправлять широковещательные сообщения используем метод send(). Заметьте, что сокет для отправки broadcast-ов мы открыли на том же порту, что и сокет для приёма сообщений. Никаких проблем с этим не возникает.
Как это использовать?
Делаем наш Pinger вложенным классом какого-нибудь Service, стартуем как обычный Thread и через очень короткое время имеем в переменной ips список IP-адресов всех кто шлёт сейчас запросы в нашей сети. Себя тоже мы там найдём, так что не забываем фильтровать свой IP-адрес. Скорость такого «поиска родственных душ» зависит только от задержки между пингами, которую (если не жалко трафика) можно сделать минимальной. Чтобы сохранять список IP-адресов актуальным, сохраняем время последнего пинга от данного клиента и вычищаем слишком «старые» адреса.
Источник
Android-er
For Android development, from beginner to beginner.
Wednesday, June 1, 2016
Android Datagram/UDP Server example
I posted «Java Datagram/UDP Server and Client, run on raspberry Pi» on my another blogspot. And last post show «Android Datagram/UDP Client example». This post show a Datagram/UDP Server run on Android.
MainActivity.java
uses-permission of «android.permission.INTERNET» is needed in AndroidManifest.xml
Remark about life-cycle:
In this example, the DatagramSocket server is run in background thread. I haven’t handle the life-cycle very well (Actually I don’t think any application will have UI like this example). Consider the cases:
Case One:
— Start the app, the activity display on screen and the DatagramSocket opened in associated thread.
— the code socket.receive(packet) block the program flow, so the thread stay here and waiting data request.
— Exit the app. It will set running to false, to request the thread to stop. But the thread is blocked in socket.receive(packet), so it’s still running.
— Restart the app, the new thread cannot open the DatagramSocket, because it’s still held by old thread.
— Client send a request, the DatagramSocket server response the request and exit socket.receive(packet), and check running and exit.
— In this case, the current activity and associated thread have no DatagramSocket opened!
Case Two:
— Start the app, the activity display on screen and the DatagramSocket opened in associated thread.
— the code socket.receive(packet) block the program flow, so the thread stay here and waiting data request.
— Exit the app. It will set running to false, to request the thread to stop. But the thread is blocked in socket.receive(packet), so it’s still running.
— Client send a request, the DatagramSocket server response the request and exit socket.receive(packet), and check running and exit.
— Restart the app, and open the DatagramSocket.
— In this case, the current activity and associated thread can open DatagramSocket and work as expected.
5 comments:
Hi man, I appreciate your job, you are doing it really good. I want to ask that I create a socketserver on app and listening to port 8080,and I set system proxy as localhost:8080 so browsers are sending requests to app. I wonder how can I connect to requested url with request headers and send data to browser. Thanks for your business.
Hi;
Im curious about adding time when sending msg from client and the time when the server receive it, to find the transmission time in millisecond or in microsecond?
HI
how can put code to find the transmission time in milliseconds or microseconed?
thanks
Is there any limit on sending or receiving? I’m working sending and receiving lots of data, not concurrently, and my application is closing alone.
Tutorial is great, but it doesnot work on celluar internet connection.
you may have tested when you where on WIFI.
do you have any Tutorial on how to use celluar internet connection?
Источник
finnjohnsen / UDPListenerService
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
package no.nsb.ombord; |
import java.net.DatagramPacket; |
import java.net.DatagramSocket; |
import java.net.InetAddress; |
import java.net.SocketTimeoutException; |
import java.net.UnknownHostException; |
import org.apache.http.util.ExceptionUtils; |
import android.app.Service; |
import android.content.Intent; |
import android.os.IBinder; |
import android.util.Log; |
import android.widget.Toast; |
/* |
* Linux command to send UDP: |
* #socat — UDP-DATAGRAM:192.168.1.255:11111,broadcast,sp=11111 |
*/ |
public class UDPListenerService extends Service < |
static String UDP_BROADCAST = «UDPBroadcast»; |
//Boolean shouldListenForUDPBroadcast = false; |
DatagramSocket socket; |
private void listenAndWaitAndThrowIntent(InetAddress broadcastIP, Integer port) throws Exception < |
byte[] recvBuf = new byte[15000]; |
if (socket == null || socket.isClosed()) < |
socket = new DatagramSocket(port, broadcastIP); |
socket.setBroadcast(true); |
> |
//socket.setSoTimeout(1000); |
DatagramPacket packet = new DatagramPacket(recvBuf, recvBuf.length); |
Log.e(«UDP», «Waiting for UDP broadcast»); |
socket.receive(packet); |
String senderIP = packet.getAddress().getHostAddress(); |
String message = new String(packet.getData()).trim(); |
Log.e(«UDP», «Got UDB broadcast from » + senderIP + «, message: » + message); |
broadcastIntent(senderIP, message); |
socket.close(); |
> |
private void broadcastIntent(String senderIP, String message) < |
Intent intent = new Intent(UDPListenerService.UDP_BROADCAST); |
intent.putExtra(«sender», senderIP); |
intent.putExtra(«message», message); |
sendBroadcast(intent); |
> |
Thread UDPBroadcastThread; |
void startListenForUDPBroadcast() < |
UDPBroadcastThread = new Thread(new Runnable() < |
public void run() < |
try < |
InetAddress broadcastIP = InetAddress.getByName(«172.16.238.255»); //172.16.238.42 //192.168.1.255 |
Integer port = 11111; |
while (shouldRestartSocketListen) < |
listenAndWaitAndThrowIntent(broadcastIP, port); |
> |
//if (!shouldListenForUDPBroadcast) throw new ThreadDeath(); |
> catch (Exception e) < |
Log.i(«UDP», «no longer listening for UDP broadcasts cause of error » + e.getMessage()); |
> |
> |
>); |
UDPBroadcastThread.start(); |
> |
private Boolean shouldRestartSocketListen=true; |
void stopListen() < |
shouldRestartSocketListen = false; |
socket.close(); |
> |
@Override |
public void onCreate() < |
>; |
@Override |
public void onDestroy() < |
stopListen(); |
> |
@Override |
public int onStartCommand(Intent intent, int flags, int startId) < |
shouldRestartSocketListen = true; |
startListenForUDPBroadcast(); |
Log.i(«UDP», «Service started»); |
return START_STICKY; |
> |
@Override |
public IBinder onBind(Intent intent) < |
return null; |
> |
> |
This comment has been minimized.
Copy link Quote reply
sudam-chavan commented Feb 27, 2013
Hi,
I wrote similar kind of service for UDP packet listener. I have created service and started thread in service class, which sits on listening UDP broadcast packet over wifi. This works fine when my phone is in awake state. But I don’t what happens when I keep the phone in ideal state and when phones screen is locked. The service become unresponsive then. Do you have any idea why this is happening.
Источник
Сокеты в Android
Существует очень много приложений (на Android и на любых других ОС), которые взаимодействуют друг с другом с помощью соединения по сети. Например, к таким приложениям можно отнести любой месседжер: WhatsApp, Viber и т.д. Как правило, соединение между приложениями достигается путём использования сокетов.
Сокеты — это интерфейс, который позволяет связывать между собой различные устройства, находящиеся в одной сети. Сокеты бывают двух типов: клиент и сервер. Различие между ними заключается в том, что сервер прослушивает входящие соединения и обрабатывает поступающие запросы, а клиент к этому серверу подключается. Когда сервер запущен, он начинает прослушивать заданный порт на наличие входящих соединений. Клиент при подключении должен знать IP-адрес сервера и порт.
В связи с этим одним из основных применений сокетов служит использование их в качестве средства коммуникации.
В Android сокеты по умолчанию используют для передачи данных протокол TCP/IP вместо UDP. Важной особенностью этого протокола является гарантированная доставка пакетов с данными от одной конечной точки до другой, что делает этот протокол более надёжным. Протокол UDP не гарантирует доставку пакетов, поэтому этот протокол следует использовать, когда надёжность менее важна, чем скорость передачи.
Для реализации сокетов в Android используются классы Socket, предоставляющий методы для работы с клиентскими сокетами, и ServerSocket, предоставляющий методы для работы с серверными сокетами. Рассмотрим на примере нашего приложения «Эрудит«, как можно реализовать многопоточное приложение на основе сокетов. Суть приложения заключается в том, что в игре принимают участие судья (сервер) и 4 игрока (клиенты). Судья задаёт вопрос, после чего запускает таймер и игроки должны нажать кнопку, ответить на него.
Сервер
В роли сервера здесь будет выступать судья, поскольку он должен принимать ответы от всех команд и контролировать процесс игры. Для начала создадим класс SocketServer, наследующий от Thread.
Примечание: В этом классе неспроста используется наследование от Thread, поскольку операции, связанные с сетью, следует выполнять в отдельном от главного потоке. В противном случае приложение будет крашиться с исключением android.os.NetworkOnMainThreadException. По этой причине здесь и далее вся работа с сокетами будет выполняться в потоках.
Данный класс будет служить «обёрткой» для ServerSocket, чтобы можно было удобнее взаимодействовать с ним. Поскольку мы используем наследование от Thread, необходимо реализовать метод run(), внутри которого будет помещена логика работы сервера.
Задача сервера заключается в том, чтобы слушать заданный порт и принимать входящие подключения. Однако поскольку у нас должно быть 4 клиента, их нужно как-то различать. Для этих целей создадим класс UserManager, целью которого будет связывание сокета, полученного в результате метода accept() с пользователем, который установил соединение.
Здесь аналогичным образов в потоке запускаем созданный сокет и ставим его на прослушивание. Параллельно с этим создаём экземпляр класса User, код которого представлен ниже. Он служит для хранения данных о пользователях и их сообщениях
После того, как сообщение было получено, проверяется наличие в нём команд в методе hasCommand(). Например, команда LOGIN_NAME сообщает никнейм подключившегося игрока, а команда CLOSED_CONNECTION — о закрытии соединения. Если никакой команды нет — просто передаём сообщение через интерфейс.
При подключении нового пользователя передаём в интерфейс данные о нём с помощью метода userConnected(), аналогично при дисконнекте вызываем userDisconnected().
Метод close() закрывает соединение с клиентом.
Метод sendMessage() отправляет сообщение клиенту.
Теперь пробросим интерфейс в класс SocketServer.
В SocketServer создадим экземпляр класса UserManager и список, содержащий объекты этого класса. При создании нового сокета он передаётся в конструктор UserManager, после чего запускается поток.
Чтобы остановить сервер, напишем метод close() со следующим кодом.
Для начала здесь нужно закрыть все соединения с клиентами, после этого остановить прослушивание и остановить сокет методом close().
Отправка сообщений клиентам происходит следующим образом.
Метод sendMessage() отправляет сообщение всем, кроме выбранного пользователя. Он используется, когда отправляется сообщение о том, что отвечает команда N, другим командам.
Метод sendMessageTo() отправляет сообщение только одному пользователю, поиск пользователя происходит по идентификатору.
Метод sendToAll() отправляет сообщение всем подключённым пользователям.
Теперь нужно создать интерфейс, который будет передавать данные в основной поток. Для этого создадим интерфейс со следующим кодом.
Теперь в главном потоке нужно создать экземпляр класса SocketServer и пробросить интерфейс.
Метод updatePlayer() обновляет список подключенных игроков при подключении\отключении кого-либо из игроков.
Примечание: если из потока нужно обновить элементы интерфейса, то следует вызывать runOnUiThread(), который позволяет выполнять код в UI-потоке.
Метод parseMessage() определяет, что за сообщение пришло. Сначала следует проверка на то, что на вопрос уже даётся ответ. В этом случае игроку, отправившему это сообщение, отправляется ответ о том, что на вопрос уже даётся ответ. После этого идёт проверка на то, запущен ли таймер. Если таймер не был запущен, то необходимо отправить игроку сообщение о фальстарте. После всех проверок определяется, какой пользователь отправил сообщение и загорается соответствующая кнопка на экране.
Примечание: поскольку отправлять сообщения в UI-потоке нельзя, здесь используется следующая конструкция.
Клиент
Клиент это игрок, который отвечает на вопрос, заданный судьёй. После сигнала он должен нажать на кнопку, чтобы дать ответ на вопрос.
Для реализации клиентского сокета создадим класс SocketClient.
Метод run() запускает клиент и содержит логику работы сокета. Внутри него создаётся экземпляр класса Socket, который подключается к конечной точке с заданными IP-адресом и портом. Затем вызывается метод onConnected() интерфейса OnMessageReceived, уведомляющий главный поток о том, что сокет установил соединение. После этого вызывается метод sendMessage(), отправляющий сообщения на сервер, в котором передаётся команда LOGIN_NAME и название команды. После этого запускается бесконечный цикл, в котором клиент ждёт сообщения от сервера. Получив сообщение, происходит вызов метода messageReceived() интерфейса OnMessageReceived, который передает сообщение в главный поток.
Метод isConnected() проверяет, подключился ли клиент к серверу.
Метод isRunning() проверяет, запущен ли клиент.
Метод stopClient() разрывает соединение с сервером, предварительно посылая сообщение с командой CLOSED_CONNECTION.
Теперь создадим на активности экземпляр класса SocketClient и пробросим интерфейс.
После того, как будут заданы название команды и IP-адрес сервера, запустится метод connectToServer(), создающий поток, в котором инициализируется экземпляр SocketClient. Внутри него реализован интерфейс с методами onConnected() и messageReceived().
В методе onConnected() мы получаем событие, что клиент установил соединение, и вызываем метод sendPing(), который будет каждые 2 секунды посылать на сервер пинги. Это необходимо для более надежного соединения, поскольку отследить на стороне клиента, что сервер прекратил работу, может быть весьма затруднительно. В случае, если соединение теряется, начинает вызываться метод connectToServer() до тех пор, пока соединение не восстановится.
В методе messageReceived() определяется, какое сообщение пришло от сервера, и в зависимости от этого выполняются соответствующие операции.
- ANSWERED — уведомляет о том, что она вопрос уже отвечает другая команда. Возвращает кнопку в исходное состояние.
- BEEP — сообщает о том, что таймер был запущен и нужно воспроизвести сигнал. Для этого вызывается метод playBeep(), который с помощью класса MediaPlayer воспроизводит MP3-файл, хранящийся в папке Assets.
- RESET — сбрасывает все данные (поле ответа от сервера, состояние кнопки). Это сообщение приходит, когда какая-либо команда ответила на вопрос и нужно восстановить все состояния для нового вопроса.
- FALSE_START — сообщает игроку, что он нажал кнопку раньше, чем был запущен таймер. Возвращает кнопку в исходное состояние.
- По умолчанию: просто выводит сообщение от сервера на экран.
Когда вызывается метод активности onPause(), клиент останавливается с помощью следующего кода.
При возврате в активность восстановить соединение можно вручную, нажать на кнопку переподключения, которая вызовет метод connectToServer().
Сообщение об ответе на сервер посылается с помощью метода sendAnswer(), который вызывается при нажатии на кнопку.
Таким образом, в результате приведенного выше кода мы создали приложение, работающее на сокетах, которые обеспечивают взаимодействие между сервером и несколькими клиентами.
Источник