получение переменного размера данных через TCP-сокеты

Я столкнулся с небольшой проблемой с передачей данных через (TCP) сокеты. Небольшой фон о том, что я делаю:

Я отправляю данные со стороны A на B. Отправляемые данные могут иметь переменную длину, предполагая, что максимальный размер составляет 1096 байт.

A) send(clientFd, buffer, size, NULL)

на B, так как я не знаю, какого размера ожидать, я всегда пытаюсь получить 1096 байт:

B) int receivedBytes = receive(fd, msgBuff, 1096, NULL)

Однако, когда я сделал это: я понял, что A посылает небольшие порции данных … скажем, около 80-90 байт. После нескольких посылок, B собирал их вместе, чтобы получить 1096 байт. Это явно испортило данные, и ад вырвался на свободу.

Чтобы это исправить, я разбил свои данные на две части: заголовок и данные.

struct IpcMsg
{
long msgType;
int devId;
uint32_t senderId;
uint16_t size;
uint8_t value[IPC_VALUES_SIZE];
};

На стороне:

A) send(clientFd, buffer, size, NULL)

на B я сначала получаю заголовок и определяю размер полезной нагрузки для получения, а затем получаю остальную часть полезной нагрузки.

B) int receivedBytes = receive(fd, msgBuff, sizeof(IpcMsg) - sizeof( ((IpcMsg*)0)->value ), 0);
int sizeToPoll = ((IpcMsg*)buffer)->size;
printf("Size to poll: %d\n", sizeToPoll);

if (sizeToPoll != 0)
{
bytesRead = recv(clientFd, buffer + receivedBytes, sizeToPoll, 0);
}

Таким образом, для каждого посыла, который имеет полезную нагрузку, я заканчиваю тем, что звоню, получаю дважды. Это сработало для меня, но мне было интересно, есть ли лучший способ сделать это?

7

Решение

Вы находитесь на правильном пути с идеей отправки заголовка, который содержит основную информацию о следующих данных, а затем сами данные. Тем не менее, это не всегда работает:

int receivedBytes = receive(fd, msgBuff, sizeof(IpcMsg) - sizeof( ((IpcMsg*)0)->value ), 0);
int sizeToPoll = ((IpcMsg*)buffer)->size;

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

Ответ заключается в том, чтобы не вызывать TCP «получать» (обычно recv) прямо, но абстрагируйте его в маленькую служебную функцию, которая принимает размер, который вы действительно должны получить, и буфер для его помещения. Заходите в цикл приема и добавления пакетов, пока не будут получены все данные или не возникнет ошибка.

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

5

Другие решения

TCP / IP — это «сырой» интерфейс для отправки данных. Он гарантирует, что, если байты отправлены, что все они находятся там и в правильном порядке, но не дает никаких гарантий относительно разбиения на блоки и ничего не знает о данных, которые вы отправляете.

Следовательно, если вы отправляете «пакет» по TCP / IP, который должен обрабатываться как таковой, вы должны знать, когда у вас есть полный пакет одним из следующих способов:

  • Пакеты фиксированного размера. В вашем случае 1096 байт
  • Сначала отправьте / получите известный «заголовок», который сообщит вам размер отправляемого пакета.
  • Используйте какой-нибудь символ «конец пакета».

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

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

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

2

По вопросам рекламы [email protected]