#c #sockets
#c #сокеты
Вопрос:
В настоящее время у меня есть базовая настройка клиент / сервер. Серверу необходимо принимать запросы от клиента (ов) и отвечать на различные типы сообщений запроса. Примером запроса клиента может быть получение списка доступных файлов, сколько других клиентов подключено к серверу и т.д.
Очевидно, мне пришлось бы найти способ определить тип сообщения по сообщению, полученному от отправителя. Мне было интересно, есть ли у меня структура, определенная с необходимыми данными, могу ли я преобразовать структуру в void *, отправить ее как через системный вызов send (sockfd, message, length, flags), а затем на стороне получателя преобразовать ее обратно в структуру. Это, конечно, предполагает, что я запускаю клиент и сервер в одной среде.
Например, если у меня есть следующая структура:
struct message {
enum messageType { GET_FILES, GET_CLINET_NUMBER} messageType,
}
и используйте эту структуру для отправки сообщения следующим образом
struct message msg;
msg.messageType = GET_FILES;
send(server_sockfd, (void*)amp;msg, sizeof(struct), 0)
и на приемнике,
recv(,msg_buffer);
struct message received = (struct message*)msg_buffer;
Игнорируя незначительные синтаксические проблемы, кто-нибудь может посоветовать, возможна ли эта схема? Если нет, есть ли какой-либо другой способ передать сообщение без отправки необработанного символа *?
Ответ №1:
Это вполне возможно. Однако не рекомендуется, потому что сейчас вы используете одну и ту же среду для клиента и сервера, но в будущем это может измениться, и вам придется изменить этот код, чтобы он был более независимым от платформы / реализации.
Параметры? boost::сериализация, XML-RPC, даже HTTP / REST или любой другой протокол высокого уровня, который вам подходит, буферы протокола Google, CORBA и т.д.
Ответ №2:
Есть две вещи, о которых вам нужно быть очень внимательным:
-
Порядковый номер. Если порядковый номер вашего клиента и сервера будет отличаться, вы окажетесь в ситуации, когда вы не сможете обмениваться данными. Учитывая, что сегодня большое внимание уделяется системам x86, вы могли бы убедить себя чувствовать себя комфортно с этим ограничением. На вашем месте я бы, как минимум, встроил проверку. Например, в начале каждого сеанса связи отправляйте известную контрольную сумму,
0xdeadbeef
например. Это позволило бы вам утверждать, что порядковый номер между клиентом и сервером одинаковый, что, надеюсь, позволит избежать продолжения сеанса и всевозможных потенциальных ошибок. -
Размер слова. Это более коварная и веская причина, по которой вам не следует делать то, что вы собираетесь делать. Если вы не можете твердо утверждать, что размер слова отправителя и получателя никогда не будет отличаться, то ваш метод сопряжен с опасностью. Предположим, у вас есть структура следующим образом:
struct some_msg { long payload; };
Что происходит, когда 32-разрядная система gcc / linux отправляет это сообщение 64-разрядной системе gcc / linux? Что ж, в 32-разрядной системе он с радостью отправит 4 байта данных. Однако 64-разрядная система наложит структуру на 8 байт данных. Плохие новости.
Хотя маловероятно, что вы переключитесь с x86, скажем, на sparc, практически само собой разумеется, что со временем ваши системы станут 64-разрядными. Мне неизвестно, есть ли у вас устаревшие 32-разрядные системы, но без очень жесткого контроля как над клиентом, так и над сервером, почти гарантировано, что у вас будет среда смешанного размера word.
Я уверен, что это можно заставить работать, если вы очень конкретно определяете используемые платформы и контролируете их. Кроме того, вы могли бы ограничить свой риск, используя typedefs фиксированного размера, такие как int32_t
и uint64_t
. ИМО, ты играешь с огнем, если делаешь это. Любое мыслимое преимущество быстро сводится к минимуму из-за высокого уровня привязки вашего проектирования к вашей системе.
Комментарии:
1. Другая, возможно, даже худшая проблема: заполнение байтов. Компиляторы C иногда добавляют «невидимые» байты заполнения между элементами структуры, чтобы сделать доступ к памяти более эффективным для целевого процессора. Заполнение не стандартизировано, поэтому оно может выполняться по-разному между одним компилятором и другим, или между двумя версиями одного и того же компилятора, или даже между сборками из одной и той же версии компилятора (особенно, если они построены с разными уровнями оптимизации). Таким образом, все, скорее всего, оборвется, если на обоих концах соединения не будет запущен один и тот же исполняемый двоичный файл.
2. Выравнивание стандартизировано на данном ISA ABI, и на любой машине с реальным текстом заполнение — это минимум, необходимый для выравнивания. Более того, почти для всех типов на реальных машинах выравнивание соответствует размеру типа. Выравнивание всегда должно равномерно делить размер типа (из-за семантики представления массива C), поэтому, если вы проектируете свои структуры таким образом, чтобы при отсутствии заполнения каждый элемент начинался с размера, кратного его размеру, тогда вы можете быть практически уверены, что заполнения не будет.
Ответ №3:
«можно преобразовать структуру в void *, отправить ее как через системный вызов send(sockfd, message, length, flags), а затем на стороне получателя преобразовать ее обратно в структуру» — см. Ответ Диего. Хорошо обратите внимание, что любой протокол, который не может восстановиться или, что еще хуже, приведет к сбою сервера, если в потоке будет вставлен / удален / изменен один дополнительный случайный байт, недостаточно безопасен, независимо от того, передается он по TCP или нет.
‘Если нет, есть ли какой-либо другой способ передать сообщение без отправки необработанного символа *?’ Единственной альтернативой протоколу, который может надежно и безошибочно повторно собирать сообщения из байтовой (октетной) последовательности, является отключение / повторное подключение после каждого сообщения, таким образом завершая поток. Производительность / задержка этой схемы именно такие, какие вы ожидаете, — действительно, действительно плохие.
Rgds, Мартин