为什么我的TCP传输在cygwin上被破坏了?

时间:2011-02-20 02:14:32

标签: c++ tcp cygwin porting endianness

我正在尝试调试从Cygwin发送时我的TCP传输被破坏的原因。我看到在Centos上运行的服务器程序中只显示每个结构的前24个字节。第25到第28个字节被加扰,之后的所有其他字节都被清零。走向另一个方向,从Cygwin上的Centos接收,再次只有每个块的前24个字节出现在我在Cygwin上运行的服务器程序中。第25到第40个字节被加扰,之后的所有其他字节都被清零。在Cygwin上发送或接收localhost时,我也看到了这个问题。对于localhost,前34个字节是正确的,之后全部归零。

我正在处理的应用程序在Centos4与Centos交谈时正常工作,我正在尝试将其移植到Cygwin。 Valgrind报告没有关于Centos的问题,我没有在Cygwin上运行Valgrind。这两个平台都是小端x86。

我在运行Cygwin的主机Windows XP系统上运行Wireshark。当我使用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;

显然,漫长的时间会被随后的四个字节所扰乱。

Centos应用程序发送的C ++内存,以_sequence开头,以十六进制表示,看起来像是write():

_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

但是,在C ++ cygwin little-endian应用程序中的read()之后,它看起来像这样:

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

我很难过这是怎么回事。这似乎是big-endian和little-endian的问题,但这两个平台都是小端的。


这里_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 个答案:

答案 0 :(得分:5)

现在您已经显示了数据,您的问题很明显。您没有控制结构的对齐,因此编译器会自动将8字节字段(long long)放在结构开头的8字节边界(偏移32)上,留下4个字节的填充

将对齐方式更改为1个字节,一切都应该解决。这是您需要的代码段:

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

我还建议您使用固定大小的类型,以便在网络中进行blitting,例如: uint8_tuint32_tuint64_t


以前的想法:

使用TCP,您不会readwrite 数据包。您从字节流读取和写入。数据包用于携带这些字节,但不保留边界。

您的代码看起来很合理,您可能想要更新问题的措辞。

答案 1 :(得分:2)

希望最后更新: - )

根据您的最新更新,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个用于type和num)。但如果你想按特定顺序进行,这可能是不可能的。

在这种情况下,您可以强制对齐在字节级别上:

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

早期繁荣的历史:

好吧,既然我{Cy}来自wgetftp巨大的文件,我的通灵调试技巧告诉我,你的代码更可能是一个问题,而不是CygWin软件。

换句话说,关于句子“数据包在Wireshark级别和程序本身之间被破坏”,我会认真地看着该规模的上端而不是下端: - )

通常情况下,你假设read将获得发送的整个数据包而不是一次发送的数据包,但是,如果没有看到有问题的代码,这是一个非常疯狂的猜测。

确保您正在检查read的返回值,以查看实际接收的字节数。除此之外,发布负责read的代码,以便我们进行更深入的分析。


根据您发布的代码,它看起来没问题。我唯一可以建议的是你检查你传入的缓冲区是否足够大,即使它们是,确保你在返回后立即打印 以防其他一些代码正在破坏数据。

事实上,在更仔细地重读你的问题时,我有点困惑。您声明您在Linux CygWin上的服务器代码存在同样的问题,但是它说它正在使用Centos。

此时我唯一的建议是将调试printf语句放在您显示的函数中,例如在selectread调用之后输出相关变量,包括更改它们之后的gotbuf,以及每个代码路径中的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; } 在两个平台上的大小不同,那么您的数据将会错位。


好的,检查两端的路线,在两个系统上编译并运行该程序:

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.

在我的CygWin上,我得到:

{{1}}

唯一奇怪的是在_type之前有填充但是这肯定是有效的,虽然意外。

检查Centos的输出以查看它是否不兼容。但是,你的CygWin-to-CygWin不起作用的声明与这种可能性不一致,因为这些声明和大小是兼容的(除非您的发送和接收代码编译方式不同)。