- Использование сокетов в Android
- Особенности использования сокетов
- Клиентский android-сокет
- Класс Connection
- Листинг Connection
- Класс активности MainActivity
- Листинг активности
- Методы управления сокетным соединением
- Серверное приложение
- Листинг ConnectionWorker
- Серверный класс
- Листинг CallableDelay
- Листинг CallableServer
- Листинг серверного класса Server
- Стабильный socket клиент для android
Использование сокетов в Android
Создано большое количество приложений как для Android, так и для других ОС, которые взаимодействуют друг с другом с помощью установления соединенией по сети. К таким приложениям относятся, например, мессенджеры социальных сетей WhatsApp, Viber. Как правило, для установления соединения между такими приложениями используются сокеты.
Сокет (socket) — это интерфейс, позволяющий связывать между собой программы различных устройств, находящихся в одной сети. Сокеты бывают двух типов: клиентский (Socket) и серверный (ServerSocket). Главное различие между ними связано с тем, что сервер «открывает» определенный порт на устройстве, «слушает» его и обрабатывает поступающие запросы, а клиент должен подключиться к этому серверу, зная его IP-адрес и порт. В Android сокеты для передачи данных используют по умолчанию протокол TCP/IP, важной особенностью которого является гарантированная доставка пакетов с данными от одного устройства до другого.
Особенности использования сокетов
Что важно знать при использовании сокетов в Android ?
- соединения сокетов отключаются при переходе устройства в спящий режим;
- чтобы не «рвать» соединение при наступлении спящего режима в устройстве можно использовать сервис;
- для использования интернет-сети необходимо Android-приложению предоставить нужные права в манифесте.
Для определения прав в манифесте необходимо в файл AndroidManifest.xml добавить следующую строку :
Теперь android-приложения будет иметь доступ к сети.
Далее в статье рассмотрим пример клиент-серверного сокетного соединения с передачей сообщения. Функции клиента будет выполнять android-приложение. Серверное java-приложение выполним в IDE Eclipse с использованием пакета concurrent. В конце страницы можно скачать оба приложения.
Клиентский android-сокет
Интерфейс andriod-приложения представлен на следующем скриншоте. Форма приложения включает поле ввода текстового сообщения и кнопки установления соединения сервером, передачи сообщения и закрытия соединения.
Клиентское приложение создадим из двух классов : класс взаимодействия с серверным сокетом Connection и класс стандартной активности MainActivity.
Класс Connection
Класс взаимодействия с сервером Connection получает при создании (через конструктор) параметры подключения : host и port. Методы Connection вызываются из активности и выполняют следующие функции :
Метод | Описание |
---|---|
openConnection | Метод открытия сокета/соединения. Если сокет открыт, то он сначала закрывается. |
closeConnection | Метод закрытия сокета |
sendData | Метод отправки сообщения из активности. |
finalize | Метод освобождения ресурсов |
Листинг Connection
Класс активности MainActivity
В активности MainActivity определены параметры сервера : host, port. Помните, что IP-адрес сервера для Вашего android-примера не может быть localhost (127.0.0.1), иначе Вы будете пытаться связаться с сервером внутри Andriod-системы. Кнопки интерфейса связаны с методами обращения к классу Connection. Кнопки отправки сообщения mBtnSend и закрытия соединения mBtnClose с сервером блокируются при старте приложения. После установления соединения с сервером доступ к кнопкам открывается.
Листинг активности
Методы управления сокетным соединением
Ниже представлены методы обработки событий, связанных с нажатием кнопок интерфейса. Обратите внимание, что подключение к серверу выполняется в отдельном потоке, а открытие доступа к кнопкам в основном потоке, для чего вызывается метод runOnUiThread. Для отправки сообщения серверу также создается отдельный поток.
Серверное приложение
Серверное приложение включает 2 класса : Server и ConnectionWorker. Серверный класс Server будет выполнять обработку взаимодействия с клиентом с использованием ConnectionWorker в отдельном потоке. Конструктор ConnectionWorker в качестве параметра получает объект типа Socket для чтения сообщений клиента из потока сокета.
Листинг ConnectionWorker
ConnectionWorker получает входной поток inputStream из клиентского сокета и читает сообщение. Если сообщение отсутствует, т.е. количество прочитанных байт равно -1, то это значит, что соединение разорвано, то клиентский сокет закрывается. При закрытии клиентского соединения входной поток сокета также закрывается.
Серверный класс
Серверный класс Server создадим с использованием многопоточного пакета util.concurrent. На странице описания сетевого пакета java.net и серверного ServerSocket был приведен пример серверного модуля с использованием обычного потока Thread, при работе с которым необходимо решать задачу его остановки : cтарый метод Thread.stop объявлен Deprecated и предан строжайшей анафеме, а безопасная инструкция Thread.interrupt безопасна, к сожалению, потому, что ровным счетом ничего не делает (отправляет сообщение потоку : «Пожалуйста, остановись»). Услышит ли данный призыв поток остается под вопросом – все зависит от разаработчика.
Чтобы иметь возможность остановить сервер «снаружи» в серверный класс Server включим 2 внутренних реализующих интерфейс Callable класса : CallableDelay и CallableServer. Класс CallableDelay будет функционировать определенное время, по истечении которого завершит свою работу и остановит 2-ой серверный поток взаимодействия с клиентами. В данном примере CallableDelay используется только для демонстрации остановки потока, организуемого пакетом util.concurrent.
Листинг CallableDelay
CallableDelay организует цикл с задержками. После завершения последнего цикла cycle поток завершает цикл, останавливает вторую задачу futureTask[1] и закрывает сокет. В консоль выводится соответствующее сообщение.
Листинг CallableServer
Конструктор CallableServer в качестве параметров получает значение открываемого порта для подключения клиентов. При старте (метод call) создается серверный сокет ServerSocket и поток переходит в режим ожидания соединения с клиентом. Остановить поток можно вызовом метода stopTask, либо завершением «задачи» типа FutureTask с данным потоком.
При подключении клиента метод serverSoket.accept возвращает сокет, который используется для создания объекта ConnectionWorker и его запуска в отдельном потоке. А сервер (поток) переходит к ожиданию следующего подключения.
В случае закрытия сокета (завершение внешней задачи FutureTask с данным потоком) будет вызвано исключение Exception, где выполняется проверка закрытия сокета; при положительном ответе основной цикл прерывается и поток завершает свою работу.
Листинг серверного класса Server
Cерверный класс Server создает два потоковых объекта (callable1, callable2), формирует из них две задачи futureTask и запускает задачи на выполнение методом execute исполнителя executor. После этого контролируется завершение выполнение обоих задач методом isTasksDone. При завершении выполнения обеих задач завершается также и цикл работы executor’а.
Два внутренних описанных выше класса (CallableDelay, CallableServer) не включены в листинг.
Источник
Стабильный socket клиент для android
Недавно столкнулся с проблемой создания приложения (наподобие ICQ) с клиентом под андроид, в связи с чем, появилась необходимость реализовать сокет-соединение, которое выполняло бы следующие функции:
- Инициировало соединение с сервером
- Посылало серверу сообщение типа «Привет, я тут!», чтобы сервер знал, что за устройство к нему подключилось
- Сидело и спокойно ждало входящие сообщения от сервера
Сама по себе задача довольно простая и ее решение описывается много где, НО есть следующие заморочки, с которыми я столкнулся:
- данное подключение должно работать в фоновом режиме и не грузить основной процесс приложения, т.е. отрабатывать в отдельном потоке;
- В случае потери соединения — оно должно автоматом восстанавливаться;
Совместно эти 2 «НО» явились для меня серьезной головной болью на 2 дня. Гугл по данной проблеме выдал несколько более-менее адекватных ссылок, но конечного работоспособного решения я так и не нашел (возможно, плохо искал, конечно, но не суть).
Собственно, предлагаю вам на растерзание рассмотрение свое решение.
1. Фоновый режим
Для того, чтобы наш сокет-клиент отрабатывал в фоновом режиме и принимал сообщения от сервера даже тогда, когда приложение не активно, запустим его внутри сервиса.
Сделать это можно, например, так:
public class NotificationService extends Service <
public class LocalBinder extends Binder <
NotificationService getService() <
return NotificationService.this;
>
>
@Override
public void onCreate() <
super.onCreate();
@Override
public IBinder onBind(Intent intent) <
return mBinder;
>
@Override
public void onDestroy()
// Здесь выполняем инициализацию нужных нам значений
// и открываем наше сокет-соединение
private void startService() <
> catch (InterruptedException e) <
e.printStackTrace();
>
>
// данный метод открыает соединение
public void openConnection() throws InterruptedException
<
try <
// WatchData — это класс, с помощью которого мы передадим параметры в
// создаваемый поток
WatchData data = new WatchData();
data.email = acc_email;
data.ctx = this;
// создаем новый поток для сокет-соединения
new WatchSocket().execute(data);
> catch (Exception e) <
// TODO Auto-generated catch block
e.printStackTrace();
>
>
>
Этот сервис делает 2 вещи:
- Получает каким-либо образом e-mail пользователя, который нужно будет передать на сервер (в примере я его просто присвоил);
- Передает этот e-mail в поток, который создаст сокет-соединение.
В данном коде интерес представляет метод public void openConnection(), а если быть точнее, классы WatchData и WatchSocket, которые используются для создания нового потока, рассмотим их подробнее.
2. Создание потока
Зачем мы вообще создаем поток? Затем, что сервисы в андроиде не создают собственного, а выполняются в потоке приложения, что может (и будет!) тормозить наше приложение. Подробнее про сервисы — тут.
Итак, поток создавать нужно. Я для этой задачи решил использовать хрень под названием AsyncTask.
Для создания нужного мне AsyncTask’а я сделал два класса, о которых писал выше: WatchData и WatchSocket
WatchData:
class WatchData
<
String email;
Context ctx;
>
Поле email — нужно для того, чтобы передать в поток email, который мы хотим отправить на сервер (естественно, тут могут быть любые другие данные). Поле ctx передает контекст приложения. Мне этот контекст, например, нужен, чтобы сохранять получаемые от сервера сообщения в sqlite’овскую базу данных андроида.
WatchSocket:
class WatchSocket extends AsyncTask
<
Context mCtx;
Socket mySock;
protected void onProgressUpdate(Integer. progress)
protected void onPostExecute(Integer result)
<
// Это выполнится после завершения работы потока
>
protected void onCancelled(Object result)
<
// Это выполнится после завершения работы потока
>
protected Integer doInBackground(WatchData. param)
<
InetAddress serverAddr;
mCtx = param[0].ctx;
String email = param[0].email;
try <
while(true)
<
serverAddr = InetAddress.getByName(«192.168.0.10»);
mySock = new Socket(serverAddr, 4505);
// открываем сокет-соединение
SocketData data = new SocketData();
data.ctx = mCtx;
data.sock = mySock;
// ВНИМАНИЕ! Финт ушами — еще один поток =)
// Именно он будет принимать входящие сообщения
GetPacket pack = new GetPacket();
AsyncTask running = pack.execute(data);
String message = email;
// Посылаем email на сервер
try <
PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter(mySock.getOutputStream())),true);
// Следим за потоком, принимающим сообщения
while(running.getStatus().equals(AsyncTask.Status.RUNNING))
<
// Если поток закончил принимать сообщения — это означает,
// что соединение разорвано (других причин нет).
// Это означает, что нужно закрыть сокет
// и открыть его опять в бесконечном цикле (см. while(true) выше)
try
<
mySock.close();
>
catch(Exception e)
<>
>
> catch (Exception e) <
return -1;
>
>
>
Тут нас интересует метод protected Integer doInBackground(WatchData… param), который выполняет все, что нам нужно.
В вечном цикле производятся следующие действия:
- Открывается сокет-соединение
- Запускается новый поток, слушающий сервер
- Посылается сообщение на сервер
- Отслеживается слушающий поток на предмет закрытия соединения
- В случае обрыва соединения цикл срабатывает заново
Здесь может возникнуть вопрос: «Зачем делать еще один поток? Почему не слушать сервер в этом же потоке?»
Отвечаю:
Во время моих экспериментов с AsyncTask’ами выяснилось, что при закрытии соединения методом «сервер взял и вырубился», слушающий его AsyncTask тупо берет и заканчивает работу. Пересоединить сокет с сервером прямо в том же таске у меня, как я ни бился — не вышло. Не берусь заявлять, что этого сделать невозможно — просто предлагаю свое рабочее решение.
Итак, чтобы слушать сервер, создается дополнительный поток GetPacket. Ему, в качестве параметров, в классе SocketData передаются открытый нами сокет и контекст приложения.
3. Слушаем сервер
Собственно, код второго потока:
class SocketData
<
Socket sock;
Context ctx;
>
class GetPacket extends AsyncTask
<
Context mCtx;
char[] mData;
Socket mySock;
protected void onProgressUpdate(Integer. progress)
<
try
<
// Получаем принятое от сервера сообщение
String prop = String.valueOf(mData);
// Делаем с сообщением, что хотим. Я, например, пишу в базу
>
catch(Exception e)
<
Toast.makeText(mCtx, «Socket error: » + e.getMessage(), Toast.LENGTH_LONG).show();
>
>
protected void onPostExecute(Integer result)
<
// Это выполнится после завершения работы потока
>
protected void onCancelled(Object result)
<
// Это выполнится после завершения работы потока
>
protected Integer doInBackground(SocketData. param)
<
mySock = param[0].sock;
mCtx = param[0].ctx;
mData = new char[4096];
Источник