ПрограммированиеФорумОбщее

C# уникальный INT для Class'а

Страницы: 1 2 Следующая »
#0
15:35, 1 авг 2011

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

Для того чтобы избавить разработчика сетевой логики при дизайне классов пакетов, от необходимости заботиться о номерах, хотелось бы сделать систему присвоения номера автоматической.
Но тут возникают проблемы, т.к. если использовать GetHashCode для самого Type класса, то на сервере и клиенте, они различаются, т.к. находятся в разных namespace'ах, или других причин. Тем более, GetHashCode, генерирует разные номера, в зависимости от битности системы (32/64). Также сам алгоритм меняется постоянно с новыми версиями .NET.
Можно взять имя класса, как уникальное данное, т.к. на сервере и клиенте они будут совпадать, это хороший подход. Но тогда нужно генерировать уникальный Hash (int) исходя из string'а.

Не могу придумать/найти хорошее решение для этой задачи, если есть идеи, буду рад выслушать, особенно с конкретными наводками на определённые алгоритмы, создания номера из string'а.

#1
16:29, 1 авг 2011

Можно из строки сделать MD5 хэш и запихнуть в 4 int-а или 2 long-а: http://en.csharp-online.net/Create_a_MD5_Hash_from_a_string
Если нужно именно int, то можно написать свою хэш-функцию и свернуть 128 бит в 32: http://www.concentric.net/~ttwang/tech/inthash.htm

#2
13:05, 3 авг 2011

А почему бы просто имя класса не отправлять? Решение легче некуда.

#3
15:08, 3 авг 2011

Тогда в заголовке пакета будет не 2 или 4 байта, а все те же 10+ (например: PacketAuthorizationRefused) и кодирование/декодирование в ASCII - это изврат, для того чтобы передать номер пакета. Не разумная трата трафика, будут нестабильности со многих сторон.
Пробую вариант с созданием хэша из имени класса используя CRC32.

#4
16:05, 3 авг 2011

> Не разумная трата трафика, будут нестабильности со многих сторон.
Стабильность то как раз будет нормальная. А насчёт перерасхода трафика мне сложно судить, надо смотреть на специфику программы.

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

#5
18:40, 3 авг 2011

Насчёт длины пакетов, если название класса 24 значка, и содержание пакета 4 байта (uint), то для посылки одного числа, в заголовке будет ещё 24 байта, при этом ещё и сеть далее около 20-64 байт прикрепит. Ради одного числа..
А вот насчёт коллизий, тут да, но есть решение, т.к. мне хашь нужен как идентификатор, далее есть словарь, и классы идут в словарь по идентификатору, поэтому при включении приложения, он просто не позволит всунуть в словарь такой же key, от сюда можно вычеслить какой класс.

Сделал CRC32 хеш, пока полёт нормальный, около 80 разных пакетов, никаких проблем..
Шанс на совпадение очень минимальный (2^32-1).

#6
21:36, 3 авг 2011

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

void initNetIDs(){

    int val = 0;


    class1.net_id = ++val;
    class2.net_id = ++val;

    classOlolo.net_id = ++val;

    classPыщьРыщь.net_id = ++val;

    classГагагГагага.net_id = ++val;

}

#7
22:17, 3 авг 2011

MoKa
> Задача как-то унифицировать сериализируемые классы для общения по сети

Зачем? можно разжевать задачу? и ещё как именно сериализуете?

#8
13:16, 4 авг 2011

FDsagizi
Дело в том что идентификатор если использовать increment, то на клиенте если пакеты будут найдены рефлекцией в другом порядке, это уже одна мешанина, другое дело, если версия постарее, но поддерживается, колличество пакетов в фабрике пакетов, будет отличаться от сервера.
Задача иметь одинаковые идентификаторы на сервере и клиенте.

SergeyRu
> Зачем? можно разжевать задачу? и ещё как именно сериализуете?
Суть такова: для того чтобы посылать/читать пакеты, нужно составлять некий заголовок, который будет указывать на различие пакетов, таким образом мы будем знать что этот пакет с таким номером идентификатора - это запрос на список друзей, а вот тот например запрос на посылку сообщения в приват. Таким образом у нас будет различия, и ты будешь определять что читать из пакета, основываясь на его типе.
Сериализация реализована весьма просто, хотя это первый опыт подобного, вот пакет для посылки сообщения:

public class PacketUserRequest:Packet {
    public static uint PacketID;

    [SVariable(1)] uint id;

    public PacketUserRequest(uint id) : base(PacketID) {
        this.id = id;
    }

    public PacketUserRequest(Socket socket,byte[] buffer) : base(socket,buffer) { }

    public uint ID {
        get { return this.id; }
    }
}

Он для сервера и для клиента. Это не финальная версия, т.к. нужно избавиться ещё от передачи сокета. Тут два конструктора, первый используется если нужно послать пакет, воторой если принимаем пакет (имея буффер).
PacketID - переменная которая держит уникальный идентификатор для этого класса пакета. Раньше это была const, и я её назначал ручками, но т.к. для этого нужно разработчику тогда заботиться о том какие номера уже есть, а каких нету, и т.п. Тут возникают проблемы и т.п. В общем при разработки, слишком много париться о том что тупо не нужно.
Поэтому было принято решение избавиться от ручного назначения, а использовать автоматический подход. Но т.к. на сервере и клиенте они должны совпадать, т.к. используются как идентификатор в пакете, чтобы при приёме узнать что за пакет идёт, поэтому нужно использовать алгоритм, что будет генерировать один и тот же номер из информации которая идентична на сервере и на клиенте. Поэтому был выбран подход генерировать число из имени класса, т.к. оно будет всегда совпадать у клиента и сервера. Для этого использовал CRC32 хэш функцию, работает как положено.
Есть также класс PacketFactory который занимается всеми опирациями для нахождения нужного класса и т.п. А класс Packet реализует всё чтение из буффера, запись в него и т.п.
Таким образом для создания нового пакета, нужно лишь объявить подобный класс как выше, и далее он готов к посылке:

client.Send(PacketUserRequest.PacketID,userID);

SVariable - это аттрибут, чтобы определить какие данные из пакета пихать в пакет, и читать из пакета. Он имеет индекс, и опционально если нужно записать строчку текста, кодировку (ASCII/Unicode).

Для получения пакета, не нужно иметь один из конструкторов. Также для отправки, не нужны Accessor'ы к данным и второй конструктор.

Всё это для организации удобства и стабильности в разработки логики для работы с сетью.
Кстати также, реализана Event система для пакетов, при приёме пакетов срабатывает событие, в котором данные из пакета уже готовы в удобном виде.

Таким образом, объявляем класс пакета, и событие с логикой при приёме, компилирум и всё.

#9
14:00, 4 авг 2011

Нафига такие сложности?

public abstract class NetworkPacket
{
    public enum PacketID: uint
    {
         PacketNo1 = 1000,
         PacketNo2 = 1001
         ...
         Packet100500 = 101500
    }

    public abstract PacketID GetPacketID();
}


public PacketNo1 : NetwortPacket
{
...
    public override PacketID GetPacketID()
    {
        return PacketID.PacketNo1;
    }

    public void Serialize()
    {
        networkstream.AddData((uint)GetPacketID());
    }
}

...
#10
14:22, 4 авг 2011

0iStalker
Значит для твоего пакета нужно за'ovveride'ить GetPacketID, и ещё ручками писать функцию Seialize. Также при добавлении пакета, писать его номер в enum.
Это пройденный этап. Удобство в твоём варианте весьма сомнительные, т.к. нужно о многих вещах заботиться. Ручками регестрировать пакеты. И с твоим вариантом, как ты будешь принимать пакет? Как найдёшь нужный класс. У меня всё автоматически:

Packet packet = PacketFactory.Make(this.buffer);

Прочтёт из буффера идентификатор класса, найдёт на сервере, проверит на валидность сериализуемые данные, и затем прочтёт всё создаст экземпляр нужного класса, например PacketAuthorizationApproved, который будет иметь внутри нужные данные. Никакой парки при создании, отправке и получении данных, всё автоматизировано.

В твоём же варианте, нада будет читать первые 32 бит (uint), и потом делать switch? Это изврат.

Сложность реализации, по сути не в самой реализации технически, а лишь логическая. При этом это даёт отличную простоту в использовании без каких либо ограничений при этом.

#11
14:26, 4 авг 2011

MoKa
> У меня всё автоматически:

Ты скорость-то  своего "автоматически" измерь,... не такая уж и огромная работа, написать вручную сериализацию. Интроспекция даёт такие накладные рассходы, что мама не горюй. И в switch'е страшного ничего нет, на фоне всех остальных вещей, которые надо проверять в данных, приходящих от клиента. Ну пришлют тебе кривой пакет, с правильным ID, - что сделает твоя автоматическая десериализация?

#12
14:37, 4 авг 2011

Скорость отличная, всё что нужно хэшируется при старте приложения, далее лишь ссылки туда-сюда (статичные списки и хаш таблицы т.п.).
Кроме ID, есть также и другая информация, плюс, если прийдёт пакет с правильным ID но где-либо данные подменяны, или длина пакета ниже чем ожидается и многое другое, всё это без проблем держиться, и воспринимается как полагается.
Размер пакетов почти такой же как если работать напрямую с бинарником, ничего лишнего не пишется.
Скорость, миллионы пакетов в миллисекунды - этого достаточно. Больше жрёт сама конвертация данных из бинарных в другие, либо обработка данных пакета на уровне логики, нежели фабрика, которая лишь связывает списки и делает немного автоматической работы.

switch - не страшно, но видеть switch'и на 100 case'ов - уф какая какаша будет.
У меня же, пишешь модуль пользователей, выводишь в разные файлы, и пишешь лишь extensions и объявляешь callback'и с аттрибутами для авторегистрации в список событий для определённого пакета. Таким образом всё шустро, уложено где нужно и вполне устраивает.
Тем более, я непишу сервер для 10к+ игроков, а пишу сервер для весьма модулируемого и удобного в разработке логики приложения. А это другие задачи, потребности и т.п.

Тут скорее выглядит с твоей стороны: "зачем писать и городить, всё автаматизировать, если и вот такой простой (прямой) подход и так работает?".
Ответ: "Чтобы не париться при разработки логики приложения, о пакетах, а заниматься именно логикой.".

#13
14:52, 4 авг 2011

MoKa
> Ответ: "Чтобы не париться при разработки логики приложения, о пакетах, а
> заниматься именно логикой.".

Пока ты паришься с методом автоматического задания ID, - пацаны уже написали сериализацию/десериализацию, и давно занимаются логикой.

> Тут скорее выглядит с твоей стороны: "зачем писать и городить, всё
> автаматизировать, если и вот такой простой (прямой) подход и так работает?".

Как ты оценил его простоту? Простой, - это вот такой

public PacketNo1
{
...
    public uint GetPacketID()
    {
        return 1000;
    }

    public void Serialize()
    {
        networkstream.AddData(GetPacketID());
    }
}

public PacketNo2
{
...
    public uint GetPacketID()
    {
        return 1001;
    }

    public void Serialize()
    {
        networkstream.AddData(GetPacketID());
    }
}

>и пишешь лишь extensions и объявляешь callback'и с аттрибутами для авторегистрации в список событий для определённого пакета.

Предполагается, что callback напишет за тебя компилятор? Ну и какой тогда смысл говорить про величину геморроя?

>но видеть switch'и на 100 case'ов - уф какая какаша будет.

Очень страшно,... можно поменять switch на массив

#14
15:55, 4 авг 2011

Чтобы не городить "рассуждения", опиши полный процесс по пунктам, что нужно чтобы объявить новый пакет который можно принять, и можно послать. Также опиши как ты принимаешь и работаешь с данными пакета.
И сравним где удобства в архитектуре проекта.

Страницы: 1 2 Следующая »
ПрограммированиеФорумОбщее

Тема в архиве.