О том, что стоящий в твоей комнате традиционный телефон с аккуратно лежащей на нем трубкой все равно может за тобой шпионить, известно чуть ли не со времен А. Г. Белла. Смартфоны продвинулись по этой скользкой дорожке значительно дальше. А как все это выглядит с программной точки зрения?

Введение

В этой статье мы рассмотрим программу, установленную на телефоне с операционной системой Android, которая без палева передает данные на сервер, установленный на персональном компьютере. Для решения поставленной задачи нам потребуется несколько вещей. В первую очередь это желание, а затем такие мелочи, как телефон (с операционной системой Android), компьютер и интернет. Начнем с разработки алгоритма взаимодействия клиента и сервера.

Исключительно в образовательных целях!

Автор и редакция напоминают, что вся представленная в статье информация опубликована исключительно в образовательных целях. В конце концов, запись аудио и передача его на сайт – задача не редкая, нужная и монетизируемая в самых обычных приложениях. Не нарушай закон!

Все операции обмена данными между клиентом и сервером будут происходить по протоколу TCP/IP. В связи с тем что все взаимодействия представляют собой пересылку последовательности байт от клиента к серверу или наоборот, необходимо разработать формат отправляемых пакетов данных. Кроме непосредственно данных, будем также передавать дополнительную информацию, а именно размер пакета и идентификатор команды.

Ниже описана разработанная система команд между клиентом и сервером. Все команды передаются побайтово в следующем формате:

<Размер пакета (4 байта)> <Идентификатор команды (4 байта)> [<Дополнительные данные >]

Команды:

  • 0 — успешно выполнено;
  • -1 — произошла ошибка;
  • 1 — подключение клиента;
  • 2 — начало записи;
  • 3 — отправка записанного AAC-файла;
  • 4 — отправка записанного WAV-файла;
  • 5 — потоковая передача данных;
  • 6 — потоковая передача WAV-заголовка.

Клиент

Для начала давай зададимся вопросом, а что должен делать клиент. В нашем случае необходимо, чтобы телефон записывал звук в фоновом режиме, после чего незаметно для пользователя отправлял файл со звуком на сервер. Причем начинать запись телефон будет только с того момента, как пользователь нажмет кнопку записи, а общее время записи задает сам пользователь в настройках программы. Итак, задача поставлена, необходимо найти ее решение. На борту нашего клиент-телефона установлена операционная система Android. Из этого очевиден выбор языка программирования — им становится Java.

Для написания клиента установим на персональный компьютер IDE Eclipse и Android SDK. Для работы данных компонентов необходим установленный комплект разработчика приложений Java Develovoper Kit. После настройки всех необходимых компонентов подключаем к компьютеру телефон (не забываем про ОС Android) с включенной возможностью отладки по USB. Теперь ты готов кодить! Создай проект в Eclipse и начинай писать программу. Написать класс для подключения к серверу труда не составит. Основные поля этого класса: Socket (программный интерфейс для обеспечения обмена данными между клиентом и сервером.), IP (адрес для подключения), Port (параметр протоколов TCP и UDP) , поток для приема данных, поток для передачи данных. Следуя принципам объектно-ориентированного программирования, все поля делаем приватными (private). В этом классе реализуем метод sendPacket() и задаем его тип synchronized. Synchronized имеет два важных момента: это гарантия того, что только один поток выполняет секцию кода в один момент времени, а данные, измененные одним потоком, будут видны всем другим потокам. В методе sendPacket() при отправке данных создаем блок обработки исключений. Если возникает исключение, информацию о нем записываем в лог и закрываем сокет.

GUI мобильного приложения
GUI мобильного приложения

Но на этом не стоит останавливаться, потребуется класс для записи данных (звука) (WavRecorder). Для этого класса необходимо описать:

  • количество бит на семпл (описываем в виде константы RECORDER_BPP = 16);
  • количество каналов записи (CHANNELINMONO);
  • формат записи (ENCODINGPCM16BIT);
  • флаг, показывающий, идет ли в данный момент запись (по умолчанию false);
  • идентификатор устройства;
  • IP-адрес сервера;
  • номер порта на сервере.

Все поля класса также делаем приватными.

На этом можно было бы и остановиться, но существует одна проблема: пользователю необходимо будет нажать на кнопку «Отправить файл» после записи, а это не самый логичный вариант для скрытого сбора данных. Для передачи данных в режиме непрерывной отправки на сервер напишем класс WavStreamer. Основой этого класса является метод run() (выполняется в отдельном потоке). Будем использовать его для записи звука и отправки данных на сервер — для дальнейшего хранения или обработки. Для потоковой передачи звука создадим «динамический» буфер, в него будут записываться наши данные с микрофона.

public void run() {
  try {
    // Подключение к сокету, получение исходящего потока данных, отправка идентификатора устройства
  } catch (IOException e) {
    // Обработка возможных ошибок и запись их в лог
  }
  byte data[] = new byte[bufferSize]; // Создание буфера
  while (isRecording) {
    read = recorder.read(data, 0, bufferSize); // Чтение данных с микрофона в буфер
    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
      totalDataSize += data.length;
      sendPacket(PackageType.client_send_data, data); // Отправка буфера на сервер
    }
  }
  closeSocket(); // Закрытие соединения с сервером
}

Подробный код ты можешь посмотреть в файле WavStreamer.java. Для самого простого клиента этих классов и методов будет достаточно. Создадим экземпляр Socket’a и будем коннектиться к серверу (о котором речь пойдет далее). В случае удачного присоединения к серверу программа ожидает дальнейших указаний пользователя о необходимости записи. Если соединение не установлено, пользователь должен быть проинформирован об этом (в нашем случае ошибкой соединения). Если мы хотим начать запись, то клиент должен сообщить об этом серверу. Клиент отправляет пакет данных, в котором есть некий код. По этому коду наш сервер понимает, что клиент начал запись. Для старта записи звука в программе создается поток WavRecorder. Этот поток записывает звуковые данные с микрофона клиента-телефона. Для дальнейшего открытия записанного файла в аудиопроигрывателе необходимо записать заголовок, который состоит из определенным образом сформированной последовательности данных (байт).

Схема взаимодействия
Схема взаимодействия

Данная последовательность представляет собой определенным образом созданный байтовый массив. Подобный массив описывается во многих интернет-источниках, если тебе не хочется искать его на просторах глобальной сети, то можешь посмотреть в исходниках (он расположен в файле WavStreamer.java).

Казалось бы, все должно работать… Но возникает вопрос: почему не работает? Так как мы пользовались функциями интернета и записи аудио, необходимо сообщить виртуальной Java-машине Dalvik о том, что наше приложение пользуется этими системными функциями. Давай заглянем в файл AndroidManifest.xml и добавим в него следующие строчки:

// Доступ в интернет
<uses-permission android:name="android.permission.INTERNET" />
// Запись звука с микрофона
<uses-permission android:name="android.permission.RECORD_AUDIO" />

После этого Java-машина предоставит доступ к запрашиваемым системным функциям, описываемым в манифесте.

После добавления некоторых элементов интерфейса наш клиент полностью готов, и можно переходить к написанию сервера.

Сервер

Как и в случае с клиентом, зададимся вопросом: что должен делать сервер? Исходя из задач самого клиента, сервер должен иметь возможность получать данные от клиентов. Данные каждого клиента будут находиться в разных папках и не будут пересекаться с данными других пользователей. Сервер напишем на языке C++ с использованием фреймворка Qt5 (http://qt-project.org/downloads).

Qt — кросс-платформенный инструментарий разработки ПО. Он включает в себя все основные классы, которые могут потребоваться при разработке прикладного программного обеспечения, начиная от элементов графического интерфейса и заканчивая классами для работы с сетью, базами данных и XML.

Одну из основных особенностей Qt составляет повсеместное применение концепции сигналов и слотов, использующихся для взаимодействия между объектами. По своей сути слот — это метод класса, который вызывается, когда происходит какое-либо событие (сигнал). Это сильно облегчает разработку графических интерфейсов или работу с сетью. Достаточно подписаться на какой-то сигнал, который генерирует сам Qt или один из классов программы, и нужная функция-обработчик будет автоматически вызвана при его наступлении.

Уведомление во время активной записи
Уведомление во время активной записи

Теперь перейдем к описанию структуры сервера. Основным классом сервера является MyServer, который инкапсулирует в себе объект типа QTcpServer, отвечающий за сетевое взаимодействие. MyServer на вход подается номер порта и IP, на котором будет работать QTcpServer. В обязанности данного класса входит управление новыми соединениями и перенаправление подключенных клиентов на другой класс. Для начала запустим QTcpServer, передав ему входные данные, и установим обработчик (слот) на сигнал типа newConnection(). Этот сигнал генерируется объектом класса QTcpServer при появлении нового соединения. В случае успешного запуска сервер переходит в режим сканирования порта. Обработчик новых подключений передает задачу по считыванию поступающих данных на объект типа User.

void MyServer::connection() {
  from = server - > nextPendingConnection();
  User * client = new User;
  connect(from, SIGNAL(readyRead()), client, SLOT(getMessage()));
}

Класс User при помощи функции getMessage() обрабатывает поступающие данные и вызывает соответствующую функцию для обработки запроса от клиента.

void User::getMessage() {
  static qint32 size_msg = 0, cmd;
  from = (QTcpSocket * ) sender();
  if ((from - > bytesAvailable() > 0 && size_msg) || (from - > bytesAvailable() > 2 * sizeof(qint32))) {
    QDataStream getm(from);
    if (size_msg == 0) {
      getm >> size_msg >> cmd;
      size_msg -= sizeof(qint32);
      buf.clear();
    }

    qint8 ch;
    while (!getm.atEnd() && size_msg > 0) {
      --size_msg;
      getm >> ch;
      buf.append(ch);
    }
    if (size_msg <= 0 && point_func.find(cmd) != point_func.end())
      bool r = (this - > * point_func[cmd])();
    buf.clear();
    getMessage();
  }
}

Для каждого клиента создается новый каталог, в который записываются файлы клиента. Таким образом, два клиента не будут иметь возможность записывать данные в один и тот же каталог. Файлы, хранящиеся на сервере, могут быть открыты с использованием любого аудиоплеера (для примера был использован VLC media player (https://www.videolan.org) ), который поддерживает воспроизведение файлов формата WAV.

Нет предела совершенству

При желании можно добавить различные фичи в этот проект. Один из вариантов дальнейшего развития — это сервер, находящийся не на компьютере пользователя, а где-нибудь в облаке. Кроме «облачного сервера», можно создать учетные данные пользователя — один и тот же человек может совершать запись звука с различных телефонов. Но хранить данные в разных папках ему будет неудобно.

Решить эту проблему способна учетная запись пользователя. При вводе своих данных пользователь сможет добавлять файлы с телефона другого человека в свой каталог. Правда, реализация данных новшеств привносит и новые задачи. При создании системы учетных записей необходимо хранить базу данных клиентов, с их логинами и паролями. Причем эта база данных должна быть хорошо защищена. Ведь никому не хочется, чтобы его файлы были всем доступны :).

В случае с «облачным сервером» возникает проблема доступа к данным, полученным с мобильного устройства. Решить эту проблему можно путем создания специального клиента-администратора, который получает доступ к данным под конкретной учетной записью и только под ней. Защита звуковых данных пользователя прежде всего!

Заключение

Поставленная нами в начале статьи цель достигнута. Теперь, запустив нашу программу и «случайно» оставив где-нибудь телефон, ты узнаешь о том, что за спиной говорят про тех, кто оставляет свой дорогостоящий смартфон без присмотра :). При этом телефон для всех окружающих находится в спящем режиме, а файлов с записанным звуком на нем не будет.

4 комментария

  1. 25.09.2014 at 20:33

    Может это дело ка-то пожать на устройстве? Не всегда можно передавать жирные wav по EDGE.

  2. 25.09.2014 at 22:18

    эээээ , а чем UDP плох ? чем вызвана необходимость в TCP ?

  3. 26.09.2014 at 10:33

    «Команды:
    0 — успешно выполнено;
    -1 — произошла ошибка;»
    По-моему, это не «команды», а «коды ошибок». Да и сигнал — не Новый год, чтобы «наступать».

  4. Lucky_girl

    13.04.2016 at 18:22

    А где можно посмотреть весь код?

Оставить мнение

Check Also

Нагнуть Nagios. Разбираем хитрую цепочку уязвимостей в популярной системе мониторинга

Nagios — это одно из популярнейших решений для мониторинга, которое используется во многих…