← All posts tagged amf

yzh44yzh
erlyvideo rtmp Flash amf Сделал себе интересную подлянку с registerClassAlias. Допустим, у нас есть типизированный объект Message, зарегистрированный этой функцией, так что сервер может отправлять объекты такого типа клиентам. Коннектятся два клиента. Один отправляет Message, сервер бродкастит его на всех, кто законнекчен. И один из клиентов (который отправлял) получает типизированный Message , а второй (который не отправлял) — нетипизированный Object, но с такими же полями.

То бишь, у второго клиента registerClassAlias не сработал. Если второй клиент теперь отправит сообщение, то дальше оба будут получать нормальный Message.

А все потому, что я сделал так:
public class Message
{
registerClassAlias("com.flashdevs.textChatLib.Message", Message);
...

А нужно было так:
registerClassAlias("com.flashdevs.textChatLib.Message", Message);
public class Message
{
...

и registerClassAlias у меня вызывался только при инстанциировании Message, а не раньше. (Причем вызывался многократно при каждом инстанциировании :)
yzh44yzh
wowza amf недосериализация amf на wowza причиняет дополнительные неудобства. Red5 даст вам LinkedHashMap<String, Object> для Object и ArrayList<Object> для Array. А Wowza даст AMFDataObj и AMFDataMixedArray соответственно. И их еще нужно будет преобразовывать во что-то юзабельное, чтобы передать в общую для Red5/Wowza бизнес логику.

Но это ерунда в сравнении с тем, что Wowza не поддерживает передачу типизированных объектов, из-за чего и приходится передавать везде Object или Array :)
yzh44yzh
amf Будучи надежно защищен юнит-тестами, принялся кромсать amf сериализацию из red5, выбрасывая всякое не нужное :)

Изначально там было 6500 строк кода. Теперь осталось 3600. Это еще не предел, буду кромсать дальше. Зато добавил 800 строк тестов :)
yzh44yzh
red5 amf Юнит-тесты выявили баг в работе с reference tables для строк. У себя пофиксил. Послать им баг-репорт, что ли? С патчем.

Однако уже 2 часа ночи. Пора спать. Завтра пошлю )
yzh44yzh
amf Ну сделал это с помощью обычного Reflection, без бобов. Оказалось просто. Еще бы убедиться, что клиент эти байты примет и поймет их как нужный класс :) Сча...
yzh44yzh
red5 amf Red5 умеет сериализовать только java bean классы :(
BeanMap beanMap = new BeanMap(object);

Мысль о том, что amf сериализация будет привязана к java bean меня удручает. Нафига лишние зависимости?
К тому же у меня нифига не получается, вылезает java.lang.NoClassDefFoundError :)

Что ж, придется копаться в spicefactory, там это дело как-то похитрее сделано.
yzh44yzh
amf Кто чем занят этим чудным вечером (ночью?). Кто-то выкачивает весь интернет для обновления гита, кто-то терзает фотошоп.

А я терзаю сериализацию-десериализацию AMF, выдранную из Red5. Уже покрыл юнит-тестами null, boolean, int, Number и String. Осталось покрыть Date, Array, Object и типизированный класс.

После этого можно смело и беспощадно рефакторить. Надо там сократить объем кода раза в два, а лучше в три :)
Ну и эти тесты пригодятся, если захочется переписать сериализацию на скала :)
yzh44yzh
amf Итак, друзья мои, если в registerClassAlias вы юзаете длинные имена классов, типа "com.mycompany.project.module.vo.SomeVO", то вот это длинное имя класса передается строкой в КАЖДОМ запросе.

Если вы морально готовы пойти на уступки стилю кодирования, и можете отказаться от "com.mycompany.project", то лучше это сделать. Чем короче имя класса, тем больше траффика вы съэкономите.

Хотя если важно уменьшить траффик, то IExternalizebale к вашим услугам.

( Разумеется, при все том AMF существенно компактнее, чем JSON, у уж тем более, чем XML. Очевидная вещь, но на всякий случай уточняю :)
yzh44yzh
amf Ну осталься последний важный вопрос, который меня интересует. Это время жизни кеша строк и объектов. Об этом как-то ничего не говорится. Надо полагать, оный кэш существует отдельно для каждого отдельного запроса. Но было бы гораздо эффективнее, если бы кэш был общий на всю сессию общения клиента и сервера.

Ясное дело, в рамках спецификации AMF такое указать невозможно, ибо никаких сессий там нет. А в рамках RTMP было бы вполне возможно.

Надо точно выяснить этот момент.
yzh44yzh
amf Я посмотрел реализацию AMF (сериализация и десериализация) в Red5, BlazeDS и Spicefactory. Нигде этот функционал не реализован как отдельный модуль. Во всех трех серверах он размазан по многим классам и нескольким пакетам.

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

В итоге оказалось, что проще всего достать AMF из кода Red5 :)
yzh44yzh
amf Реализация в BlazeDS по занудности побивает реализацию в Red5. Ежели интересно вникнуть, то смотреть нужно spicefactory.

А если поставить вопрос иначе? Откуда выдрать сериализацию amf для своего сервера? Думаю, что оттуда, где проще написано. Из spicefactory :)

Проще написано — проще выдрать :)
yzh44yzh
amf Примерно так yzh44yzh.com это будет выглядеть в моем докладе.
Делаю небольшой парсер-визуализатор. Полноценный парсер не нужен, нужен только в той мере, чтобы визуализировать некоторые простые примеры типа этого.
yzh44yzh
amf Ладно, хватит на сегодня. С типизированым объектом вообще все понятно:
var user : User = new User;
user.id = 123;
user.name = "Bob";
var so4 : SharedObject = SharedObject.getLocal("so4");
so4.data.val = user;
/*
76 61 6C val
0A object marker
23 0010|0|01|1 has 2 fields|not-dynamic|traits|value
0F 00001111 (7 << 1) | 1 class name length
76 6F 2E 55 73 65 72 vo.User
05 string header
69 64 id
09 string header
6E 61 6D 65 name
04 integer marker
7B 123
06 string marker
07 string header
42 6F 62 Bob
*/
c нетипизированым объектом непонятно, откуда берутся два null marker, один вначале списка name-value, другой в конце списка
var so3 : SharedObject = SharedObject.getLocal("so3");
so3.data.val = {id:123, name:"David"};
/*
76 61 6C val
0A object marker
0B 0000|1|01|1 dynamic|not traits-ref, not traits-ext but traits|not ref but value
01 null marker ?
09 string header 00001001 (4 << 1) | 1
6E 61 6D 65 name
06 string marker
0B string header 00001011 (5 << 1) | 1
44 61 76 69 64 David
05 string header 00000101 (2 << 1) | 1
69 64 id
04 integer marker
7B 123
01 null marker ?
*/
В исходниках Red5 никакие null marker при сериализации объектов не вставляются.
yzh44yzh
red5 amf С сериализацией простых типов разобрался, тут исходники более-менее понятны моему скудному уму. Правда все размазано по нескольким классам и пакетам, но отследить можно.

С сериализацией объектов нифига не разобрался. По исходникам то же вроде бы не Rocket Science, но hex-редактор показывает кое-где какие-то непонятные байты, которые я не могу интерпретировать, ни по спеке, ни по исходникам.

К тому же типизированый объект почему-то записался как name1:name2:value1:value2, а не типизированый записался нормально name1:value1:name2:value2

Поищу еще какую-нибудь открытую реализацию. BlazeDS, хотя бы. Может там понятнее будет :)
yzh44yzh
amf Увы, моему ограниченому уму не все понятно :(
Сохраняю всякие данные в SharedObject и гляжу hex-редактором, чего там получилось.

Начинаю с простого:
var so1 : SharedObject = SharedObject.getLocal("so1");
so1.data.val = 123;
интерпретируется легко:
76 61 6C | val
04 | integer marker
7B | 123 как число

Чуть более сложное:
var so2 : SharedObject = SharedObject.getLocal("so2");
so2.data.val = "123";
и уже затык
76 61 6C | val
06 | string marker
07 | string header
31 32 33 | 123 как строка
не могу понять string header. По идее первый бит показывает, что это value, а не reference, остальные долны обозначать длинну строки. Но длинна строки — 3 символа (по идее 3 байта в UTF-8), а хедер говорит, что 7.

Дальше еще хуже:
var so3 : SharedObject = SharedObject.getLocal("so3");
so3.data.val = {id:123, name:"Bob"};
76 61 6C val
0A 0B 01 09 ?
6E 61 6D 65 name
06 string marker
07 string header
42 6F 62 Bob
05 ?
69 64 id
04 integer marker
7B 123
01 ?

registerClassAlias("vo.User", vo.User);
var user : User = new User;
user.id = 123;
user.name = "Bob";
var so4 : SharedObject = SharedObject.getLocal("so4");
so4.data.val = user;

76 61 6C val
0A 23 0F ?
76 6F 2E 55 73 65 72 vo.User
05
69 64 id
09
6E 61 6D 65 name
04 integer marker
7B 123
06 string marker
07 string header
42 6F 62 Bob

ладно, пора спать. Рано или поздно разберусь :)