поэтому у меня есть эта игра в реальном времени с сервером C++ с отключенным nagle, использующим библиотеку SFML, и клиент использование asyncsocket также отключает nagle. Я отправляю 30 пакетов каждую 1 секунду. Нет проблем с отправкой от клиента к серверу, но при отправке с сервера к клиентам некоторые пакеты мигрируют. Например, если я отправляю "a" и "b" в совершенно разных пакетах, клиент читает это как "ab". Это случается только один раз, но это создает настоящую проблему в игре.
И что я должен делать? Как я могу это решить? Может что-то в сервере? Может настройки ОС?
Чтобы было ясно: я НЕ использую nagle, но у меня все еще есть эта проблема. Я отключил и в клиенте, и в сервере.
Ответ 1 · рейтинг 1 · cnicutar
Вы должны отключить Nagle на обоих узлах. Возможно, вы захотите найти другой протокол, основанный на записи, например SCTP.
Редактировать2
Поскольку вы просите протокол, вот как я это сделаю:
Определите заголовок для сообщения. Допустим, я бы выбрал 32-битный заголовок.
Header:
MSG Length: 16b
Version: 8b
Type: 8b
Затем приходит настоящее сообщение, имеющее MSG Length байта.
Итак, теперь, когда у меня есть формат, как мне поступить?
Сервер
Когда я пишу сообщение, я добавляю контрольную информацию (на самом деле длина является наиболее важной) и отправляю все это целиком. Включение NODELAY или нет не имеет значения.
Клиент
Я постоянно получаю данные с сервера, верно? Так что я должен сделать что-то вроде read.
- Чтение байтов с сервера. Любая сумма может прийти. Продолжайте читать, пока не получите хотя бы 4 байта.
- Получив эти 4 байта, интерпретируйте их как заголовок и извлеките
MSG Length
- Продолжайте читать, пока не получите хотя бы
MSG Length байт. Теперь вы получили сообщение и можете его обработать.
Это работает независимо от параметров TCP (таких как NODELAY), ограничений MTU и т. д.
Ответ 2 · рейтинг 3 · Robᵩ
Например, если я отправляю "a" и "b" в совершенно разных пакетах, клиент читает это как "ab". Это случается только один раз, но это создает настоящую проблему в игре.
Я думаю, вы упустили из виду фундаментальную природу TCP: это потоковый протокол, а не пакетный. TCP не учитывает и не сохраняет границы данных отправителя. Иными словами, TCP может объединять (или разделять!) "пакеты", которые вы отправляете, и представлять их получателю так, как он хочет. Единственное ограничение, которое соблюдает TCP, заключается в следующем: если байт доставлен, он будет доставлен в том же порядке, в котором он был отправлен. (И ничто в Нэгле не меняет этого.)
Итак, если вы дважды вызываете send (или write) на сервере, отправляя эти шесть байтов:
"packet" 1: A B C
"packet" 2: D E F
Ваша клиентская сторона может recv (или read) любую из этих последовательностей байтов:
ABC / DEF
ABCDEF
AB / CD / EF
Если вашему приложению требуется знание границ между writes отправителя, то вы несете ответственность за сохранение и передачу этой информации.
Как уже говорили другие, есть много способов сделать это. Например, вы можете отправлять новую строку после каждого кванта информации. Так (частично) работают HTTP, FTP и SMTP.
Вы можете отправить длину пакета вместе с данными. Обобщенная форма для этого называется TLV, что означает "Тип, длина, значение". Отправьте поле типа фиксированной длины, поле фиксированной длины, а затем значение произвольной длины. Таким образом, вы узнаете, когда прочитали все значение и готовы к следующему TLV.
Вы можете сделать так, чтобы каждый отправляемый вами пакет был идентичен по длине.
Я полагаю, что есть и другие решения, и я полагаю, что вы можете придумать их самостоятельно. Но сначала вы должны осознать следующее: TCP может и будет объединять или разбивать пакеты вашего приложения. Вы можете полагаться на порядок доставки байтов, но не на что иное.