Как передать структуру через сокет
Мой вопрос: достаточно ли использовать JUST pragma pack или просто сериализацию? Или мне нужно использовать оба?
Кроме того, поскольку сериализация - это процесс, интенсивно использующий процессор, это резко снижает производительность, поэтому как лучше всего сериализовать структуру БЕЗ использования внешней библиотеки (мне бы понравился пример кода / алгоритма)?
Для переносимой отправки структур по сети вам понадобится следующее:
Упакуйте конструкцию. Для gcc и совместимых компиляторов сделайте это с помощью __attribute__((packed)) .
Не используйте какие-либо члены, кроме целых чисел без знака фиксированного размера, других упакованных структур, удовлетворяющих этим требованиям, или массивов любых из первых. Целые числа со знаком тоже в порядке, если только ваш компьютер не использует представление с дополнением до двух.
Решите, будет ли ваш протокол использовать прямое или прямое кодирование целых чисел. Выполняйте преобразования при чтении и записи этих целых чисел.
Кроме того, не принимают указатели на элементы упакованной структуры , за исключением тех, которые имеют размер 1 или других вложенных упакованных структур. См. этот ответ.
Что касается функций преобразования порядка байтов, то в вашей системе могут использоваться htons() / ntohs() и htonl() / ntohl() для 16- и 32-битных целых чисел, соответственно, преобразовать в / из прямого порядка байтов. Однако я не знаю ни одной стандартной функции для 64-битных целых чисел или преобразования в / из прямого порядка байтов. Вы можете использовать мои функции преобразования порядка байтов; если вы это сделаете, вы должны указать ему порядок байтов вашего компьютера , указав BADVPN_LITTLE_ENDIAN или BADVPN_BIG_ENDIAN .
Что касается целых чисел со знаком, функции преобразования могут быть безопасно реализованы таким же образом, как и те, которые я написал и связал (перестановка байтов напрямую); просто измените неподписанный на подписанный.
Google Protocol Buffer предлагает отличное решение этой проблемы. См. Здесь Google Protobol Buffer - реализация C
Создайте файл .proto на основе структуры полезной нагрузки и сохраните его как payload.proto .
Скомпилируйте файл .proto, используя
Это создаст заголовочный файл payload.pb-c.h и соответствующий ему payload.pb-c.c в вашем каталоге.
Создайте файл server.c и включите файлы заголовков protobuf-c.
На принимающей стороне client.c
Убедитесь, что вы компилируете свои программы с флагом -lprotobuf-c
Пакет Pragma используется для двоичной совместимости вашей структуры на другом конце. Поскольку сервер или клиент, которому вы отправляете структуру, может быть написан на другом языке или построен с другим компилятором c или с другими параметрами компилятора c.
Сериализация, как я понимаю, делает из вас поток байтов struct. Когда вы пишете структуру в сокете, вы производите сериализацию.
Обычно сериализация дает несколько преимуществ перед, например, отправка битов структуры по сети (например, fwrite ).
- Это происходит индивидуально для каждых неагрегированных атомарных данных (например, int).
- Он точно определяет формат последовательных данных, передаваемых по сети.
- Таким образом, он имеет дело с неоднородной архитектурой: отправляющие и принимающие машины могут иметь разную длину слова и порядок следования байтов.
- При небольшом изменении типа он может стать менее хрупким. Поэтому, если на одной машине работает старая версия вашего кода, она может взаимодействовать с машиной с более новой версией, например один с char b[80]; вместо char b[64];
- Он может иметь дело с более сложными структурами данных - векторами переменного размера или даже с хеш-таблицами - логическим способом (для хеш-таблицы передать ассоциацию, ..)
Очень часто создаются процедуры сериализации. Еще 20 лет назад для этой цели уже существовал RPCXDR, а примитивы сериализации XDR все еще присутствуют во многих libc.
Зачем вам это делать, если есть хорошие и быстрые библиотеки сериализации, такие как Message Pack, которые делают всю тяжелую работу за вас , а в качестве бонуса они предоставляют вам кросс-языковую совместимость вашего протокола сокетов?
Для этого используйте Message Pack или другую библиотеку сериализации.
Это зависит от того, можете ли вы быть уверены, что ваши системы на обоих концах соединения однородны или нет. Если вы уверены, на все времена (чего большинство из нас не может быть), тогда вы можете воспользоваться некоторыми сокращениями, но вы должны знать, что это ярлыки.
И аналогичный read() .
Однако, если есть вероятность, что системы могут отличаться, вам необходимо установить, как данные будут передаваться формально. Вы могли бы хорошо линеаризовать (сериализовать) данные - возможно, причудливо с помощью чего-то вроде ASN.1 или, возможно, проще, с форматом, который можно легко перечитать. Для этого часто полезен текст - его легче отлаживать, когда вы видите, что идет не так. В противном случае вам необходимо определить порядок байтов, в котором передается int , и убедиться, что передача следует этому порядку, и строка, вероятно, получает счетчик байтов, за которым следует соответствующий объем данных (подумайте, следует ли передавать конечный ноль или нет), а затем некоторое представление числа с плавающей запятой. Это более неудобно. Не так уж и сложно написать функции сериализации и десериализации для обработки форматирования. Сложная часть - это разработка (принятие решения) протокола.
Без спецификации легко задавать вопросы, на которые просто невозможно ответить. Если что-то пойдет не так, кто виноват? В спецификации виноват конец, который не соответствует спецификации. (И если оба конца соответствуют спецификации, но она по-прежнему не работает, значит, спецификация ошибочна.)
Если у вас есть спецификация, гораздо проще отвечать на вопросы о том, как следует проектировать тот или иной конец.
Я также настоятельно рекомендую не разрабатывать сетевой протокол с учетом особенностей вашего оборудования. По крайней мере, не без доказанной проблемы с производительностью.
например, я хочу send/recv весь company использование сокета (UDP). Итак, отправьте и recv один раз.
формулировка вашего вопроса предполагает, что то, что вы ищете, это:
это в основном сбросит всю память company struct в сокет. Это НЕ БУДЕТ РАБОТАТЬ в этом случае. И даже когда это вроде как работает, это действительно плохая идея. Причина, по которой это не сработает, заключается в том, что vector s не содержат данных напрямую. Они указывают на него. Это означает, что когда вы сбрасываете структуру, содержащую векторы, в сокет, вы будете сбрасывание указателей на память, но не на то, на что указывают. Это приведет к (в лучшем случае) сбоям на принимающей стороне.
это будет работать для отдельных person или car объекты. Они не содержат указателей, и поэтому их память содержит все необходимые значения'
на стороне отправки:
на принимающей стороне:
но это все еще плохая идея. Он полагается на отправляющую и принимающую стороны, имеющие точно такой же макет памяти для их структур. Это может быть неверно по ряду причин. Некоторые из них включают в себя один на чипе PowerPC, а другой-на чипе Intel x86. Или один из них находится на машине Windows, скомпилированной с Visual Studio, а другой-на машине Linux, скомпилированной с gcc. Или, может быть, кто-то изменил некоторые флаги компилятора, которые заставляют макет структуры по умолчанию отличаться. По многим причинам.
действительно, Вы должны использовать структуру сериализации как все здесь и предполагали. Я бы предложил буферы протокола Google или повысить структуру сериализации что другие люди уже связаны. Но есть много других.
другой фреймворк сериализации, который следует упомянуть, потому что он невероятно быстр (почти так же быстро, как прямой сброс образа памяти структуры в сокет), является капитан Прото.
Если вы должны сделать много из них, вы можете посмотреть в бережливости. Он автоматически сгенерирует весь необходимый код для сериализации всех структур, поэтому вам не придется делать это вручную.
Они поддерживают строки тоже очень практично.
другое дело, он отправляет двоичные данные как таковые, поэтому вам не придется преобразовывать числа в строки и наоборот. Это намного быстрее сделать, если большинство ваших данных не являются строками.
используйте библиотеку сериализации Boost:
Это, пожалуй, самый надежный, универсальный, надежный, простой в использовании и даже кросс-платформенный способ для таких задач.
как уже говорили другие, использование какой-то библиотеки сериализации обеспечит наиболее надежное (и, вероятно, самое простое в обслуживании) решение. Если, однако, вы действительно хотите реализовать все это самостоятельно, то следующее показывает "идею" того, как, возможно, подойти к нему. Следующее выделяет буфер, а затем заполняет его содержимым вектора employees. Переменная c считается типа company .
Сокеты (англ. socket — разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.
Принципы сокетов¶
Каждый процесс может создать слушающий сокет (серверный сокет) и привязать его к какому-нибудь порту операционной системы (в UNIX непривилегированные процессы не могут использовать порты меньше 1024). Слушающий процесс обычно находится в цикле ожидания, то есть просыпается при появлении нового соединения. При этом сохраняется возможность проверить наличие соединений на данный момент, установить тайм-аут для операции и т.д.
Каждый сокет имеет свой адрес. ОС семейства UNIX могут поддерживать много типов адресов, но обязательными являются INET-адрес и UNIX-адрес. Если привязать сокет к UNIX-адресу, то будет создан специальный файл (файл сокета) по заданному пути, через который смогут сообщаться любые локальные процессы путём чтения/записи из него (см. Доменный сокет Unix). Сокеты типа INET доступны из сети и требуют выделения номера порта.
Обычно клиент явно подсоединяется к слушателю, после чего любое чтение или запись через его файловый дескриптор будут передавать данные между ним и сервером.
Основные функции¶
socket()¶
Создаёт конечную точку соединения и возвращает файловый дескриптор. Принимает три аргумента:
domain указывающий семейство протоколов создаваемого сокета
- AF_INET для сетевого протокола IPv4
- AF_INET6 для IPv6
- AF_UNIX для локальных сокетов (используя файл)
type
- SOCK_STREAM (надёжная потокоориентированная служба (сервис) или потоковый сокет)
- SOCK_DGRAM (служба датаграмм или датаграммный сокет)
- SOCK_RAW (Сырой сокет — сырой протокол поверх сетевого уровня).
protocol
Протоколы обозначаются символьными константами с префиксом IPPROTO_* (например, IPPROTO_TCP или IPPROTO_UDP). Допускается значение protocol=0 (протокол не указан), в этом случае используется значение по умолчанию для данного вида соединений.
Функция возвращает −1 в случае ошибки. Иначе, она возвращает целое число, представляющее присвоенный дескриптор.
Пример на Python
Связывает сокет с конкретным адресом. Когда сокет создается при помощи socket(), он ассоциируется с некоторым семейством адресов, но не с конкретным адресом. До того как сокет сможет принять входящие соединения, он должен быть связан с адресом. bind() принимает три аргумента:
- sockfd — дескриптор, представляющий сокет при привязке
- serv_addr — указатель на структуру sockaddr, представляющую адрес, к которому привязываем.
- addrlen — поле socklen_t, представляющее длину структуры sockaddr.
Возвращает 0 при успехе и −1 при возникновении ошибки.
Пример на Python
Автоматическое получение имени хоста.
listen()¶
Подготавливает привязываемый сокет к принятию входящих соединений. Данная функция применима только к типам сокетов SOCK_STREAM и SOCK_SEQPACKET. Принимает два аргумента:
- sockfd — корректный дескриптор сокета.
- backlog — целое число, означающее число установленных соединений, которые могут быть обработаны в любой момент времени. Операционная система обычно ставит его равным максимальному значению.
После принятия соединения оно выводится из очереди. В случае успеха возвращается 0, в случае возникновения ошибки возвращается −1.
Пример на Python
accept()¶
Используется для принятия запроса на установление соединения от удаленного хоста. Принимает следующие аргументы:
- sockfd — дескриптор слушающего сокета на принятие соединения.
- cliaddr — указатель на структуру sockaddr, для принятия информации об адресе клиента.
- addrlen — указатель на socklen_t, определяющее размер структуры, содержащей клиентский адрес и переданной в accept(). Когда accept() возвращает некоторое значение, socklen_t указывает сколько байт структуры cliaddr использовано в данный момент.
Функция возвращает дескриптор сокета, связанный с принятым соединением, или −1 в случае возникновения ошибки.
Пример на Python
connect()¶
Устанавливает соединение с сервером.
Некоторые типы сокетов работают без установления соединения, это в основном касается UDP-сокетов. Для них соединение приобретает особое значение: цель по умолчанию для посылки и получения данных присваивается переданному адресу, позволяя использовать такие функции как send() и recv() на сокетах без установления соединения.
Загруженный сервер может отвергнуть попытку соединения, поэтому в некоторых видах программ необходимо предусмотреть повторные попытки соединения.
Возвращает целое число, представляющее код ошибки: 0 означает успешное выполнение, а −1 свидетельствует об ошибке.
Пример на Python
Передача данных¶
Для передачи данных можно пользоваться стандартными функциями чтения/записи файлов read и write, но есть специальные функции для передачи данных через сокеты:
Есть еще несколько дополнительных вопросов:
Вод код сервера:
Как все понимают, эта программа принимает строку от клиента и отражает ее обратно.
Цель:
Сервер должен отражать принятую строку не только тому клиенту который ее послал, а всем подключенным клиентам в этот момент времени.
Вопросы:
1. Как узнать число подключенных клиентов к серверу?
2. Как отправлять данные конкретно какому-либо клиенту подключенному к серверу.
Думаю, что ответы на поставленные мной вопросы решат поставленную мною задачу.
Заранее спасибо.
Цитата |
---|
Максим пишет: 1. Как узнать число подключенных клиентов к серверу? |
Цитата |
---|
Максим пишет: 2. Как отправлять данные конкретно какому-либо клиенту подключенному к серверу. |
sxd, данный сервер должен работать с fork, поэтому штука с потоками не прокатит по сл. причине:
Подключение клиента:
Сервер записывает дочерний PID и IP подключенного клиента в таблицу БД MYSQL.
Завершение соединения:
При перехвате сигнала SIGCHLD функция waitpid возвращает PID завершенного дочернего процесса, по этому PID выполняется удаление записи из БД.
Таким образом кол-во записей в БД соответствует кол-ву подключенных клиентов к серверу.
В итоге перед вызовом функции send
можно получить PID'ы всех дочерних процессов и IP всех подключенных клиентов, только вот что с этим всем добром делать?
Каким образом можно использовать эту информацию для отражения принятой строки сервером всем подключенным клиентам?
Репутация: нет
Всего: нет
Помогите передать и принять структуру по сети. Что надо передавать в полях данные и размер?
struct Contr short mouse_y;
BYTE action;>;
Contr con;
send(s,(char*)&con,sizeof(Contr),0);
Репутация: 9
Всего: 14
Все зависит от того как устроена программа.
Единственный способ определить границы возможного - это выйти за эти границы, в невозможное.Артур Кларк.
Репутация: нет
Всего: нет
Репутация: 9
Всего: 14
Артур Кларк.
Репутация: 1
Всего: 4
Репутация: 1
Всего: 1
Репутация: 9
Всего: 14
Артур Кларк.
Репутация: 1
Всего: 1
Да ладно тебе ptr просто никогда не сталкивались =)))
Мне чёт кажется вот так :
struct Contr short mouse_y;
BYTE action;>;
Contr con;
send(s,con,sizeof(con),0);
или так:
send(s,con,strlen(con),0);
Ну незнаю я =))) Хотя скоро пригодится =)
Репутация: 1
Всего: 4
send(s,(constr char*)&con,sizeof(Contr),0); банально! я посмотрел ещё тогда в мсдн и в купил! там же чётко написанно какой тип данных передаётся! тема старая, решённая! если автор забыл поставить галочку о том что тема решена! ненадо её поднимать!
drZmeu , не трогой темы тарые!
Репутация: нет
Всего: 1
Народ помогите и мне передать структуру. Вот что я написал:
Какая-то ошибка
И еще вопрос: как принимать структуру? Что чему присваивать?
Репутация: 1
Всего: 1
С отправкой структуры всё понятно!
А вот с приёмом вобшем у мну такойже вопрос как и у Serqio !
И еще вопрос: как принимать структуру? Что чему присваивать?
К примеру я отправил такую структуру на сервер! Как сервер должен разбирать принятые данные.
Тоесть у сервера есть похожая структура:
Как мне присвоить текст в szRecvSt и байт в szByteSt .
Ещё раз извеняюсь за поднятие старой темы. написал тут чтоб не создавать новую !
Репутация: нет
Всего: 6
Репутация: 1
Всего: 1
>Не надо её отправлять целеком! Если в структуре есть сложные элементы, такие как строки, то >отправлять её надо по частям, и принимать так же. А если у вас там указатели, сами ведь >понимаете такое:код C++
>1:
> send(s,con,sizeof(con),0);
>
>
>
>
>НЕ ПРОКАТИТ!
Репутация: нет
Всего: 18
Почему незя, можно. У тебя 1 Кб + 1 байт передаются. Вполне нормальный размер чтобы передавать его целиком. Потом получаешь (recv) и приводишь буфер к твоему типу.
Репутация: 9
Всего: 14
Единственный способ определить границы возможного - это выйти за эти границы, в невозможное.
Артур Кларк.
Репутация: 1
Всего: 1
Репутация: нет
Всего: 5
Репутация: 9
Всего: 14
Да, recv возвращает только то, что пришло на данный момент. Поэтому количество байт может быть не больше того, что было затребовано.
P.S. если хочешь задать какой-нибудь вопрос, то лучше для этого создавать новую тему. Тем более что вопрос никак не связан с текущей темой.
Единственный способ определить границы возможного - это выйти за эти границы, в невозможное.Артур Кларк.
Репутация: нет
Всего: нет
После тово как получил структуру по сокету и запихал её в буфер, хочу привести тот самый буфер к типу структуры,
но компилятор кричит "так не пойдёт". Не пойму где я там касяк спорол
Репутация: нет
Всего: 5
Репутация: нет
Всего: нет
Репутация: нет
Всего: нет
Вот ещё небольшая проблемка.
по сокету отправляю структуру в сторону клиента, но тот ничего не получает, хотя блок else выполняется.
В структуре int и указатель на char.
А вот от клиента:
Код |
struct Message char *_message; int messageType; >; |
Репутация: 1
Всего: 158
правильно сдается - ты пересылаешь указатель на строку, а не саму строку.
структура должна быть примерно такого вида:
и при получении надо учитывать тот момент, что данные, отосланные одним вызовом send, вовсе не обязательно придут одним куском - вполне возможно их нужно будет читать несколькими вызовами recv.
Репутация: нет
Всего: нет
а ты мне нравишься
Репутация: 1
Всего: 20
Все так весело передают структуры по сокетам, даже не задумываясь о выравнивании струкуры в памяти.
Только лишь dumb не стал этим принебрегать (на то он и эксперт).
Репутация: нет
Всего: 306
Комрады если уж сыр - бор про передачу инфы через сокеты - то как насчет юзанья структуры WSANETWORKEVENTS. А именно что бы сохранить инфу от сервера на клиенте скажим хотя бы в файл. Кто нибудь посоветует что нибудь? Пжаста!
Репутация: 1
Всего: 158
Репутация: нет
Всего: 306
Репутация: нет
Всего: нет
А еще int может иметь разный размер, разную endianness. Вообще говоря.
Репутация: нет
Всего: 1
Приветствую!
Не стал создавать новую тему - спрошу здесь
Код |
struct Message AnsiString MsgType; AnsiString MsgIP; AnsiString MsgText; >; Message MsgRecv; Message MsgSend; |
все передается и принимаеться нормально. Но далее происходит вот что
Как только выполнение программы приходит на строку с if ((MsgRecv.MsgType) == "0100") условие срабатывает. Хотя в принятом MsgRecv.MsgType = "0004". Смотрю в отладчике - да, все принимается ок, MsgType = "0004". Программа переходит на строку с проверкой условия - и MsgType становится равно "0100". Пробовал менять значения в условии - что бы ни поставил, всегда MsgType становится равным ему. Как будто в if-е не "==" стоит, а "=".
В чем может быть дело?
Репутация: нет
Всего: 5
здесь явное нарушение безопасности типов. Странно, что потом не выскакивает Access violation, но ничего, потом выскочит .
Хотите передать 3 строки за раз, придётся сначала перевести строки в char* , PChar или PAnsiChar
потом скопировать все 3 строки друг за другом в 1 блок памяти и передавать этот блок целиком.
С помощью "C" структур так сделать нельзя, потому что только последнее поле структуры может иметь вариабельный размер.
Я бы сделал по-другому, написал 2 функции:
sendMessage() и recvMessage() похожие на send() и recv() , только вместо аргументов buf и length аргумент типа Message.
внутри этих функций 3 раза (для MsgType,MsgIP и MsgText) вызываются соответственно sendString() и recvString()
в sendString() в сокет сначала посылаете 4 байта (длину строки ) потом данные типа Pchar
в recvSring() из сокета сначала читаете 4 байта (длину строки , len ) потом !в цикле! читаете в буфер типа PChar и не забывайте, что может понадобиться несколько вызовов recv, чтобы прочитать всю строку. Выставляете нолик в последнем байте буфера (buf[len]) и конвертируете PChar обратно в AnsiString
Репутация: нет
Всего: 1
leniviy,
т.е. если я правильно понял - возможен такой вариант решения: изменить структуру Message так, чтобы только последнее поле имело непостоянный размер, например так
Код |
struct Message BYTE MsgType; // или WORD MsgType u_long MsgIP; //u_long S_addr; AnsiString MsgText; > |
на крайняк, можно и последнее поле запихнуть в какойнить char[256]. но не хотелось бы
Репутация: нет
Всего: 5
Код |
struct Message BYTE MsgType; // или WORD MsgType u_long MsgIP; //u_long S_addr; AnsiString MsgText; > |
Всё равно одновременное использование
AnsiString как поля структуры +
но тогда надо и другие части проги менять:
Сложно? Конечно. Потому лучше сделайте, как я посоветовал в пред. посте.
Оставьте структуру, как была, но напишите 2 функции для передачи/приёма такой структуры и 2 функции для передачи/приёма AnsiString
Репутация: нет
Всего: 1
Репутация: 1
Всего: 14
скорее класс-оболочка над char массивом
а как иначе вы предлагаете использовать recv?
другой вопрос в несоответствии кол-ва принимаемых данныы размерам структуры
Репутация: нет
Всего: 5
У меня нет с++ билдера, я не мог проверить.
В VS просто выскакивает
Репутация: 1
Всего: 14
У меня нет с++ билдера, я не мог проверить.
В VS просто выскакивает
это не имеет отношения к конкретному компилятору
отступление от стандарта - потенциальный баг
Читайте также: