410013796724260
• Webmoney
R335386147728
Z369087728698
Использование сокетов в AndroidСоздано большое количество приложений как для Android, так и для других ОС, которые взаимодействуют друг с другом с помощью установления соединенией по сети. К таким приложениям относятся, например, мессенджеры социальных сетей WhatsApp, Viber. Как правило, для установления соединения между такими приложениями используются сокеты. Сокет (socket) — это интерфейс, позволяющий связывать между собой программы различных устройств, находящихся в одной сети. Сокеты бывают двух типов: клиентский (Socket) и серверный (ServerSocket). Главное различие между ними связано с тем, что сервер «открывает» определенный порт на устройстве, «слушает» его и обрабатывает поступающие запросы, а клиент должен подключиться к этому серверу, зная его IP-адрес и порт. В Android сокеты для передачи данных используют по умолчанию протокол TCP/IP, важной особенностью которого является гарантированная доставка пакетов с данными от одного устройства до другого. Особенности использования сокетовЧто важно знать при использовании сокетов в 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 вызываются из активности и выполняют следующие функции :
Листинг Connectionimport 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 для чтения сообщений клиента из потока сокета. Листинг ConnectionWorkerConnectionWorker получает входной поток 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. Листинг CallableDelayCallableDelay организует цикл с задержками. После завершения последнего цикла 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(); } } Листинг серверного класса ServerCерверный класс 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. |