116. [junior] Работа с сокет-серверами во Flash

junior – серия статей для начинающих ActionScript-разработчиков.
Все статьи серии:
http://flashpress.ru/blog/category/junior/
ByteArray – серия статей посвященных работе с байтовым массивом.
Все статьи серии:
http://flashpress.ru/blog/category/bytearray/
Серия статей на тему Сервер:
  1. Server Part1. Работа с сервером. Теория
  2. Server Part2. Обмен данными по протоколу HTTP
  3. Server Part3. Отладка запросов в браузере
  4. Server Part4. Работа с сокет-серверами

О чем будет данная статья? Здесь вы не найдете описания принципов работы сокетов на низких уровнях, данная информация слишком специфичная и выходит за рамки нашего блога. Я постараюсь вам рассказать о том, что такое сокеты(сокетные соединения) с точки зрения ActionScript-разработчика. Я расскажу вам вкратце теорию сокетных соединений, и покажу на практике как устанавливать такие соединения, и как получить/отправлять данные сокет-серверу. В статье мы обсудим следующие темы:


Что такое сокеты? Взгляд ActionScript-разработчика

В статье Обмен данными по протоколу HTTP я рассказывал вам о том, как можно сделать HTTP-запрос на сервер и получить от него ответ. Такая реализация клиент-серверного взаимодействия подходит для большинства клиент-серверных приложений. Проблема данной реализации в том, что сервер не может быть инициатором передачи сообщения. Т.е. если необходимо передать сообщение(команду) от сервера клиенту, флешке необходимо периодически делать запросы на сервер и проверять наличие обновлений, и если на сервере что то изменилось, сервер присылает команду в ответ на запрос флешки. Самый главный минус такого механизма – низкая скорость взаимодействия и избыточные запросы клиента(для получения новых команд от сервера).

Для решения вышеуказанной проблемы служат сокет-соединения. Сокет соединение – это постоянное соединение между клиентом(например флешкой) и сервером. Т.е. в любой момент времени сервер может отправить сообщение клиенту, и наоборот. И для этого не нужно что бы клиент(флешка) делала запросы на сервер для получения изменений. Непосредственная передача данных по сокету происходит быстрее чем по HTTP-запросу.

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

В данной статье мы не будем разбирать как создавать сокет серверы, т.к. это выходит за рамки специфики блога, но для тестирования ваших клиентов вы можете воспользоваться моим AIR-приложением, которое может выступать в роли локального сокет-сервера.

Создаем сокет соединение в ActionScript 3.0

Итак, скачайте приложение SocketServer и запустите его:
socket-server-1
Выберите host из выпадающего списка (можно оставить значение по умолчанию 0.0.0.0) и укажите порт. Если в поле порт указать значение 0 – будет выбран первый свободный порт(можно оставить значение по умолчанию 0). После нажатия на кнопку connect, будет запущен локальный сокет-сервер. Подробное описание всех возможностей данного сокет-сервера можно получить на странице приложения SocketServer.

Запустив локальный сокет-сервер, вы можете установить с ним прямое соединение с помощью кода ActionScript:

FlashBuilderFlashIDE
Код для редактора Adobe FlashBuilder
Код для редактора Adobe Flash CS Professional

Мы научились создавать прямое сокетное подключение к серверу, все просто, не правда ли? Как же теперь посылать данные на сервер и получать ответы? читайте далее…

Отправка и получение данных через сокеты

При работе с сокетами во флеше, данные передаются в виде ByteArray – байтовый массив. Формат передаваемых данных зависит от реализации сервера, например сервер может ожидать что данные передаются в виде строки, или в виде AMF-массива(объекта). Например если ваш сервер ожидает запрос в виде строки, вы можете отправить строку следующим образом:

Метод writeUTFBytes() преобразовывает вашу строку в байтовый массив и записывает этот массив в сокет. А метод flush() отправляет записанные байты в сеть(т.е. сокету подключенному на другом конце соединения). Если необходимо передать параметры вместе с командой, вы можете отправить строку в которой после названия команды следует набор параметров через какой нибудь разделитель, например так:

В этом коде на сервер передается команда MyCommand с двумя параметрами: Serious Sam(имя) и mail@domain.ru(почта).

Для получения строки отправленной сервером, необходимо в обработчике события ProgressEvent.SOCKET_DATA считывать данные из сокета методом readUTF() или readUTFBytes(). Если вы внимательно прочитали документацию к этим двум методам (readUTF и readUTFBytes), то вы должны были заметить интересную особенность этих методов. Метод readUTF правильно считывает строку в том случае, если перед строкой имеется префикс со значением указывающим на длину строки, а метод readUTFBytes принимает одно значение – сколько байт содержит строка. Т.е. для обоих методов необходимо знать длину строки, что бы корректно прочитать её. Вы спросите зачем нам знать длину для чтения строки? Дело в том, что класс Socket получает данные одним целым массивом байтов. Т.е. если сервер одновременно отправил вам несколько команд, то есть вероятность того, что они будут объеденены в один байтовый массив(ByteArray). И что бы научиться выделять различные команды из одного сплошного потока данных, необходим какой нибудь разделитель, например нулевой байт: \0. Разделитель в виде нулевого байта использует класс XMLSocket, я расскажу вам о нем в следующей статье из серии про сокеты.

На мой взгляд использовать разделитель в виде нулевого байта в случае использования класса Socket не очень хорошо, т.к. придется делать постоянные проверки всего массива на наличие этого разделителя. Не плохим решением проблемы будет использование размера пакета перед каждой отправкой команды (на сервер или на клиент – не важно). Т.е. мы сформировали строку которую надо отправить на сервер, перед тем как отправить эту строку, вычисляем длину строки, и отправляем длину, а следом и саму строку, например так:

Что мы сделали? Создали пустой байтовый массив pack и записали в него нашу строку. После записи очень важно поместить значение указателя в 0(строка 7), т.к. в методе writeBytes(строка 12) будет происходить считывание именно с этой позиции. Далее записываем в сокет значение длины пакета(строка 10) используя свойство pack.length, в данном случае мы могли бы написать и command.length, но лучше взять за привычку отправлять длину именно пакета(ByteArray) а не строки, потому если передаваемая строка получится слишком длинной, вы можете сжать эту строку методом pack.compress(), и в итоге длина вашего пакета может получится меньше чем длина строки(команды). Отправив длину пакета, мы сразу отправляем в сокет и сам пакет(строка 12). В строке 13 мы отправляем все данные в сеть. Если таким образом отправить подряд несколько команд, они могут быть отправлены одной длинной “колбасой” как показано на рисунке:
socket-server-2

Как я говорил раньше, если одновременно передать несколько маленьких пакетов, они могут быть получены одним байтовым массивом(т.е. в одном событии ProgressEvent.SOCKET_DATA), а вот если передать один большой пакет, теоретически он может быть получен кусками в двух событиях ProgressEvent.SOCKET_DATA. Схематически это показано на рисунке:
socket-server-3
На рисунке видно что отправитель отправил подряд три команды, первая имеет длину 2 и значение ab, затем пакет cdef77 длиной 6 и в конце пакет xyzw длиной 4. Теоретически во флешку эти команды могут прийти кусками, как показано красными пунктирными линиями на рисунке выше. Т.е. в первом событии ProgressEvent.SOCKET_DATA мы получили байтовый массив длиной 5 байт, из которых первый байт – это размер пакета, далее идут два байта самого пакета, затем идет размер следующего пакета и один байт из тела следующего пакета. В этом событии мы сразу можем выделить первую команду, т.к. пакет получен полностью, далее флешка должна перейти в режим ожидания, и ждать пока второй пакет будет загружен целиком, как видно из рисунка это произойдет только с приходом третьего события ProgressEvent.SOCKET_DATA. В третьем событии мы собираем наш второй пакет и ждем окончания загрузки третьего пакета, и т.д….

Обратите внимание, что на рисунке мы предположили что размер пакета должен уместиться в один байт, один байт – это максимум 0xff=255 символов в пакете, на практике обычно пакеты получаются намного длиннее, поэтому надо выделить под размер пакета чуть больше байтов. Например в коде отправки данных, мы отправляли размер пакета методом writeShort(), это значит что мы выделили под размер пакета 4 байта (16 бит), а это максимум 0xffff=65535

Давайте напишем функцию которая умеет правильно считывать пакеты из байтового массива, с учетом длины пакета(пакет может быть длиной максимум 4 байта=0xffff). Для определения количества имеющихся байтов в сокете имеется свойство bytesAvailable, это свойство показывает сколько байтов имеется в сокете начиная от текущей позиции (.position) и до конца (.length)

Собрав все выше сказанное, у вас должен получиться вот такой вот класс:

Этот класс умеет устанавливать сокет-соединение с локальным СокетСервером, если на сервере инициировать отправку сообщения, вы получите это сообщение в методе applyCommand.

Что дальше?

Локальное приложение SocketServer имеет функционал примитивного сервера чата, т.е. с его помощью можно создать простой текстовый чат, попробуйте создать свое собственное клиентское приложение, для получения и отправки сообщений через сокет-сервер. Если у вас не получится, не расстраивайтесь, в следующей статье я выложу ActionScript исходники готового приложения flash-чат.

Серия статей на тему Сервер:
  1. Server Part1. Работа с сервером. Теория
  2. Server Part2. Обмен данными по протоколу HTTP
  3. Server Part3. Отладка запросов в браузере
  4. Server Part4. Работа с сокет-серверами
ByteArray – серия статей посвященных работе с байтовым массивом.
Все статьи серии:
http://flashpress.ru/blog/category/bytearray/
junior – серия статей для начинающих ActionScript-разработчиков.
Все статьи серии:
http://flashpress.ru/blog/category/junior/

Да прeбудет с вами Flash.
Serious Sam
Эту статью прочитали 5972 раз

Возникли вопросы по статье? Не стесняйтесь спрашивать в комментариях или любым другим способом на странице Контакты .

Присоединяйтесь к нам в социальных сетях: ВКонтакте , Twitter и Facebook
Понравилась статья? Буду благодарен если вы поделитесь ссылкой с друзьями:


Комментарии ВКонтакте:




Комментарии Facebook:




Комментарии WordPress:


  1. Апчхи

    “Если у вас не получится, не расстраивайтесь, в следующей статье я выложу ActionScript исходники готового приложения flash-чат.” – а ге же обещанное продолжение?

    1. Serious Sam Автор записи

      Новых идей и тем для уроков так много, а личного времени на это все так мало :( обещаю сделать чат и выложить исходники в самое ближайшее время.

  2. Nik

    Я тоже заинтересован в продолжении и коде сервера. А ещё скажите, как же мне увидеть то, что сервер возвращает клиенту? Где текстовые поля, кнопки? я запускаю клиент и вижу чистый лист. Но на сервере видно что соединение установлено.