我正在尝试调试从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
答案 0 :(得分:5)
现在您已经显示了数据,您的问题很明显。您没有控制结构的对齐,因此编译器会自动将8字节字段(long long
)放在结构开头的8字节边界(偏移32)上,留下4个字节的填充
将对齐方式更改为1个字节,一切都应该解决。这是您需要的代码段:
__attribute__ ((aligned (1))) __attribute ((packed))
我还建议您使用固定大小的类型,以便在网络中进行blitting,例如: uint8_t
,uint32_t
,uint64_t
以前的想法:
使用TCP,您不会read
和write
数据包。您从字节流读取和写入。数据包用于携带这些字节,但不保留边界。
您的代码看起来很合理,您可能想要更新问题的措辞。
答案 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}来自wget
和ftp
巨大的文件,我的通灵调试技巧告诉我,你的代码更可能是一个问题,而不是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;
}
在两个平台上的大小不同,那么您的数据将会错位。
好的,检查两端的路线,在两个系统上编译并运行该程序:
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不起作用的声明与这种可能性不一致,因为这些声明和大小是兼容的(除非您的发送和接收代码编译方式不同)。