Почему мои передачи TCP повреждены на Cygwin? - PullRequest
2 голосов
/ 20 февраля 2011

Я пытаюсь отладить, почему мои передачи TCP повреждены при отправке из Cygwin. Я вижу, что только первые 24 байта каждой структуры отображаются в моей серверной программе, работающей в Centos. С 25 по 28 байты скремблируются, а все остальные после этого обнуляются. Идя в другом направлении, получая от Centos на Cygwin, снова только первые 24 байта каждого блока отображаются в моей серверной программе, работающей на Cygwin. С 25-го по 40-й байты скремблируются, а все остальные после этого обнуляются. Я также вижу проблему при отправке или получении в / из localhost на Cygwin. Для localhost первые 34 байта являются правильными и все после этого обнуляются.

Приложение, над которым я работаю, работает на Centos4, общается с Centos и пытаюсь перенести его на Cygwin. Valgrind не сообщает о каких-либо проблемах с Centos, у меня не работает Valgrind на Cygwin. Обе платформы имеют младший порядок x86.

Я запустил Wireshark на хост-системе Windows XP, под которой работает Cygwin. Когда я анализирую пакеты с помощью Wireshark, они выглядят идеально как для отправленных пакетов из Cygwin, так и для полученных пакетов в Cygwin.

Каким-то образом данные повреждены между уровнем Wireshark и самой программой.

Код C ++ использует ::write(fd, buffer, size) и ::read(fd, buffer, size) для записи и чтения пакетов TCP, где fd - дескриптор файла для сокета, который открывается между клиентом и сервером. Этот код прекрасно работает при обращении к Centos4 с Centos.

Самым странным для меня является то, что анализатор пакетов показывает правильный завершенный пакет для всех случаев, однако приложение cygwin никогда не читает полный пакет, или в другом направлении приложение Centos никогда не читает полный пакет.

Кто-нибудь может подсказать, как мне отладить это?

Вот некоторый запрошенный код:

size_t
read_buf(int fd, char *buf, size_t count, bool &eof, bool immediate)
{
  if (count > SSIZE_MAX) {
    throw;
  }

  size_t want = count;
  size_t got = 0;

  fd_set readFdSet;
  int fdMaxPlus1 = fd + 1;

  FD_ZERO(&readFdSet);
  FD_SET(fd, &readFdSet);

  while (got < want) {
    errno = 0;

    struct timeval timeVal;
    const int timeoutSeconds = 60;

    timeVal.tv_usec = 0;
    timeVal.tv_sec = immediate ? 0 : timeoutSeconds;

    int selectReturn = ::select(fdMaxPlus1, &readFdSet, NULL, NULL, &timeVal);

    if (selectReturn < 0) {
      throw;
    }

    if (selectReturn == 0 || !FD_ISSET(fd, &readFdSet)) {
      throw;
    }

    errno = 0;

    // Read buffer of length count.
    ssize_t result = ::read(fd, buf, want - got);

    if (result < 0) {
      throw;
    } else {
      if (result != 0) {
        // Not an error, increment the byte counter 'got' & the read pointer,
        // buf.
        got += result;
        buf += result;
      } else { // EOF because zero result from read.
        eof = true;
        break;
      }
    }
  }
  return got;
}

Я узнал больше об этой ошибке. Класс C ++, в который читается пакет, выглядит так:

unsigned char _array[28];
long long _sequence;
unsigned char _type;
unsigned char _num;
short _size;

Очевидно, длинное длинное шифруется четырьмя следующими байтами.

Память C ++, отправляемая приложением Centos, начиная с _sequence, в шестнадцатеричном виде, выглядит следующим образом: (1026 *)

_sequence: 45 44 35 44 33 34 43 45
    _type: 05
     _num: 33
    _size: 02 71

Wireshark показывает объем памяти в сетевом формате с прямым порядком байтов, например, в пакете:

_sequence: 45 43 34 33 44 35 44 45
    _type: 05
     _num: 33
    _size: 71 02

Но после read () в приложении C ++ Cygwin с прямым порядком байтов это выглядит так:

_sequence: 02 71 33 05 45 44 35 44
    _type: 00
     _num: 00
    _size: 00 00

Я озадачен тем, как это происходит. Кажется, это проблема с прямым и прямым порядком байтов, но обе платформы имеют прямой порядок байтов.


Здесь _array - 7 дюймов вместо 28 символов.

Полный дамп памяти у отправителя:

_array[0]: 70 a2 b7 cf
_array[1]: 9b 89 41 2c
_array[2]: aa e9 15 76
_array[3]: 9e 09 b6 e2
_array[4]: 85 49 08 81
_array[5]: bd d7 9b 1e
_array[6]: f2 52 df db
_sequence: 41 41 31 35 32 43 38 45
    _type: 05
     _num: 45
    _size: 02 71

А при получении:

_array[0]: 70 a2 b7 cf
_array[1]: 9b 89 41 2c
_array[2]: aa e9 15 76
_array[3]: 9e 09 b6 e2
_array[4]: 85 49 08 81
_array[5]: bd d7 9b 1e
_array[6]: f2 52 df db
_sequence: 02 71 45 05 41 41 31 35
    _type: 0
     _num: 0
    _size: 0

Результат теста Cygwin:

4
8
48
0x22be08
0x22be28
0x22be31
0x22be32
0x22be38

Результат теста Centos:

4
8
40
0xbfffe010
0xbfffe02c
0xbfffe035
0xbfffe036
0xbfffe038

Ответы [ 2 ]

5 голосов
/ 20 февраля 2011

Теперь, когда вы показали данные, ваша проблема ясна.Вы не управляете выравниванием вашей структуры, поэтому компилятор автоматически помещает 8-байтовое поле (long long) на 8-байтовую границу (смещение 32) от начала структуры, оставляя 4 байта заполнения.

Измените выравнивание на 1 байт, и все должно разрешиться.Вот фрагмент кода, который вам нужен:

__attribute__ ((aligned (1))) __attribute ((packed))

Я также предлагаю вам использовать типы фиксированного размера для структур, скрывающихся в сети, например, uint8_t, uint32_t, uint64_t


Предыдущие мысли:

С TCP вы не получаете read и write пакетов .Вы читаете и пишете из поток байтов .Пакеты используются для переноса этих байтов, но границы не сохраняются.

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

2 голосов
/ 20 февраля 2011

Надеюсь, окончательное обновление: -)

На основании вашего последнего обновления Centos упаковывает ваши структуры на уровне байтов, а CygWin - нет. Это вызывает проблемы с выравниванием. Я не уверен, почему в случае с CygWin-to-CygWin возникают проблемы, так как там должны быть одинаковые отступы, но я могу рассказать вам, как исправить другой случай.

Используя код, который я дал ранее:

#include <stdio.h>
typedef struct {
    unsigned char _array[28];
    long long _sequence;
    unsigned char _type;
    unsigned char _num;
    short _size;
} tType;
int main (void) {
    tType t[2];
    printf ("%d\n", sizeof(long));
    printf ("%d\n", sizeof(long long));
    printf ("%d\n", sizeof(tType));
    printf ("%p\n", &(t[0]._array));
    printf ("%p\n", &(t[0]._sequence));
    printf ("%p\n", &(t[0]._num));
    printf ("%p\n", &(t[0]._size));
    printf ("%p\n", &(t[1]));
    return 0;
}

Если вы не хотите заполнять, у вас есть два варианта. Первый - это реорганизовать вашу структуру, чтобы поставить более ограниченные типы впереди:

typedef struct {
    long long _sequence;
    short _size;
    unsigned char _array[28];
    unsigned char _type;
    unsigned char _num;
} tType;

, что дает вам:

4
8
40
0x22cd42
0x22cd38
0x22cd5f
0x22cd40
0x22cd60

Другими словами, каждая структура составляет ровно 40 байтов (8 для последовательности, 2 для размера, 28 для массива и 1 для типа и числа). Но это может быть невозможно, если вы хотите, чтобы это было в определенном порядке.

В этом случае вы можете заставить выравнивания быть на уровне байтов с помощью:

typedef struct {
    unsigned char _array[28];
    long long _sequence;
    unsigned char _type;
    unsigned char _num;
    short _size;
} __attribute__ ((aligned(1),packed)) tType;

aligned(1) устанавливает его в байтовое выравнивание, но это не сильно повлияет, поскольку объектам не нравится, когда их выравнивание уменьшается. Для этого вам также нужно использовать packed.

Делая это, вы получаете:

4
8
40
0x22cd3c
0x22cd58
0x22cd61
0x22cd62
0x22cd64

Более ранняя история процветания:

Ну, так как я wget и ftp огромных файлов просто отлично от CygWin, мои навыки психической отладки говорят мне, что это скорее проблема с вашим кодом, чем с программным обеспечением CygWin.

Другими словами, в отношении предложения «пакеты повреждены между уровнем, который просматривает Wireshark, и самой программой», я бы серьезно посмотрел на верхний предел этой шкалы, а не на нижний::)

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

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


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

На самом деле, перечитывая ваш вопрос более внимательно, я немного растерялся. Вы заявляете, что у вас одинаковая проблема с кодом вашего сервера в Linux и CygWin, но говорите, что он работает на Centos.

На данный момент мой единственный совет - ставить отладочные операторы printf в той функции, которую вы показали, например, после вызовов select и read для вывода соответствующих переменных, включая got и buf после их изменения, а также в каждом пути кода, чтобы вы могли видеть, что он делает. А также выгрузите всю структуру побайтно в конце отправки.

Это, мы надеемся, сразу покажет вам, в чем проблема, тем более что у вас, кажется, есть данные, отображаемые не в том месте.

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


Хорошо, проверьте выравнивание на обоих концах, скомпилируйте и запустите эту программу на обеих системах:

#include <stdio.h>
typedef struct {
    unsigned char _array[28];
    long long _sequence;
    unsigned char _type;
    unsigned char _num;
    short _size;
} tType;
int main (void) {
    tType t[2];
    printf ("%d\n", sizeof(long));
    printf ("%d\n", sizeof(long long));
    printf ("%d\n", sizeof(tType));
    printf ("%p\n", &(t[0]._array));
    printf ("%p\n", &(t[0]._sequence));
    printf ("%p\n", &(t[0]._num));
    printf ("%p\n", &(t[0]._size));
    printf ("%p\n", &(t[1]));
    return 0;
}

На моем CygWin я получаю:

4            long size
8            long long size
48           structure size
0x22cd30     _array start (size = 28, padded to 32)
0x22cd50     _sequence start (size = 8, padded to 9???)
0x22cd59     _type start (size = 1)
0x22cd5a     _size start (size = 2, padded to 6 for long long alignment).
0x22cd60     next array element.

Единственный нечетный бит - это заполнение перед _type, но это, конечно, верно, хотя и неожиданно.

Проверьте выходные данные Centos, чтобы убедиться, что они несовместимы. Однако ваше утверждение о том, что CygWin-to-CygWin не работает, несовместимо с этой возможностью, поскольку размеры и размеры будут совместимы (если ваш код отправки и получения не скомпилирован по-другому).

...