Я столкнулся с небольшой проблемой с передачей данных через (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);
}
Таким образом, для каждого посыла, который имеет полезную нагрузку, я заканчиваю тем, что звоню, получаю дважды. Это сработало для меня, но мне было интересно, есть ли лучший способ сделать это?
Вы находитесь на правильном пути с идеей отправки заголовка, который содержит основную информацию о следующих данных, а затем сами данные. Тем не менее, это не всегда работает:
int receivedBytes = receive(fd, msgBuff, sizeof(IpcMsg) - sizeof( ((IpcMsg*)0)->value ), 0);
int sizeToPoll = ((IpcMsg*)buffer)->size;
Причина в том, что TCP может свободно фрагментировать и отправлять ваш заголовок в любом количестве фрагментов так, как он считает нужным, основываясь на собственной оценке базовых условий сети, применяемых к тому, что называется стратегия контроля заторов. В локальной сети вы почти всегда получаете свой заголовок в одном пакете, но попробуйте по всему миру через Интернет, и вы можете получить гораздо меньшее количество байтов за раз.
Ответ заключается в том, чтобы не вызывать TCP «получать» (обычно recv
) прямо, но абстрагируйте его в маленькую служебную функцию, которая принимает размер, который вы действительно должны получить, и буфер для его помещения. Заходите в цикл приема и добавления пакетов, пока не будут получены все данные или не возникнет ошибка.
Если вам нужно работать асинхронно и обслуживать несколько клиентов одновременно, то применяется один и тот же принципал, но вам нужно исследовать вызов select, который позволяет вам получать уведомления о поступлении данных.
TCP / IP — это «сырой» интерфейс для отправки данных. Он гарантирует, что, если байты отправлены, что все они находятся там и в правильном порядке, но не дает никаких гарантий относительно разбиения на блоки и ничего не знает о данных, которые вы отправляете.
Следовательно, если вы отправляете «пакет» по TCP / IP, который должен обрабатываться как таковой, вы должны знать, когда у вас есть полный пакет одним из следующих способов:
В любом из первых двух вы знаете количество байтов, которые вы ожидаете получить, поэтому вам нужно буферизовать все, что вы получите, до получения полного сообщения, а затем обработать его.
Если вы получаете больше, чем ожидали, то есть он перетекает в следующий пакет, вы разделяете его, обрабатываете завершенный пакет и оставляете оставшуюся часть в буфере для последующей обработки.
В последнем случае, когда у вас есть символ конца пакета, который может быть где угодно в вашем сообщении, поэтому все, что следует за ним, вы буферизуете для следующего пакета.