Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > КорДум

Рейтинг: 5.00. Голосов: 3.

Бинарные сокеты в AS3. Часть 3

Запись от КорДум размещена 13.07.2012 в 23:20
Обновил(-а) КорДум 01.12.2012 в 08:39

Часть 1. Часть 2.

Ага. Packet – просто удобная штука для хранения информации, формирования массива сырых байтов и всего такого прочего. Он чрезвычайно прост и представляет собой «абстрактный» класс для всех других пакетов, имеющих уже каждый свой тип.
Код AS3:
package client.net.packets {
	import flash.utils.ByteArray;
 
	/**
	 * @author KorDum
	 */
 
	public class Packet {
		public static const SOME_PACKET:uint = 0x00;
		// другие константы
 
		private var _length:uint;
		private var _type:uint;
		private var _data:ByteArray;
 
		//---------------------------------------------------------------------------
		//
		// CONSTRUCTOR
		//
		//---------------------------------------------------------------------------
 
		public function Packet() {
 
		}
 
 
		//---------------------------------------------------------------------------
		//
		// PUBLIC METHODS
		//
		//---------------------------------------------------------------------------
 
		public function getByteArray():ByteArray {
			var buffer:PacketByteArray = new PacketByteArray();
 
			buffer.writeLength(_length);
			buffer.writeType(_type);
			buffer.writeBytes(_data);
 
			return buffer;
		}
 
 
		//---------------------------------------------------------------------------
		//
		// PUBLIC ACCESSORS
		//
		//---------------------------------------------------------------------------
 
		public function get type():uint { return _type; }
		public function set type(value:uint):void {
			_type = value;
		}
 
 
		public function get length():uint { return _length; }
		public function set length(value:uint):void {
			_length = value;
		}
 
 
		public function get data():ByteArray { return _data; }
		public function set data(value:ByteArray):void {
			_data = value;
		}
	}
}
Как видно, вообще ничего страшного и сложного (но опять фигурирует PacketByteArray). Этот класс расширяют другие пакеты. Один из таких я покажу. Сигнатура этого пакета базируется на базовой (опять масло масляное) сигнатуре, определенной нами в самом начале, но имеет некоторое расширение. В data теперь зашито кроме сообщения еще и команда размерностью short. Давайте посмотрим:
Код AS3:
package client.net.packets {
 
	/**
	 * @author KorDum
	 */
 
	public class SomePacket extends Packet {
		public static const SOME_COOMAND:uint = 0x0000;
		// дриугие константы
 
		private var _command:uint;
		private var _message:String;
 
		//---------------------------------------------------------------------------
		//
		// CONSTRUCTOR
		//
		//---------------------------------------------------------------------------
 
		public function SomePacket () {
			type = Packet.SOME_PACKET;
		}
 
 
		//---------------------------------------------------------------------------
		//
		// PUBLIC STATIC METHODS
		//
		//---------------------------------------------------------------------------
 
		// для формирования пакета из сырых байтов (когда пришли из сокета)
		public static function createByData(length:uint, data:PacketByteArray):SomePacket {
			var packet:SomePacket = new SomePacket ();
 
			packet.data = data;
			packet.length = length;
			packet.command = data.readCommand();
			packet.message = data.readMessage();
 
			return packet;
		}
 
 
		// для формирования пакета из команды (для отправки)
		public static function createByCommand(command:uint):SomePacket {
			var packet:SomePacket = new SomePacket ();
 
			var buffer:PacketByteArray = new PacketByteArray();
			buffer.writeCommand(command);
 
			packet.data = buffer;
			packet.length = buffer.length;
			packet.command = command;
 
			return packet;
		}
 
 
		// для формирования пакета из команды и сообщения (для отправки)
		public static function createByCommandMessage(command:uint, message:String):SomePacket {
			var packet:SomePacket = new SomePacket ();
 
			var buffer:PacketByteArray = new PacketByteArray();
			buffer.writeCommand(command);
			buffer.writeUTFBytes(message);
 
			packet.data = buffer;
			packet.length = buffer.length;
			packet.command = command;
			packet.message = message;
 
			return packet;
		}
 
 
		//---------------------------------------------------------------------------
		//
		// PUBLIC ACCESSORS
		//
		//---------------------------------------------------------------------------
 
		public function get command():uint { return _command; }
		public function set command(value:uint):void {
			_command = value;
		}
 
 
		public function get message():String { return _message; }
		public function set message(value:String):void {
			_message = value;
		}
	}
}
Последний код взят из реального проекта, чуть-чуть подрехтован (имя класса убрал =) ) и обогатился капитанскими комментариями.
Ведь и тут ничего сложного, правда?

Наконец-то я решил осветить PacketByteArray! Это надстройка на ByteArray, весьма и весьма удобная. Ведь если изменится протокольная сигнатура в плане того, что команда вдруг станет не short, а int, все легко правится в одном единственном месте. Пожалуй, то, что в AS3 нет никаких целочисленный типов, окромя (u)int, здесь играет очень даже на руку.
Код AS3:
package client.net.packets {
	import flash.utils.ByteArray;
 
	/**
	 * @author KorDum
	 */
 
	public class PacketByteArray extends ByteArray {
 
		//---------------------------------------------------------------------------
		//
		// CONSTRUCTOR
		//
		//---------------------------------------------------------------------------
 
		public function PacketByteArray() {
 
		}
 
 
		//---------------------------------------------------------------------------
		//
		// PUBLIC METHODS
		//
		//---------------------------------------------------------------------------
 
		public function writeLength(value:uint):void {
			writeInt(value);
		}
 
 
		public function writeType(value:uint):void {
			writeByte(value);
		}
 
 
		public function writeCommand(value:uint):void {
			writeShort(value);
		}
 
 
		public function writeIdRoom(value:uint):void {
			writeInt(value);
		}
 
 
		public function readLength():uint {
			return readInt();
		}
 
 
		public function readType():uint {
			return readByte();
		}
 
 
		public function readCommand():uint {
			return readShort();
		}
 
 
		public function readIdRoom():uint {
			return readInt();
		}
 
 
		public function readMessage():String {
			if (!bytesAvailable) {
				return null;
			}
 
			return readUTFBytes(bytesAvailable);
		}
	}
}
Такая вот малоинформативная простыня кода. Я не решил из нее вырезать все ненужное и аналогичное. Глядите. Обычные обертки вокруг обычных методов ByteArray. Менять с int на short и с short на int нужно только здесь, ничего больше трогать не надо. Не правда ли здорово? Говорю еще раз спасибо ramshteks.

Теперь вернемся к методу writePacket. Он принимает в себя пакет (ему без разницы, какой пакет именно, собираются в сырые данные они все совершенно одинаково). И пример использования:
Код AS3:
_socket.writePacket( SomePacket.createByCommand(SomePacket.SOME_COMMAND) );
Вот и все =)

Я очень советую почитать комментарии ко всем частям. Там на самом деле дофига полезного.

Часть 1. Часть 2.
Размещено в net
Комментарии 30 Отправить другу ссылку на эту запись
Всего комментариев 30

Комментарии

Старый 14.07.2012 00:55 ramshteks вне форума
ramshteks
 
Аватар для ramshteks
момент с чтением и записью в поток. Как бы это "велосипедно" не прозвучало, но лучше всего написать для каждого проекта свой ByteArray(имеется ввиду расширить). И дело не в том, что стандартный плох, а в том, что протоколы и бинарные и любые другие подразумевают, что существуют некие структуры данных которые собственно и передаются. Такой структурой может быть как обычный айди, так и более сложная, например строка, которую кстати тоже не запишешь и не прочитаешь просто так, необходима длина строки перед ней. Структуры могут быть еще более сложными, например сериализованный игровой объект, в котором масса интов, флоатов, строчка и что нибудь в довесок. Вычитывать и записывать такие объекты гораздо удобнее имея для этого методы. Даже если речь идет о простом инте для айди чего-бы то не было. То есть стоит написать методы аля readID():int. в чем соль? да соль в том, что протоколы имеют свойства менятся, расширятся и исправляться, а это значит, что когда айди станет не шортом, а нормальным интом, вам прийдется ползать по всему коду, чтобы найти и исправить устаревший метод чтения айдишника. Если бы использовался readID, исправление бы понадобилось всего в одном месте.

Это пожалуй небольшой опыт, которым бы хотелось дополнить ваши заметки
Старый 14.07.2012 01:00 КорДум вне форума
КорДум
 
Аватар для КорДум
Вполне себе логичный совет. Дело в том, что опыта у меня не то чтобы много, но, кажется, недостаточно. Посему со сложными и/или часто меняющимися протоколами дела иметь не приходилось.
Есть смысл пакеты расширить прослойкой "абстрактных" классов для разных видов пакетов? - то есть создать пакет, в котором только Команда; пакет, где только сообщение; пакет, где И команда, И сообщение. Реализовать протектные методы чтения. Затем на основе их расширять уже итоговые пакеты?
Старый 14.07.2012 01:06 КорДум вне форума
КорДум
 
Аватар для КорДум
Нет, я понял Вас неверно, убежал в другую степь.
Если расширить ByteArray, то необходимо, чтобы во всех пакетах ID был 2 байта, потом во всех же пакетах сменился на 4. А если ID в одном типе пакета 2, а в другом 4? Ну мало ли. Или, более логичная ситуация: команда. Где-то команд 100, для этого подойдет байт, а где-то 1000 - тут уже шорт. Вот такая штука...
Но в любом случае, как-то "автоматизировать" процесс смены протокола нужно. Буду думать. И приму Ваш совет к сведению.
Старый 14.07.2012 01:13 ramshteks вне форума
ramshteks
 
Аватар для ramshteks
мой опыт показывает, что вы в итоге запутаетесь. Чтение в данном контексте должно быть линейным, тогда быдет легче отлаживать, а отлаживать прийдется. И уж поверьте мне, нет ничего гаже, чем отладка бинарных протоколов. Они просто нечитабельны. Их нельзя протрейсить. Нужно делать дамп, а потом с ним копаться. Поэтому делайте просто. Даже если у вас много команд, делайте чтение линейным, без всяких наследований и прочего. Да реализовать абстактный класс-пакет, с базовыми полями вроде айди команды и строковое представление стоит, но не более того. Вы должны зайти в код чтения команды и сразу понять, что тут читается и в каком порядке, без вспоминания того, что же там было, что от чего наследовалось и какие супер-методы что то прочитали. Не забывайте это поток чистых данных, там нужна битовая точность, на разряд ошиблись - и happy debugging, bitch. Подводя итог: В чтении бинарного протокола должна быть максимальная ясность и прозрачность. Как только протокол устоится вы забудете про эти пакеты, а вот через полгода, когда надо будет расиширить пару пакетов вы с удивление обнаружите, что все хотя бы понятно...

Хотя тоже есть момент. Все зависит от протокола. Есть такие протоколы у которых есть кхм... философия, тогда там есть смысл в реализации частного над абстрактным
Старый 14.07.2012 01:13 bav вне форума
bav
 
Аватар для bav
То ли я со временем начинаю все больше любить принцип бритвы Оккама, то ли я чего-то не понимаю . У меня оно все как-то проще реализовано:
Код AS3:
_socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
_lastComSize = 0;
...
private function socketDataHandler(e:ProgressEvent):void 
{
	// Пришли данные, рассматриваем три варианта:
	while (_socket.bytesAvailable)
	{
		// Вариант №1: мы ждали новый пакет, о чем свидетельствует
		// то, что нам не известен размер текущего (последнего)
		// пакета.
		if (!_lastComSize)
		{
			// Читаем размер в том случае если можем его прочитать.
			if (_socket.bytesAvailable >= 2)
				_lastComSize = _socket.readShort();
			// Если не можем, уходим из цикла и из функции,
			// до прихода следующих данных.
			else
				break;
			// Здесь оказываемся если длина пакета успешно считана.
			// Если нам повезло и пакет пришел целиком...
			if (_socket.bytesAvailable >= _lastComSize)
			{
				// ...парсим его. В конце функции parse() переменная
				// _lastComSize выставляется в ноль, что свидетельствует
				// об окончании парсинга одного пакета.
				parse();
				// После этого парсинга или того, что будет в Варианте №2,
				// благодаря циклу while, попытка обработки пришедших
				// данных запустится еще раз. На случай если нам
				// за один раз пришло более одного пакета.
			}
		}
		// Вариант №2: мы ждали окончания пакета, начало которого было
		// в одном из предыдущих вызовов функции socketDataHandler().
		// Если на этот раз байт пришло достаточно, начинаем парсить.
		else if (_socket.bytesAvailable >= _lastComSize)
		{
			parse();
		}
		// Вариант №3: мы также как и во втором варианте дожидаемся
		// окончания пакета, но даже с этой пачкой данных он все равно
		// пришел не полностью. В таком случае просто выходим из функции
		// и ждем еще байтов.
		else
			break;
	}
}
 
private function parse():void
{
	// parse() вызывается только в том случае, когда нам
	// точно известно, что очередной пакет пришел полностью,
	// поэтому можно его смело обрабатывать.
	// Читаем id команды.
	var comId:int = _socket.readShort();
	// И вызываем соответствующую функцию.
	switch(comId)
	{
		case S_READY: readyHandler(); break;
		case S_NO_ROOM: noRoomHandler(); break;
		...
		case S_END_GAME: endGameHandler(); break;
	}
	// Как и было сказано выше, устанавливаем флаг окончания
	// парсинга пакета.
	_lastComSize = 0;
}
И да, разумеется, при необходимости размеры полей длины пакета и id команды можно увеличить до четырех байт.
Старый 14.07.2012 01:15 ramshteks вне форума
ramshteks
 
Аватар для ramshteks
Дело в том, что таких перепадов быть не должно. Я имею ввиду, что айди пользователя в одном пакете 2 байта а в другом 4 это явная проблема с протоколом. Но разная размерность айди в протоколе, решается тем, что вы пишите не один метод readID а readFooID и readBarId
Старый 14.07.2012 01:19 ramshteks вне форума
ramshteks
 
Аватар для ramshteks
2 bav, ну вы в курсе, что это жесть?
Код AS3:
switch(comId)
	{
		case S_READY: readyHandler(); break;
		case S_NO_ROOM: noRoomHandler(); break;
		...
		case S_END_GAME: endGameHandler(); break;
	}
Извините просто, но я видел чем заканчиваются, такие начинания. Это превращается в итоге в треш, в полотна кода на 20 листов. Которые рефакторить не то, что сложно - СТРАШНО
Старый 14.07.2012 01:24 bav вне форума
bav
 
Аватар для bav
Предложите красивый и хороший вариант, я с радостью им воспользуюсь. 20 листов кода ведь никто печатать не собирается, а функцию и свернуть можно.
Старый 14.07.2012 01:25 КорДум вне форума
КорДум
 
Аватар для КорДум
bav, у каждого свой велосипед =))
Твой симпатичнее и на самом деле проще...

ramshteks, не-не, я не спорю ведь. Просто хотел уточнить для себя.

Хочу пояснить, почему такой алгоритм оказался таким (честно, думал, что проще него только топорные большие условия...). У меня до этого был парсинг данных размером в 100 строк. То есть большой, неповоротливый, со временем я даже сам забыл, что я там нафигачил, сидел вот разбирался. Параллельно пишу сервер на java с netty. Я в исходники не вникал, но PacketDecoder пришлось написать свой, расширить от неттивского класса. Он как раз работает по такому же принципу, как то, что я написал в статье. Тоже экзепшены отлавливаются, пускается на докачку данных, снова парсинг и так далее. Мне показалось это жутко симпатичным, тем более, что я видел свой алгоритм на тот момент жутко несовершенным. Он не рассматривал вариант, что интовая длина, которая читалась, могла дать сбой и вывалить экзепшен из-за недостатка данных в буфере сокета. Вот и реализовал нечто похожее.

В любом случае все ваши советы для меня жутко полезны =) Пишите еще!
Старый 14.07.2012 01:27 КорДум вне форума
КорДум
 
Аватар для КорДум
Парсинг команды у меня реализован точно таким же способом, с поправкой на то, что я свитчи-кейсы не люблю =)
Есть вариант запихать в словарь. Команда : ссылка на метод
Жаль только, что в java такое не прокатывает.
Старый 14.07.2012 01:32 bav вне форума
bav
 
Аватар для bav
Словарь да, уже лучше. То же количество строк, но уже без break'ов.
Старый 14.07.2012 01:39 ramshteks вне форума
ramshteks
 
Аватар для ramshteks
2 bav, легко

пакет примерно такого вида [len][code][data]
code конкретно содержит код команды, который вычитывается, data вычитывается полностью, но не декодируется. Далее класс занимающийся вычитыванием данным имеет например вот такой код

https://github.com/ramshteks/as3bicycle-lib/blob/master/src/ramshteks/as3/supadupa/SupaDupa.as

А потом есть код который от него наследуется работает вот так:
https://github.com/ramshteks/as3bicycle-lib/blob/master/src/ramshteks/as3/cozy/Cozy.as

а вот клиентский код, который подписывается на команды и регистрирует сами парсеры:
Код AS3:
//game-list
			_sock.registerParser(Parsers.gl_add);
			_sock.registerParser(Parsers.gl_remove);
			_sock.registerParser(Parsers.gl_update);
			_sock.registerParser(Parsers.gc_create);
и реализует обработку самих команд:
Код AS3:
//handlers
			_strategy = new Strategy();
			_strategy.registerMethod(Parsers.decline.name, onDeclineCommand);
			_strategy.registerMethod(Parsers.regresp.name, onRegAccept)
			_strategy.registerMethod(Parsers.ul_join.name, onJoinUser);
			_strategy.registerMethod(Parsers.ul_remove.name, onRemoveUser);
			_strategy.registerMethod(Parsers.ul_player_info.name, onPlayerInfo);
			_strategy.registerMethod(Parsers.chat_say.name, onChatMessage);
получив событие от Cozy.as(вторая ссылка), таким образом отправлет на обработку
Код AS3:
private function onResponse(e:ResponseEvent):void 
		{
			_strategy.execute(e.response);
		}
только вот в эти методы стратегии приходит уже распарсенная команда. А сам парсер выглядит например вот так:
Код AS3:
package protocol.parsers.list.chat 
{
	import flash.utils.ByteArray;
	import io.SeabattleByteArray;
	import protocol.packets.list.chat.PlayerInfoResp;
	import ramshteks.as3.cozy.interfaces.IParser;
	import ramshteks.as3.cozy.interfaces.IResponse;
 
	/**
	 * ...
	 * @author Shirobok Pavel (ramshteks@gmail.com)
	 */
	public class PlayerInfoRespParser implements IParser 
	{
 
		public function PlayerInfoRespParser() 
		{
 
		}
 
		/* INTERFACE ramshteks.as3.cozy.interfaces.IParser */
 
		public function parse(bytes:ByteArray):IResponse 
		{
			var ba:SeabattleByteArray = SeabattleByteArray.getInstance(bytes);
 
			return new PlayerInfoResp(command, name, ba.read_player_info());
		}
 
		public function get command():int 
		{
			return 13;
		}
 
		public function get name():String 
		{
			return "PlayerInfoResp";
		}
 
	}
 
}
В итоге мы получаем многоразово используемый код с полнын контролем полученного не в одном свиче а где нам удобнее всего. Ну это конечно мои архитектурные "изыски". Кроме всего прочего подписку на комнды можно было сделать чуток по другому, удобнее, но это уже не так важно на самом деле
Старый 14.07.2012 01:42 ramshteks вне форума
ramshteks
 
Аватар для ramshteks
в java можно извернуться вот так:

https://github.com/ramshteks/touchpo...ientLogic.java

Зависимость от :
https://github.com/ramshteks/javabyc.../Strategy.java
Старый 14.07.2012 01:55 Котяра вне форума
Котяра
 
Аватар для Котяра
Ребята, - есть protoBuf, AMF, BSON
Обновил(-а) Котяра 14.07.2012 в 11:51
Старый 14.07.2012 02:00 ramshteks вне форума
ramshteks
 
Аватар для ramshteks
это все те же яйца, только вид в профиль
Старый 14.07.2012 02:16 bav вне форума
bav
 
Аватар для bav
ramshteks, почитал, вроде разобрался. Для меня это и вправду изыски. Например вместо класса реализующего IParser у меня просто функция, скажем, noRoomHandler. Кстати, в SupaDupa.onDataReceived если пришла половина пакета, она просто считывается из сокета и игнорируется, или я что-то упустил?

P.S.
Код:
/**
  * Be man, implement this
  *
  * @param arg object
  */
Вот это мне нравится
Старый 14.07.2012 02:30 ramshteks вне форума
ramshteks
 
Аватар для ramshteks
2 bav, вот я пока писал тот коммент тоже об этом задумался, нужно будет разобраться. Проблем пока не было видимо потому что все пакеты у меня очень маленькие(до 100 с копейками байт) и они не "паровозятся". Про проблему эту знаю,но почему это там не учтено, даже не могу сказать, видимо просто забыл.
Старый 14.07.2012 02:43 Котяра вне форума
Котяра
 
Аватар для Котяра
Обоснуйте против pb свои протоколы - хоть один.
+ pb - это не только протокол, это большая поддержка этого протокола, компиляторы, генараторы с-на любой язык. да и даже если будете придумывать свой всё-равно к pb прийдёте. я его правда расширял. мне не хватало алиаса главного класса. видимо гугловцы совсем погрязли в рпц технолгии, крест,крест..
Старый 14.07.2012 02:58 ramshteks вне форума
ramshteks
 
Аватар для ramshteks
а что тут обосновывать то?
протобуффер, насколько я понимаю, это не столько протокол, сколько инструмент способ де- и сериализации, с удобочитаемого для человека в бинарный и обратно? В итоге обосновывать нечего. Ну да. Протобуффер круче, потому что сам конвертит то, что нужно делать нам руками. Но по сути ровно то же.

Говорить против то и нечего. Можно с pb можно без него. Учтите, что протобуффер, читает и записывает данные ровно тем же способом. Зато целый ворох обвязки для якобы большего удобства. Я в свое время немного читал про него, смотрел их доку. Он не решает больше проблем, чем создает. На мой взгляд опять таки
Старый 14.07.2012 11:46 Котяра вне форума
Котяра
 
Аватар для Котяра
Цитата:
Зато целый ворох обвязки для якобы большего удобства.
Я в одном своём проекте не увидел большого вороха. небольшая либа сериализации на полкилобайта.
А прото используется много где. не во флэшовских вещах. серверник яндексовских маршрутов был приятно удивлён, что я смогу взять их пб напрямую.
Еще хорош - BSON. Но вменяемой реализации на AS пока не видел.
Старый 14.07.2012 15:01 КорДум вне форума
КорДум
 
Аватар для КорДум
Черт, это и правда в разы удобнее, чем постоянно вспоминать, команда - инт или шорт.
Код AS3:
package client.net.packets {
	import flash.utils.ByteArray;
 
	/**
	 * @author KorDum
	 */
 
	public class PacketByteArray extends ByteArray {
 
		//---------------------------------------------------------------------------
		//
		// CONSTRUCTOR
		//
		//---------------------------------------------------------------------------
 
		public function PacketByteArray() {
 
		}
 
 
		//---------------------------------------------------------------------------
		//
		// PUBLIC METHODS
		//
		//---------------------------------------------------------------------------
 
		public function writeLength(value:uint):void {
			writeInt(value);
		}
 
 
		public function writeType(value:uint):void {
			writeByte(value);
		}
 
 
		public function writeCommand(value:uint):void {
			writeShort(value);
		}
 
 
		public function writeIdRoom(value:uint):void {
			writeInt(value);
		}
 
 
		public function readLength():uint {
			return readInt();
		}
 
 
		public function readType():uint {
			return readByte();
		}
 
 
		public function readCommand():uint {
			return readShort();
		}
 
 
		public function readIdRoom():uint {
			return readInt();
		}
 
 
		public function readMessage():String {
			if (!bytesAvailable) {
				return null;
			}
 
			return readUTFBytes(bytesAvailable);
		}
	}
}
И пакет переписал еще. Вот думаю, доделывать статью или комментарии сгодятся?
Код AS3:
package client.net.packets {
 
	/**
	 * @author KorDum
	 */
 
	public class SystemPacket extends Packet {
		private var _command:uint;
		private var _message:String;
 
		//---------------------------------------------------------------------------
		//
		// CONSTRUCTOR
		//
		//---------------------------------------------------------------------------
 
		public function SystemPacket() {
			type = Packet.SYSTEM;
		}
 
 
		//---------------------------------------------------------------------------
		//
		// PUBLIC STATIC METHODS
		//
		//---------------------------------------------------------------------------
 
		public static function createByData(length:uint, data:PacketByteArray):SystemPacket {
			var packet:SystemPacket = new SystemPacket();
 
			packet.data = data;
			packet.length = length;
			packet.command = data.readCommand();
			packet.message = data.readMessage();
 
			return packet;
		}
 
 
		public static function createByCommand(command:uint):SystemPacket {
			var packet:SystemPacket = new SystemPacket();
 
			var buffer:PacketByteArray = new PacketByteArray();
			buffer.writeCommand(command);
 
			packet.data = buffer;
			packet.length = buffer.length;
			packet.command = command;
 
			return packet;
		}
 
 
		public static function createByCommandMessage(command:uint, message:String):SystemPacket {
			var packet:SystemPacket = new SystemPacket();
 
			var buffer:PacketByteArray = new PacketByteArray();
			buffer.writeCommand(command);
			buffer.writeUTFBytes(message);
 
			packet.data = buffer;
			packet.length = buffer.length;
			packet.command = command;
			packet.message = message;
 
			return packet;
		}
 
 
		//---------------------------------------------------------------------------
		//
		// PUBLIC ACCESSORS
		//
		//---------------------------------------------------------------------------
 
		public function get command():uint { return _command; }
		public function set command(value:uint):void {
			_command = value;
		}
 
 
		public function get message():String { return _message; }
		public function set message(value:String):void {
			_message = value;
		}
	}
}
Старый 15.07.2012 15:08 Furinax вне форума
Furinax
Цитата:
Сообщение от КорДум;
И пакет переписал еще. Вот думаю, доделывать статью или комментарии сгодятся?
Статья полезная, лучше было бы конечно допилить, чтобы было красиво
Старый 15.07.2012 16:57 incoob вне форума
incoob
Интересно - использовать netty, и при этом вручную заниматься (де)сериализацией сообщений.
Тут, как мне кажется, либо уж protobuf использовать (благо netty его умеет). Либо писать самодельный сервер на голом NIO.
А то какой-то половинчатый вариант

Я, конечно, субъективен в суждении, но перейдя от самописного NIO сервера к серверу на mina (а потом и на netty), я со временем перешел и от ручной (де)сериализации к protobuf.

Что сейчас нужно сделать, чтобы добавить новое сообщение:
1) написать .proto файл, сгенерировать maven-ом исходники для явы и as3
Код:
message FlashVar {
    required string key = 1;
    required string value = 2;
}

message LoginRequest {
    repeated FlashVar vars = 1;
}

message LoginResponse {
    ...
}
2) Добавить в клиентском и серверном коде константу LOGIN=[номер команды]
3) написать руками обработчик на серверной стороне типа
Код:
public class LoginHandler extends AbstractHandler<LoginRequest, LoginResponse>
Благодаря генерикам, его методы импелементятся в виде
Код:
protected LoginResponse handleImpl(LoginRequest message, DBProvider provider, User user)
Зарегистрировать этот обработчик
Код:
register(LOGIN, new LoginHandler());
4) написать руками обработчик на клиентской стороне (тут генериков нет, так что все приходится прописывать руками)
Код AS3:
	public class LoginHandler extends AbstractHandler
	{
		static public function sendMessage(message:LoginRequest, callback:Function):void
		{
			send(LoginHandler, message, callback);
		}
 
		override protected function get responseClass():Class
		{
			return LoginResponse;
		}
 
		override protected function handleImpl(m:Message):void
		{
			var message:LoginResponse = m as LoginResponse;
Зарегистрировать этот обработчик
Код AS3:
register(LOGIN, new LoginHandler());
Использовать его в коде
Код AS3:
			var message:LoginRequest = new LoginRequest();
			for (var key:String in _flashvars)
			{
				var flashVar:FlashVar = new FlashVar();
				flashVar.key = key;
				flashVar.value = _flashvars[key];
				message.vars.push(flashVar);
			}
			LoginHandler.sendMessage(message, login_callback);

При изменении протокола нужно изменить пару строк в .proto файле, и добавить код в обработчики.
Как дальше упростить использование пока не придумывал. Навскидку, можно при генерации кода генерировать и номер команды.
Старый 15.07.2012 19:33 КорДум вне форума
КорДум
 
Аватар для КорДум
Furinax, окей, в ближайшее время внесу правки.

incoob, как я уже сказал, в серверных делах опыта маловато (и конечно же, как всегда, такие "нубы", как я ломятся писать статьи).
Не понял, причем здесь конкретно netty. На netty декодер уже написан:
Код AS3:
@Override
protected Object decode(ChannelHandlerContext context, Channel channel, ChannelBuffer channelBuffer, DecoderStates state) throws Exception {
	if (state == DecoderStates.LENGTH) {
		_length = channelBuffer.readInt();
		checkpoint(DecoderStates.TYPE);
	}
	else if (state == DecoderStates.TYPE) {
		_type = channelBuffer.readByte();
		checkpoint(DecoderStates.MESSAGE);
	}
	else if (state == DecoderStates.MESSAGE) {
		ChannelBuffer buffer = channelBuffer.readBytes(_length);
		checkpoint(DecoderStates.LENGTH);
 
		return PacketFactory.create(_length, _type, buffer.array());
	}
 
	return null;
}
Писал я его по официальной справке + немного подсмотрел у gloomyBrain.
Дальше пакет собирается уже окончательно (режутся байты в сообщении, вытаскиваются команды) так же удобно, как посоветовал ramshteks. Не вижу неудобств и сложностей. Сменится размерность значащих кусков в сообщении — правим только в одном месте, автоматом "подхватывается" во всех других.
Или я Вас не понял совершенно?

Разве что я не врубился, как прикрутить мой допиленный ChannelBuffer в этот метод так, чтобы длину и тип считывать моими методами (обертки вокруг readInt и readByte). А то ведь сменится протокол, придется менять в двух местах, а не в одном.
Обновил(-а) КорДум 15.07.2012 в 20:11
Старый 16.07.2012 14:01 КорДум вне форума
КорДум
 
Аватар для КорДум
И тут дописал-переписал.
Котяра, я разбавил учебный тон статьи своими неформальными комментариями )
Старый 17.07.2012 15:08 Котяра вне форума
Котяра
 
Аватар для Котяра
Цитата:
Как дальше упростить использование пока не придумывал. Навскидку, можно при генерации кода генерировать и номер команды.
У меня было немного другое использование
Т.е. для нового .proto класса мне надо было сгенерить новый AS класс, прописать его в registerClassByAlias
и реализовать метод а-ля execute (в примере это changeModel)
Покороче твоего варианта.
Старый 18.07.2012 20:11 incoob вне форума
incoob
Ты имя класса передавал с каждым сообщением, а имя может быть очень длинным
Я передаю не строку, а целочисленную константу (id команды). Вот тут и получается разница - в твоем случае константа просто не нужна.
Обновил(-а) incoob 18.07.2012 в 21:15
Старый 18.07.2012 23:18 КорДум вне форума
КорДум
 
Аватар для КорДум
А где я передаю строку?
Старый 19.07.2012 10:37 Котяра вне форума
Котяра
 
Аватар для Котяра
Цитата:
Ты имя класса передавал с каждым сообщением, а имя может быть очень длинным
Я передаю не строку, а целочисленную константу (id команды). Вот тут и получается разница - в твоем случае константа просто не нужна.
Это в примере у меня имя класса, в реальности я использовал короткие id "1", "2" итп.
Старый 19.07.2012 12:05 КорДум вне форума
КорДум
 
Аватар для КорДум
А, это Котяре =)
 

 


Часовой пояс GMT +4, время: 03:37.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.