Правильный способ создания пакетов вместе с отправкой и получением их в программировании сокетов с использованием C - PullRequest
4 голосов
/ 22 июля 2011

Я написал небольшой клиент-серверный код, в котором я отправлял целые числа и символы с моего клиента на сервер.Итак, я знаю основы программирования сокетов на C, такие как шаги, которым нужно следовать, и все.Теперь я хочу создать пакет и отправить его на мой сервер.Я думал, что создам структуру

    struct packet  
    { 
    int srcID;
    long int data;
    .....
    .....
    .....
    };
    struct packet * pkt;

Перед выполнением send () я подумал, что напишу значения внутри пакета, используя

   pkt-> srcID = 01
   pkt-> data = 1 2 3 4 

Мне нужно знать, являюсь ли яна правильном пути, и если да, то могу ли я отправить, используя

      send(sockfd, &packet, sizeof(packet), 0)

для получения

    recv(newsockfd, &PACKET, sizeof(PACKET), 0)

Я только начал с сетевым программированием, поэтому я не уверен, включен ли яправильный путь или нет.Было бы очень полезно, если бы кто-нибудь мог направить меня с моим вопросом в любой форме (теоретическая, примеры и т. Д.).Заранее спасибо.

Ответы [ 3 ]

4 голосов
/ 22 июля 2011
  1. Структура пакета

Когда вы собираетесь отправлять данные по сети, вам нужно подумать о наличии заголовка фиксированного размера, за которым следует полезная нагрузка переменной длины.

[1-байтовый заголовок] [Переменная полезная нагрузка байта]

В заголовке должен быть указан размер данных, которые вы собираетесь отправлять, чтобы получатель всегда считывал заголовок фиксированного размера, а затем определял длину пакета и считывал остальные байты.

Например:

int nRet = recv(nSock,(char*)pBuffer,MESSAGE_HEADER_LENGTH,0);  
if (nRet ==MESSAGE_HEADER_LENGTH)               
{
   int nSizeOfPayload = //Get the length from pBuffer;
   char* pData = new Char(nSizeOfPayload );
   int nPayloadLen = recv(nSock, (char*)pData,nSizeOfPayload ,0); 
}
  1. Данные переменной длины в полезной нагрузке

Если в вашей структуре есть строка, вы всегда должны иметь размер строки, добавляемой перед строкой.

  1. 1021 * порядок байтов *

Если вы отправляете пакет двум разным приложениям, работающим на разных машинах, вам необходимо заранее договориться о том, как вы представляете свои байты, т. Е. Собираетесь ли вы сначала отправлять MSB или сначала LSB.

4 голосов
/ 22 июля 2011

Указатель pkt НЕ определен в вашем приложении. У вас есть два варианта: 1) Объявите pkt как нормальную переменную

 struct packet pkt;

 pkt.srcID = 01;
 ....
 send(sockfd, &pkt, sizeof(struct packet), 0);

2) Второй подход полезен, когда ваш пакет содержит заголовок, за которым следует полезная нагрузка:

 char buffer[MAX_PACKET_SIZE];
 struct packet *pkt = (struct packet *) buffer;
 char *payload = buffer + sizeof(struct packet);
 int packet_size;  /* should be computed as header size + payload size */

 pkt->srcID = 01;
 ...
 packet_size = sizeof(struct packet) /* + payload size */ ;

 send(sockfd, pkt, packet_size, 0);
 .... 

ОБНОВЛЕНО (чтобы ответить на ваш комментарий): Во-первых, вы должны знать, что получение из сокета TCP НЕ МОЖЕТ предоставлять весь пакет. Вам нужно реализовать цикл (как предложено Nemo) для чтения всего пакета. Поскольку вы предпочитаете второй вариант, то вам нужны две петли. Первый цикл - это чтение заголовка пакета для извлечения размера полезной нагрузки, а второй цикл - для чтения данных. В случае UDP вам не нужно беспокоиться о частичном получении. Вот пример кода (без зацикливания), где sockfd - это сокет UDP:

char buffer[MAX_PACKET_SIZE];
struct packet *pkt = (struct packet *) buffer;
char *payload = buffer + sizeof(struct packet);
int packet_size;  /* should be computed as header size + payload size */

.....
/* read the whole packet */
if (recv(sockfd, pkt, MAX_PACKET_SIZE, 0) < 0) {
    /* error in receiving the packet. It is up to you how to handle it */
}
/* Now, you can extract srcID as  pkt->srcID */
/* you can get data by processing payload variable */

Помните: * вам нужно реализовать сериализацию, как уже упоминалось другими пользователями * UDP - ненадежный транспортный протокол, а TCP - надежный транспортный протокол.

3 голосов
/ 22 июля 2011

Непосредственная запись struct s в сеть соблазнительно чиста, проста, аккуратна ... и, к сожалению, неверна.(Это не мешает многим людям, включая тех, кто должен знать лучше, делать это в любом случае).

Есть несколько причин для этого 1 :

  • Представление данных.Это включает в себя размеры различных типов, проблемы, такие как порядковый номер и форматы с плавающей запятой.Вы не можете предполагать, что они одинаковы на другом конце соединения, как и на вашем конце;они сильно различаются в зависимости от архитектуры.
  • Структура структуры.Компиляторы обычно добавляют невидимые отступы между элементами struct s, когда они имеют неравномерный размер - и расположение этого дополнения также зависит от архитектуры.Битовые поля - это еще одна переменная структуры структуры.
  • Канонизация.В частности, указатели не имеют смысла отправлять через сокет - они ничего не значат для другой стороны.Вместо этого вы должны отправить то, на что он указывает .Другим аспектом этого является отправка всего массива, когда только некоторые из него заполнены значимыми значениями - неинициализированные элементы могут утечь содержимое памяти, которое вам не нужно (и отправка их в любом случае расточительна).
  • Частичное чтение.В конечном итоге вам все равно придется использовать указатель char * при чтении, потому что вам, возможно, придется возобновить чтение на полпути через struct (например, ваш struct может иметь длину 830 байт, но первое чтение вернуло только 500 байт).

Правильный способ состоит в том, чтобы использовать процесс, называемый сериализацией: для каждой структуры данных, которую вы хотите отправить по сети, у вас есть функция, которая канонизируетсодержимое и упаковывает их в буфер char в определенном формате.Это включает в себя преобразование целых чисел в конкретный порядок байтов (здесь полезны такие функции, как htonl()).Также имеется соответствующая функция, которая распаковывает буфер char в форму struct, используемую на стороне получения.

Существует несколько существующих библиотек кода для сериализации - например, буфер протокола Google .

Другая работоспособная альтернатива - сериализация ваших структур данных в текстовый формат, такой как JSON.Это очень хорошо для устранения неполадок, потому что это означает, что ваш сетевой протокол удобен для чтения человеком.


1.Предвосхищая некоторые возражения: да, для большинства из этих проблем есть обходные пути.Но это именно то, чем они являются: обходные пути, которые часто опираются на специфичные для компилятора функции, отменяют большинство преимуществ простоты и все равно не совсем надежны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...