Я посылаю довольно много данных на сервер и с сервера для игры, которую я создаю.
В настоящее время я отправляю данные о местоположении следующим образом:
sendToClient((("UID:" + cl.uid +";x:" + cl.x)));
sendToClient((("UID:" + cl.uid +";y:" + cl.y)));
sendToClient((("UID:" + cl.uid +";z:" + cl.z)));
Очевидно, что он отправляет соответствующие значения X, Y и Z.
Будет ли эффективнее отправлять такие данные?
sendToClient((("UID:" + cl.uid +"|" + cl.x + "|" + cl.y + "|" + cl.z)));
networking
joehot200
источник
источник
Ответы:
Сегмент TCP имеет много накладных расходов. Когда вы отправляете 10-байтовое сообщение с одним TCP-пакетом, вы фактически отправляете:
В результате получается 42 байта трафика для транспортировки 10 байтов данных. Таким образом, вы используете только менее 25% доступной пропускной способности. И это еще не учитывает накладные расходы, которые потребляют низкоуровневые протоколы, такие как Ethernet или PPPoE (но их трудно оценить, потому что существует так много альтернатив).
Кроме того, многие небольшие пакеты создают большую нагрузку на маршрутизаторы, межсетевые экраны, коммутаторы и другое оборудование сетевой инфраструктуры, поэтому, когда вы, ваш поставщик услуг и ваши пользователи не инвестируете в высококачественное оборудование, это может стать еще одним узким местом.
По этой причине вы должны попытаться отправить все имеющиеся у вас данные сразу в одном сегменте TCP.
Что касается обработки потери пакетов : когда вы используете TCP, вам не нужно беспокоиться об этом. Сам протокол гарантирует, что все потерянные пакеты отправляются повторно, а пакеты обрабатываются по порядку, поэтому вы можете предполагать, что все отправленные вами пакеты будут доставлены на другую сторону, и они будут доставлены в порядке их отправки. Ценой этого является то, что при потере пакета ваш проигрыватель будет испытывать значительные задержки, поскольку один отброшенный пакет будет останавливать весь поток данных до тех пор, пока он не будет повторно запрошен и получен.
Когда это проблема, вы всегда можете использовать UDP. Но тогда вам нужно найти собственное решение для потерянных и неупорядоченных сообщений (это по крайней мере гарантирует, что сообщения, которые действительно приходят, прибывают полными и неповрежденными).
источник
Один большой (в пределах разумного) лучше.
Как вы сказали, потеря пакетов является основной причиной. Пакеты обычно отправляются в кадрах фиксированного размера, поэтому лучше взять один кадр с большим сообщением, чем 10 кадров с 10 маленькими.
Однако со стандартным TCP это на самом деле не проблема, если вы не отключите его. (Он называется алгоритмом Nagle , и для игр его следует отключить.) TCP будет ожидать фиксированного тайм-аута или до тех пор, пока пакет не «заполнится». Где "full" - это немного магическое число, частично определяемое размером кадра.
источник
recv()
вызов для каждогоsend()
вызова, что и ищет большинство людей. Использование протокола, который гарантирует это, как, впрочем, и UDP. «Когда все, что у вас есть, это TCP, все выглядит как поток»Все предыдущие ответы неверны. На практике не имеет значения, что вы делаете один длинный
send()
звонок или несколько маленькихsend()
вызовов.Как утверждает Филлип, сегмент TCP имеет некоторые накладные расходы, но, как программист приложения, вы не контролируете, как создаются сегменты. Проще говоря:
ОС полностью бесплатна для буферизации всех ваших данных и отправки их в один сегмент, или взять длинный и разбить его на несколько небольших сегментов.
Это имеет несколько последствий, но наиболее важным является то, что:
Причина этого заключается в том, что TCP является потоковым протоколом. TCP обрабатывает ваши данные как длинный поток байтов и абсолютно не имеет понятия «пакеты». Когда
send()
вы добавляете байты в этот поток,recv()
вы получаете байты с другой стороны. TCP будет активно буферизовать и разбивать ваши данные, где сочтет нужным, чтобы ваши данные передавались на другую сторону как можно быстрее.Если вы хотите отправлять и получать «пакеты» по протоколу TCP, вам необходимо внедрить маркеры запуска пакетов, маркеры длины и так далее. Как насчет использования протокола, ориентированного на сообщения, такого как UDP? UDP гарантирует, что один
send()
вызов преобразуется в одну отправленную дейтаграмму и в одинrecv()
вызов!источник
recv()
, вам необходимо создать собственную буферизацию, чтобы компенсировать это. Я бы оценил это в той же сложности, что и реализация надежности по UDP.while(1) { uint16_t size; read(sock, &size, sizeof(size)); size = ntoh(size); char message[size]; read(sock, buffer, size); handleMessage(message); }
(для краткости опущена обработка ошибок и частичное чтение, но она не сильно меняется) Делать этоselect
не намного сложнее, и если вы используете TCP, вам, вероятно, все равно придется буферизовать частичные сообщения. Реализация надежной надежности по UDP намного сложнее, чем это.Многие небольшие пакеты в порядке. На самом деле, если вы беспокоитесь о накладных расходах TCP, просто вставьте
bufferstream
до 1500 символов (или каковы бы ни были ваши MTU TCP, лучше всего запрашивать его динамически), и решите проблему в одном месте. Это сэкономит вам ~ 40 байт за каждый дополнительный пакет, который вы в противном случае создали бы.Тем не менее, все-таки лучше отправлять меньше данных, и в этом вам помогают создание более крупных объектов. Конечно, отправлять меньше,
"UID:10|1|2|3
чем отправлятьUID:10;x:1UID:10;y:2UID:10;z:3
. На самом деле, на этом этапе вам также не следует изобретать велосипед, используйте библиотеку типа protobuf, которая может уменьшить такие данные до 10-байтовой строки или меньше.Единственное, что вы не должны забывать, это вставить
Flush
команды в свой поток в соответствующих местах, потому что, как только вы прекратите добавлять данные в свой поток, он может ждать бесконечно, прежде чем отправлять что-либо. Действительно проблематично, когда ваш клиент ожидает эти данные, и ваш сервер не отправит ничего нового, пока клиент не отправит следующую команду.Потеря посылки - это то, что вы можете повлиять здесь, незначительно. Каждый отправленный вами байт может быть поврежден, и TCP автоматически запросит повторную передачу. Меньшие пакеты означают меньшую вероятность повреждения каждого отдельного пакета, но поскольку они увеличивают накладные расходы, вы отправляете еще больше байтов, еще больше увеличивая шансы потерянного пакета. Если пакет потерян, TCP будет буферизовать все последующие данные до тех пор, пока отсутствующий пакет не будет повторно отправлен и получен. Это приводит к большой задержке (пинг). Хотя общая потеря пропускной способности из-за потери пакетов может быть незначительной, более высокий пинг нежелателен для игр.
Итог: отправляйте как можно меньше данных, отправляйте большие пакеты и не пишите свои собственные низкоуровневые методы для этого, но полагайтесь на хорошо известные библиотеки и методы, такие как
bufferstream
и protobuf, для обработки тяжелой работы.источник
bufferstream
тривиально, поэтому я назвал это методом. Вы по-прежнему хотите обрабатывать это в одном месте и не интегрировать свою логику буфера с кодом сообщения. Что касается Сериализации Объектов, я очень сомневаюсь, что вы получите что-то лучше, чем тысячи человеко-часов, вложенных другими, даже если вы ДЕЛАЕТЕ попытку, я настоятельно рекомендую вам сравнить свое решение с известными реализациями.Хотя я и новичок в сетевом программировании, я хотел бы поделиться своим ограниченным опытом, добавив несколько моментов:
Что касается измерений, метрики, которые следует учитывать, являются:
Как уже упоминалось, если вы обнаружите, что в некотором смысле вы не ограничены и можете использовать UDP, сделайте это. Существует несколько реализаций на основе UDP, поэтому вам не нужно изобретать велосипед или работать против многолетнего опыта и проверенного опыта. Такие реализации, о которых стоит упомянуть:
Вывод: поскольку реализация UDP может превзойти (в 3 раза) TCP-реализацию, имеет смысл рассмотреть ее, как только вы определили свой сценарий как UDP-дружественный. Имейте в виду! Реализация полного стека TCP поверх UDP всегда плохая идея.
источник