Афоризм
Я стою дорого, особенно в одежде.
Наталья Резник
Последние статьи

 • RawContacts
Необработанные контакты в Android
 • javax.crypto.Cypher
Cимметричное шифрование и дешифрирование
 • Random, Math.random
Генерация случайных чисел
 • Компонент JDatePicker
Описание и пример компонента JDatePicker
 • Компонент Tree
Описание и пример дерева Tree библиотеки base-gui
 • Grid с навигатором
Описание и пример Gridp с навигатором
 • Компонент Grid
Описание и пример Grid библиотеки base-gui
 • Библиотека base-gui
Описание компонентов библиотеки base-gui
 • Оператор SELECT
Использование SQL-оператора SELECT

Использование сокетов в Android

Создано большое количество приложений как для Android, так и для других ОС, которые взаимодействуют друг с другом с помощью установления соединенией по сети. К таким приложениям относятся, например, мессенджеры социальных сетей WhatsApp, Viber. Как правило, для установления соединения между такими приложениями используются сокеты.

Сокет (socket) — это интерфейс, позволяющий связывать между собой программы различных устройств, находящихся в одной сети. Сокеты бывают двух типов: клиентский (Socket) и серверный (ServerSocket). Главное различие между ними связано с тем, что сервер «открывает» определенный порт на устройстве, «слушает» его и обрабатывает поступающие запросы, а клиент должен подключиться к этому серверу, зная его IP-адрес и порт. В Android сокеты для передачи данных используют по умолчанию протокол TCP/IP, важной особенностью которого является гарантированная доставка пакетов с данными от одного устройства до другого.

Особенности использования сокетов

Что важно знать при использовании сокетов в Android ?

  • соединения сокетов отключаются при переходе устройства в спящий режим;
  • чтобы не «рвать» соединение при наступлении спящего режима в устройстве можно использовать сервис;
  • для использования интернет-сети необходимо Android-приложению предоставить нужные права в манифесте.

Для определения прав в манифесте необходимо в файл AndroidManifest.xml добавить следующую строку :

<uses-permission android:name="android.permission.INTERNET" />

Теперь android-приложения будет иметь доступ к сети.

Далее в статье рассмотрим пример клиент-серверного сокетного соединения с передачей сообщения. Функции клиента будет выполнять android-приложение. Серверное java-приложение выполним в IDE Eclipse с использованием пакета concurrent. В конце страницы можно скачать оба приложения.

Клиентский android-сокет

Интерфейс andriod-приложения представлен на следующем скриншоте. Форма приложения включает поле ввода текстового сообщения и кнопки установления соединения сервером, передачи сообщения и закрытия соединения.

Клиентское приложение создадим из двух классов : класс взаимодействия с серверным сокетом Connection и класс стандартной активности MainActivity.

Класс Connection

Класс взаимодействия с сервером Connection получает при создании (через конструктор) параметры подключения : host и port. Методы Connection вызываются из активности и выполняют следующие функции :

МетодОписание
openConnection Метод открытия сокета/соединения. Если сокет открыт, то он сначала закрывается.
closeConnection Метод закрытия сокета
sendData Метод отправки сообщения из активности.
finalize Метод освобождения ресурсов

Листинг Connection

import android.util.Log;

import java.io.IOException;
import java.net.Socket;

public class Connection
{
    private  Socket  mSocket = null;
    private  String  mHost   = null;
    private  int     mPort   = 0;

    public static final String LOG_TAG = "SOCKET";

    public Connection() {}

    public Connection (final String host, final int port)
    {
        this.mHost = host;
        this.mPort = port;
    }

    // Метод открытия сокета
    public void openConnection() throws Exception
    {
        // Если сокет уже открыт, то он закрывается
        closeConnection();
        try {
            // Создание сокета
            mSocket = new Socket(mHost, mPort);
        } catch (IOException e) {
            throw new Exception("Невозможно создать сокет: "
                                       + e.getMessage());
        }
    }
    /**
     * Метод закрытия сокета
     */
    public void closeConnection()
    {
        if (mSocket != null && !mSocket.isClosed()) {
            try {
                mSocket.close();
            } catch (IOException e) {
                Log.e(LOG_TAG, "Ошибка при закрытии сокета :"
                                      + e.getMessage());
            } finally {
                mSocket = null;
            }
        }
        mSocket = null;
    }
    /**
     * Метод отправки данных
     */
    public void sendData(byte[] data) throws Exception {
        // Проверка открытия сокета
        if (mSocket == null || mSocket.isClosed()) {
            throw new Exception("Ошибка отправки данных. " +
                              "Сокет не создан или закрыт");
        }
        // Отправка данных
        try {
            mSocket.getOutputStream().write(data);
            mSocket.getOutputStream().flush();
        } catch (IOException e) {
            throw new Exception("Ошибка отправки данных : "
                                         + e.getMessage());
        }
    }
    @Override
    protected void finalize() throws Throwable
    {
        super.finalize();
        closeConnection();
    }
}

Класс активности MainActivity

В активности MainActivity определены параметры сервера : host, port. Помните, что IP-адрес сервера для Вашего android-примера не может быть localhost (127.0.0.1), иначе Вы будете пытаться связаться с сервером внутри Andriod-системы. Кнопки интерфейса связаны с методами обращения к классу Connection. Кнопки отправки сообщения mBtnSend и закрытия соединения mBtnClose с сервером блокируются при старте приложения. После установления соединения с сервером доступ к кнопкам открывается.

Листинг активности

public class MainActivity extends AppCompatActivity
{
    private Button      mBtnOpen  = null;
    private Button      mBtnSend  = null;
    private Button      mBtnClose = null;
    private EditText    mEdit     = null;
    private Connection  mConnect  = null;

    private  String     HOST      = "10.120.51.22";
    private  int        PORT      = 9876;

    private  String     LOG_TAG   = "SOCKET";

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBtnOpen  = (Button)   findViewById(R.id.btn_open );
        mBtnSend  = (Button)   findViewById(R.id.btn_send );
        mBtnClose = (Button)   findViewById(R.id.btn_close);
        mEdit     = (EditText) findViewById(R.id.edText   );

        mBtnSend .setEnabled(false);
        mBtnClose.setEnabled(false);

        mBtnOpen.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onOpenClick();
            }
        });

        mBtnSend.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onSendClick();
            }
        });

        mBtnClose.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                onCloseClick();
            }
        });
    }
}

Методы управления сокетным соединением

Ниже представлены методы обработки событий, связанных с нажатием кнопок интерфейса. Обратите внимание, что подключение к серверу выполняется в отдельном потоке, а открытие доступа к кнопкам в основном потоке, для чего вызывается метод runOnUiThread. Для отправки сообщения серверу также создается отдельный поток.

private void onOpenClick()
{
    // Создание подключения
    mConnect = new Connection(HOST, PORT);
    // Открытие сокета в отдельном потоке
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                mConnect.openConnection();
                // Разблокирование кнопок в UI потоке
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mBtnSend.setEnabled(true);
                        mBtnClose.setEnabled(true);
                    }
                });
                Log.d(LOG_TAG, "Соединение установлено");
                Log.d(LOG_TAG, "(mConnect != null) = "
                              + (mConnect != null));
            } catch (Exception e) {
                Log.e(LOG_TAG, e.getMessage());
                mConnect = null;
            }
        }
    }).start();
}
private void onSendClick()
{
    if (mConnect == null) {
        Log.d(LOG_TAG, "Соединение не установлено");
    }  else {
        Log.d(LOG_TAG, "Отправка сообщения");
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String text;
                    text = mEdit.getText().toString();
                    if (text.trim().length() == 0)
                        text = "Test message";
                    // отправляем сообщение
                    mConnect.sendData(text.getBytes());
                } catch (Exception e) {
                    Log.e(LOG_TAG, e.getMessage());
                }
            }
        }).start();
    }
}
private void onCloseClick()
{
    // Закрытие соединения
    mConnect.closeConnection();
    // Блокирование кнопок
    mBtnSend .setEnabled(false);
    mBtnClose.setEnabled(false);
    Log.d(LOG_TAG, "Соединение закрыто");
}

Серверное приложение

Серверное приложение включает 2 класса : Server и ConnectionWorker. Серверный класс Server будет выполнять обработку взаимодействия с клиентом с использованием ConnectionWorker в отдельном потоке. Конструктор ConnectionWorker в качестве параметра получает объект типа Socket для чтения сообщений клиента из потока сокета.

Листинг ConnectionWorker

ConnectionWorker получает входной поток inputStream из клиентского сокета и читает сообщение. Если сообщение отсутствует, т.е. количество прочитанных байт равно -1, то это значит, что соединение разорвано, то клиентский сокет закрывается. При закрытии клиентского соединения входной поток сокета также закрывается.

public class ConnectionWorker implements Runnable
{
    // Сокет для взаимодействия с клиентом
    private Socket clientSocket = null;

    // Входной поток получения данных из сокета
    private InputStream inputStream = null;

    public ConnectionWorker(Socket socket) {
        clientSocket = socket;
    }
    @Override     
    public void run() {
        try {             
            // Определение входного потока         
            inputStream = clientSocket.getInputStream();
        } catch (IOException e) {
            System.err.println("Can't get input stream");
        } 
        // Буфер для чтения информации
        byte[] data = new byte[1024*4];
        while(true) {
            try {
                /*
                 * Получение информации : 
                 *    count - количество полученных байт
                 */
                int count;
                count=inputStream.read(data,0,data.length);

                if (count > 0) {
                    String msg=new String(data, 0, count);
                    // Вывод в консоль сообщения
                    System.out.println(msg);
                } else if (count == -1 ) { 
                    // Если count=-1, то поток прерван           
                    System.out.println("socket is closed");
                    clientSocket.close();
                    break;
                }
            } catch (IOException e) {
                System.err.println(e.getMessage()); 
            }
        }
        System.out.println("ConnectionWorker stoped");
    }
}

Серверный класс

Серверный класс 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] и закрывает сокет. В консоль выводится соответствующее сообщение.

class CallableDelay implements Callable<String>
{
    private int cycle;

    public CallableDelay(int cycle)
    {
        this.cycle = cycle;
    }
    @Override
    public String call() throws Exception 
    {
        while (cycle > 0) {
            System.out.println("" + cycle);
            Thread.sleep(1000);
            cycle--;
        }
        // Останов 2-ой задачи 
        futureTask[1].cancel(true);
        // Закрытие серверного сокета
        serverSoket.close();

        System.out.println("Thread '"
                    + Thread.currentThread().getName()
                    + "' stoped" );
        // Наименование потока, выполняющего задачу
        return "" + Thread.currentThread().getName();
    }
}

Листинг CallableServer

Конструктор CallableServer в качестве параметров получает значение открываемого порта для подключения клиентов. При старте (метод call) создается серверный сокет ServerSocket и поток переходит в режим ожидания соединения с клиентом. Остановить поток можно вызовом метода stopTask, либо завершением «задачи» типа FutureTask с данным потоком.

При подключении клиента метод serverSoket.accept возвращает сокет, который используется для создания объекта ConnectionWorker и его запуска в отдельном потоке. А сервер (поток) переходит к ожиданию следующего подключения.

В случае закрытия сокета (завершение внешней задачи FutureTask с данным потоком) будет вызвано исключение Exception, где выполняется проверка закрытия сокета; при положительном ответе основной цикл прерывается и поток завершает свою работу.

class CallableServer implements Callable<String>
{
    private  int      port;
    private  boolean  started;

    public CallableServer(int port)
    {
        this.port    = port;
        this.started = true;
    }
    public void stopTask()
    {
        started = false;
    }
    @Override
    public String call() throws Exception 
    {
        // Создание серверного сокета
        serverSoket = new ServerSocket(port); 
        System.out.println("Server start on port : " + port);

        // Цикл ожидания соединений клиентов с сервером
        while(started) {       
            ConnectionWorker worker = null;
            try {
                // Ожидание соединения с клиентом
                worker = new ConnectionWorker(
                                     serverSoket.accept());
                /*
                 * Обработка соединения выполняется
                 * в отдельном потоке
                 */ 
                Thread t = new Thread(worker);      
                t.start();                 
            } catch (Exception e) {
                System.err.println("Connection error : "
                                       + e.getMessage());
                // Завершение цикла.
                if (serverSoket.isClosed())
                    break;
            }
        }
        System.out.println("Thread '"
                        + Thread.currentThread().getName()
                        + "' stoped" );

        futureTask[1].cancel(true);
        // Наименование потока, выполняющего задачу
        return "" + Thread.currentThread().getName();
    }
}

Листинг серверного класса Server

Cерверный класс Server создает два потоковых объекта (callable1, callable2), формирует из них две задачи futureTask и запускает задачи на выполнение методом execute исполнителя executor. После этого контролируется завершение выполнение обоих задач методом isTasksDone. При завершении выполнения обеих задач завершается также и цикл работы executor'а.

Два внутренних описанных выше класса (CallableDelay, CallableServer) не включены в листинг.

public class Server
{
    // Открываемый сервером порт для клиентов
    private final int SERVER_PORT = 9876;       

    // Сокет соединения с клиентами     
    private  ServerSocket  serverSoket = null;

    // Поток контроля времени работы сервера
    private  CallableDelay  callable1 = null;
    // Поток соединения с клиентами
    private  CallableServer  callable2 = null;

    // Список задач
    private FutureTask<String>[] futureTask = null;
    // Исполнитель задач
    private ExecutorService executor = null;


    private Server() 
    {
        // 1-ый поток контролирует задержку работы сервера
        callable1  = new CallableDelay (50);
        // 2-йй поток открывает соединение
        callable2  = new CallableServer(SERVER_PORT);

        // Создание задач
        futureTask = new FutureTask[2];

        futureTask[0] = new FutureTask<String>(callable1);
        futureTask[1] = new FutureTask<String>(callable2);
        
        // Выполнение задач
        executor = Executors.newFixedThreadPool(2);
        executor.execute(futureTask[0]);
        executor.execute(futureTask[1]);
        
        // Цикл работы executor'а
        while (true) {
            if (isTasksDone()) {
                // Завершение работы executor'а
                executor.shutdown();
                System.out.println("\nexecutor shutdown");
                break;
            }
        }
    }       
    //-----------------------------------------------------
    private boolean isTasksDone()
    {
        return futureTask[0].isDone() &&
               futureTask[1].isDone();
    }
    //-----------------------------------------------------
    public static void main(String[] args) 
    {
        new Server();
    }
}

Скачать пример

Архив примера android-socket.zip (98 Кб) включает два проекта : клиентский android (client-android/p12socket) и серверный server-eclipse.

  Рейтинг@Mail.ru