Передача значений с плавающей точкой по TCP/IP и повреждение данных
У меня очень странная ошибка.
У меня есть два приложения, которые общаются через TCP/IP.
Приложение A является сервером, а приложение B является клиентом.
Приложение A отправляет набор значений с плавающей запятой в приложение B каждые 100 миллисекунд.
Ошибка заключается в следующем: иногда некоторые значения с плавающей запятой, полученные приложением B, не совпадают со значениями, переданными приложением A.
Первоначально я думал, что есть проблема с драйверами Ethernet или TCP / IP (своего рода повреждение данных). Затем я проверил код на других машинах Windows, но проблема осталась.
Затем я протестировал код в Linux (Ubuntu 10.04.1 LTS), и проблема все еще существует!!!
Значения регистрируются непосредственно перед отправкой и сразу после их получения.
Код довольно прост: протокол сообщения имеет 4-байтовый заголовок, например:
//message header
struct MESSAGE_HEADER {
unsigned short type;
unsigned short length;
};
//orientation message
struct ORIENTATION_MESSAGE : MESSAGE_HEADER
{
float azimuth;
float elevation;
float speed_az;
float speed_elev;
};
//any message
struct MESSAGE : MESSAGE_HEADER {
char buffer[512];
};
//receive specific size of bytes from the socket
static int receive(SOCKET socket, void *buffer, size_t size) {
int r;
do {
r = recv(socket, (char *)buffer, size, 0);
if (r == 0 || r == SOCKET_ERROR) break;
buffer = (char *)buffer + r;
size -= r;
} while (size);
return r;
}
//send specific size of bytes to a socket
static int send(SOCKET socket, const void *buffer, size_t size) {
int r;
do {
r = send(socket, (const char *)buffer, size, 0);
if (r == 0 || r == SOCKET_ERROR) break;
buffer = (char *)buffer + r;
size -= r;
} while (size);
return r;
}
//get message from socket
static bool receive(SOCKET socket, MESSAGE &msg) {
int r = receive(socket, &msg, sizeof(MESSAGE_HEADER));
if (r == SOCKET_ERROR || r == 0) return false;
if (ntohs(msg.length) == 0) return true;
r = receive(socket, msg.buffer, ntohs(msg.length));
if (r == SOCKET_ERROR || r == 0) return false;
return true;
}
//send message
static bool send(SOCKET socket, const MESSAGE &msg) {
int r = send(socket, &msg, ntohs(msg.length) + sizeof(MESSAGE_HEADER));
if (r == SOCKET_ERROR || r == 0) return false;
return true;
}
Когда я получаю сообщение "Ориентация", иногда значение "азимут" отличается от значения, отправленного сервером!
Разве данные не должны быть одинаковыми все время? TCP / IP не гарантирует доставку данных без повреждений? Может ли быть так, что исключение в математическом сопроцессоре влияет на стек TCP / IP? это проблема, что я сначала получаю небольшое количество байтов (4 байта), а затем тело сообщения?
РЕДАКТИРОВАТЬ:
Проблема заключается в подпрограмме обмена порядком байтов. Следующий код меняет местами порядковый номер определенного float, а затем меняет его снова и печатает байты:
#include <iostream>
using namespace std;
float ntohf(float f)
{
float r;
unsigned char *s = (unsigned char *)&f;
unsigned char *d = (unsigned char *)&r;
d[0] = s[3];
d[1] = s[2];
d[2] = s[1];
d[3] = s[0];
return r;
}
int main() {
unsigned long l = 3206974079;
float f1 = (float &)l;
float f2 = ntohf(ntohf(f1));
unsigned char *c1 = (unsigned char *)&f1;
unsigned char *c2 = (unsigned char *)&f2;
printf("%02X %02X %02X %02X\n", c1[0], c1[1], c1[2], c1[3]);
printf("%02X %02X %02X %02X\n", c2[0], c2[1], c2[2], c2[3]);
getchar();
return 0;
}
Выход:
7F 8A 26 BF 7F CA 26 BF
Т.е. присвоение с плавающей точкой, вероятно, нормализует значение, создавая значение, отличное от исходного.
Любой вклад в это приветствуется.
EDIT2:
Спасибо всем за ваши ответы. Кажется, проблема в том, что подкачка с плавающей запятой, когда она возвращается с помощью оператора return, помещается в стек с плавающей запятой процессора. Затем вызывающая сторона извлекает значение из стека, значение округляется, но это перестановка с плавающей точкой, и, следовательно, округление портит значение.
2 ответа
Вы отправляете двоичные данные по сети, используя заполнение, определяемое реализацией, для структуры структуры, поэтому это будет работать, только если вы используете одно и то же оборудование, ОС и компилятор для приложения A и приложения B.
Если все в порядке, я не вижу ничего плохого в вашем коде. Одна потенциальная проблема заключается в том, что вы используете ntohs для извлечения длины сообщения, и эта длина является общей длиной за вычетом длины заголовка, поэтому вам нужно убедиться, что вы ее правильно настроили. Это должно быть сделано как
msg.length = htons(sizeof(ORIENTATION_MESSAGE) - sizeof(MESSAGE_HEADER));
но вы не показываете код, который устанавливает сообщение...
TCP пытается доставить неизмененные байты, но если машины не имеют одинаковые ЦП и операционные системы, нет гарантии, что представление с плавающей запятой в одной системе идентично представлению в другой. Вам нужен механизм для обеспечения этого, такой как XDR или Google Protobuf.