Передача значений с плавающей точкой по 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.

Другие вопросы по тегам