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 вызываются из активности и выполняют следующие функции :
Листинг 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 для чтения сообщений клиента из потока сокета. Листинг 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. |
