#cpp #linux #сокет #tcp #udp
Необходимо отправить данные типа:float/int/char через сокет, как организовать "упаковку" на стороне отправителя что бы отправить всё одним пакетом, и "распаковать" на принимающей стороне. В сети нашёл скрин приложения для игры, как упаковать так же?
Ответы
Ответ 1
Примитивный способ для простых случаев - описать структуру данных: struct Data { int a; float b; char c[32]; } data; Записать её в сокет на передающей стороне (send(socket, &data, sizeof(data), 0) и на принимающей прочитать в точно такую же структуру (recv(socket, &data, sizeof(data), 0)). Очень важно чтобы стуктура на обоих сторонах (передающей и приемной) была идентичной по расположению в памяти (одинаковые размеры типов, одинаковый порядок байтов в системе, одинаковое выравнивание полей структуры, одинаковое представление чисел с плавающей точкой). Иначе получаем не те данные, что отправили. На практике, если принимающая сторона ещё и на другом языке написана, получим лишнюю возню и простор для появления ошибок. Следующий вариант - набивать буфер данных вручную: int foo = 42; long bar = 0; std::string str; str.append((char*)&foo, sizeof(int)); str.append((char*)&bar, sizeof(long)); Здесь уже нет проблемы с выравниванием полей структуры как в первом варианте, т.к. данные мы склеиваем сами, без промежутков. Но остальные проблемы пока ещё с нами (по прежнему порядок байтов, размеры типов, представление чисел с плавающей точкой должны быть идентичными на передатчике и приемнике). Ручная, побайтовая набивка потока. uint32_t foo = 42; std::vectorbuffer; buffer.push_back(static_cast (foo >> 0)); buffer.push_back(static_cast (foo >> 8)); buffer.push_back(static_cast (foo >> 16)); buffer.push_back(static_cast (foo >> 24)); Здесь просто берем каждый кусок данных и вручную переносим в выходной поток в независимом от системы порядке. Разбирать тоже придется вручную. Наиболее универсальный способ, т.к. все аспекты генерируемого потока контролируем сами. Для удобства можно написать класс сериализатора/десериализатора для требуемых типов (включая пользовательские). Со временем (а может быть и сразу) добавляются сложности, связанные с изменением передаваемых данных (например понадобилось передать дополнительные данные или какие-то старые уже стали неактуальными). Особенно если приемник должен принимать данные и в старом формате и в новом. Придется добавлять какие-то идентификаторы версии. Дополнительно нужно обработать случаи, когда нужно передать опциональные данные (которые могут отсутствовать) или данные динамического размера (массивы). Чтобы не решать все эти задачи самостоятельно, можно взять готовое решение, например protobuf от google. Поддерживает разные языки, имеет систему версий, поддержку комплексных данных. Или немного более простое решение (но и более быстрое), тоже от google flatbuffers. Если объем передаваемой информации не критичен, возможно будет удобным формировать данные в виде json (например с помощью https://github.com/nlohmann/json). Если на принимающей стороне JavaScript программист, он будет вам очень благодарен (да и не только JavaScript программист). Также, как программисту из типизированного языка, рекомендую использовать схемы для проверки json. Как альтернативу json можно взять messagepack, который "как json", но компактнее. Если нужно ещё компактнее, можно пожать передаваемую строку с помощью zlib например. Для всех вариантов также надо учитывать, что передавать указатели бессмысленно, т.к. на принимающей стороне они будут указывать неизвестно куда. Также понимать тонкости передачи данных по сети. К примеру данные, отправленные по UDP, могут не дойти до получателя, данные отправленные по TCP могут быть фрагментированы или склеены с соседними при получении и т.п. Возможно стоит подумать о готовых сетевых библиотеках, например RakNet, которая включает в себя практически все для построения мультиплеерной игры. Ответ 2
В функциях отправки данных на другой сокет (например, send) и функциях приема данных (например, recv) одним из параметров всегда является указатель на буфер с этими данными (байтами). Необходимо предварительно сформировать этот буфер. Сделать это можно очень разными способами. Например, если структура передаваемых данных динамическая и/или таких структур очень много, то можно формировать буфер, так сказать, "на лету". Т.е. мы нужные данные постепенно, по мере их получения, запихиваем в буфер. std::string buffer; uint32_t i32 = 0x32fe56ad; float f = 1.0; std::string str = "1234"; uint8_t sz = str.size(); buffer.append((char*)&i32, sizeof(i32)); buffer.append((char*)&f, sizeof(f)); buffer.append((char*)&sz, sizeof(sz)); buffer.append(str); std::cout << "lenght message: " <Ответ 3
Правильным подходом будет использование сериализации данных. Например: Protocol Buffers, JSON, XML, ASN.1, и т.п. Сравнительная таблица.
Комментариев нет:
Отправить комментарий