Ответ 1
Если вы выполняете программирование на уровне сокетов, то независимо от того, сколько портов вы открываете для каждого типа сообщения, вам все равно нужно иметь какой-то заголовок. Даже если это всего лишь длина остальной части сообщения. Сказав, что легко добавить простую структуру заголовка и хвоста в сообщение. Я бы подумал, что проще иметь дело только с одним портом на стороне клиента.
Я считаю, что современные MMORPG (и, возможно, даже старые) имели два уровня серверов. Серверы входа, которые проверяют вас как платежного клиента. После проверки они передают вас игровому серверу, который содержит всю информацию о мире игры. Тем не менее, это все еще требует, чтобы клиент имел один сокет открытым, но не запретил иметь больше.
Кроме того, большинство MMORPGS также шифруют все свои данные. Если вы пишете это как упражнение для удовольствия, тогда это не будет иметь большого значения.
Для разработки/написания протоколов в целом вот о чем я беспокоюсь:
порядок байтов
Всегда ли клиент и сервер гарантированно имеют одинаковую контенту. Если нет, мне нужно обработать это в моем коде сериализации. Существует несколько способов обработки endianess.
- Игнорировать его - Очевидно, что плохой выбор
- Укажите соответствие протокола. Это то, что старые протоколы делали/делали, следовательно, термин "порядок сети", который всегда был большим аргументом. На самом деле не имеет значения, какую конкретизацию вы укажете только, что вы указываете тот или иной. Некоторые хрустящие старые сетевые программисты встанут на руки, если вы не пользуетесь большой энтузиазмом, но если ваши серверы и большинство клиентов мало ориентированы, вы действительно не покупаете себе ничего, кроме дополнительной работы, сделав протокол большим эндиантом.
- Отметьте Endianess в каждом заголовке. Вы можете добавить куки файл, который будет сообщать вам конечную цель клиента/сервера и дать каждому сообщению преобразование соответствующим образом по мере необходимости. Дополнительная работа!
- Сделайте свой протокол неактивным - если вы отправляете все как строки ASCII, то endianess не имеет значения, но также намного более неэффективно.
Из 4 я обычно выбирал бы 2 и указывал бы, что endianess будет таковой у большинства клиентов, которые теперь дни будут немногочисленными.
Обратная и обратная совместимость
Должен ли протокол быть передовым и обратным. Ответ должен всегда быть да. В этом случае это определит, как я проектирую по всему протоколу с точки зрения управления версиями и как создается каждое отдельное сообщение для обработки незначительных изменений, которые на самом деле не должны быть частью процесса управления версиями. Вы можете использовать это и использовать XML, но вы теряете большую эффективность.
Для общего управления версиями я обычно проектирую что-то простое. Клиент отправляет сообщение с версией, указывающее, что он говорит версию X.Y, если сервер может поддерживать эту версию, он отправляет обратно сообщение, подтверждающее версию клиента, и все идет вперед. В противном случае он удаляет клиента и завершает соединение.
Для каждого сообщения у вас есть что-то вроде следующего:
+-------------------------+-------------------+-----------------+------------------------+
| Length of Msg (4 bytes) | MsgType (2 bytes) | Flags (4 bytes) | Msg (length - 6 bytes) |
+-------------------------+-------------------+-----------------+------------------------+
Длина, очевидно, говорит вам, сколько времени занимает сообщение, не считая самой длины. Тип сообщения MsgType. Для этого всего два байта, так как 65356 - множество типов сообщений для приложений. Флаги, чтобы вы знали, что сериализовано в сообщении. Это поле в сочетании с длиной - это то, что дает вам передовую и обратную совместимость.
const uint32_t FLAG_0 = (1 << 0);
const uint32_t FLAG_1 = (1 << 1);
const uint32_t FLAG_2 = (1 << 2);
...
const uint32_t RESERVED_32 = (1 << 31);
Затем ваш код десериализации может сделать что-то вроде следующего:
uint32 length = MessageBuffer.ReadUint32();
uint32 start = MessageBuffer.CurrentOffset();
uint16 msgType = MessageBuffer.ReadUint16();
uint32 flags = MessageBuffer.ReadUint32();
if (flags & FLAG_0)
{
// Read out whatever FLAG_0 represents.
// Single or multiple fields
}
// ...
// read out the other flags
// ...
MessageBuffer.AdvanceToOffset(start + length);
Это позволяет добавлять новые поля в конец сообщений без необходимости пересматривать весь протокол. Это также гарантирует, что старые серверы и клиенты будут игнорировать флаги, о которых они не знают. Если они должны использовать новые флаги и поля, то вы просто изменяете общую версию протокола.
Использовать работу с рамой или нет
Существуют различные сетевые структуры, которые я бы рассмотрел для использования в бизнес-приложении. Если бы у меня не было особых проблем, я бы пошел со стандартной структурой. В вашем случае вы хотите изучить программирование на уровне сокетов, так что это уже вопрос для вас.
Если вы используете фреймворк, убедитесь, что он затрагивает две проблемы выше или, по крайней мере, не мешает вам, если вам нужно настроить его в этих областях.
Я имею дело с третьей стороной
Во многих случаях вы можете иметь дело с сторонним сервером/клиентом, с которым вам нужно общаться. Это подразумевает несколько сценариев:
- У них уже установлен протокол - просто используйте свой протокол.
- У вас уже определен протокол (и они готовы его использовать) - снова просто используйте определенный протокол
- Они используют стандартную платформу (на основе WSDL и т.д.). Используйте фреймворк.
- Ни одна из сторон не имеет определенного протокола. Попробуйте определить оптимальное решение, основанное на всех факторах (все те, которые я упомянул здесь), а также уровень их компетенции (по крайней мере, насколько это возможно). Независимо убедитесь, что обе стороны согласны и понимают протокол. Из опыта это может быть болезненным или приятным. Это зависит от того, с кем вы работаете.
В любом случае вы не будете работать с третьей стороной, так что это действительно просто добавлено для полноты.
Мне кажется, что я мог бы написать гораздо больше об этом, но он довольно длинный. Я надеюсь, что это поможет, и если у вас есть какие-то конкретные вопросы, просто спросите об Stackoverflow.
Редактирование ответа на вопрос knoopx: