C++ 通过C+中的套接字传输数据+;
我目前正在从事一个使用网络的项目。我必须发送一个结构C++ 通过C+中的套接字传输数据+;,c++,c,sockets,serialization,tcp,C++,C,Sockets,Serialization,Tcp,我目前正在从事一个使用网络的项目。我必须发送一个结构 struct Header { uint32_t magic; uint32_t checksum; uint32_t timestamp; uint16_t commandId; uint16_t dataSize; }; struct Packet { struct Header header; char
struct Header
{
uint32_t magic;
uint32_t checksum;
uint32_t timestamp;
uint16_t commandId;
uint16_t dataSize;
};
struct Packet
{
struct Header header;
char data[128];
};
我正在尝试使用TCP将结构数据包从一个套接字发送到另一个套接字。我试着这样发送我的结构
send(socket, &my_struct, sizeof(my_struct), 0);
但它不起作用,所以我尝试将结构序列化为字符*
unsigned char *Serialization::serialize_uint32(unsigned char *buffer, uint32_t arg)
{
buffer[3] = (arg >> 24);
buffer[2] = (arg >> 16);
buffer[1] = (arg >> 8);
buffer[0] = (arg);
return (buffer + sizeof(uint32_t));
}
unsigned char *Serialization::serialize_uint16(unsigned char *buffer, uint16_t arg)
{
buffer[1] = (arg >> 8);
buffer[0] = (arg);
return (buffer + sizeof(uint16_t));
}
unsigned char *Serialization::deserialize_uint32(unsigned char *buffer, uint32_t *arg)
{
memcpy((char*)arg, buffer, sizeof(uint32_t));
return (buffer + sizeof(uint32_t));
}
unsigned char *Serialization::deserialize_uint16(unsigned char *buffer, uint16_t *arg)
{
memcpy((char*)arg, buffer, sizeof(uint16_t));
return (buffer + sizeof(uint16_t));
}
即使客户端同时发送结构头数据,当我在服务器端读取时,数据也会损坏
为什么数据会损坏
客户端发送循环
TcpSocket tcp;
Packet p;
std::stringstream ss;
int cpt = 0;
int ret = 0;
char *serialized;
tcp.connectSocket("127.0.0.1", 4242);
while (getchar())
{
ss.str("");
ss.clear();
ss << cpt++;
p.header.magic = 0;
p.header.checksum = 1;
p.header.timestamp = 2;
p.header.commandId = 3;
p.header.dataSize = ss.str().length();
memset(p.data, 0, 128);
memcpy(p.data, ss.str().c_str(), ss.str().length());
serialized = new char[sizeof(Header) + ss.str().length()];
bzero(serialized, sizeof(Header) + ss.str().length());
Serialization::serialize_packet(serialized, p);
hexDump("serialized", serialized+1, sizeof(Header) + ss.str().length());
ret = tcp.write(serialized+1, sizeof(Header) + ss.str().length());
}
套接字->读取():
当我运行这些程序时:
./server
[Server] new connexion :: [5]
recv returns : 17
buff serialized:
0000 00 00 00 00 14 00 00 00 1c 00 00 00 1a 00 00 00 ................
0010 1b
./client
serialized data:
0000 00 00 00 00 00 00 01 00 00 00 02 00 03 00 01 30 ...............0
0010 00
send returns : 17
buffer[3] = (arg >> 24) & 0xff;
buffer[2] = (arg >> 16) & 0xff;
buffer[1] = (arg >> 8) & 0xff;
buffer[0] = (arg) & 0xff;
如果我是你,我就不会重新发明轮子。有很多经过良好记录和测试的库/协议正是为了满足您的需求。我刚刚想到的一个小清单:
- JSON
- XML
- 科尔巴
- 因此,这是错误的,它肯定会导致错误
buff = new char[bav];
socket->read(buff, bav);
hexdump("buff", buff, bav);
socket->read() :
int TcpSocket::read(char *buff, int len)
{
return recv(this->_socket, buff, len, 0);
}
不能忽略recv()
的返回值。
从man 2 recv
:
RETURN VALUES
These calls return the number of bytes received, or -1 if an error
occurred.
For TCP sockets, the return value 0 means the peer has closed its half
side of the connection.
返回值
这些调用返回接收到的字节数,如果出现错误,则返回-1
发生。
对于TCP套接字,返回值0表示对等方已关闭其一半
连接的一侧。
那么,您收到了多少字节?如果放弃recv()
的结果,则无法判断。可能recv()
失败了,如果不检查返回值,您将永远不会发现。也许它只填满了你的一部分缓冲区。您必须检查recv()
中的返回代码。这是人们在编写使用TCP的程序时犯的第一个错误
您需要修改代码以处理以下情况:
recv()
调用可能会完全填满您的缓冲区recv()
调用可能会部分填充缓冲区recv()
调用可能返回0,表示发送方已关闭连接recv()
调用可能指示EINTR
,因为它被系统调用中断recv()
调用可能指示econreset
,因为发送方突然关闭了连接或已消失recv()
调用可能会遇到其他错误请记住:在使用TCP时,仅仅因为您
send()
16字节,并不意味着另一个对等方将recv()
16字节-它可能会被分成块。TCP是一种流协议。与UDP不同,相邻的数据块可以任意合并或拆分。我们假设客户机是以正确的反向算法解包的(请注意,您应该使用unsigned char
作为包缓冲区)。此外,第一种情况下的结构打包以及机器端位格式也将发挥作用。对于uint16和uint32,您可以只使用ntohl、ntohs、htonl和htons。简单的回答是:您必须在字节级别精确定义数据格式,并在发送和接收时正确地转换为“有线格式”。@DavidSchwartz我同意,但几天前,当我为一个类似的问题提出解决方案时,我立即跳到了另一边,因为它不存在“标准”。仍然与此不同。(顺便说一句,我仍然会使用POSIX函数,就像你可能会做的那样)。有两个明显的问题。首先,send(socket,&my_struct,sizeof(my_struct))不起作用,因为send()需要四个参数。其次,序列化应该使用无符号字符(即“byte”)正如已经指出的那样,“网络”功能可以避免端点问题,而不是字符。WhozCraig:由于这个问题被标记为C
和C++
,并使用uint32\u t
,我无法想象“标准”是什么“可能是……)你能解释一下为什么缓冲区会被签名吗?这个答案是错误的。这里的屏蔽在任何具有8位字节的平台上都不会起任何作用。由于arg
和buffer
都是无符号的,因此根据C标准,所有算法都是模块化的。因此,如果x
具有类型unsigned char
,并且unsigned char
具有8位,则x=arg
和x=arg&0xff
产生完全相同的结果。@DietrichEpp-给出答案时,问题指定缓冲区是有符号的,这个问题是在回答后两个小时编辑的。@BinyaminSharet:当然,但即使签名,也不会对两个补码平台产生影响,例如所有平台。我知道,但我正在做一个学校项目,我们不允许使用任何库,也不能使用文本协议:(请定义“库”?C库是库吗?(例如,您允许使用htonX/ntohX函数吗?)我们允许使用libc htons/htonl&ntohX,但我从未使用过它们,我不知道如何使用它们。)to@WhozCraig:我认为没有粗体也可以。我的客户端发送返回值为17,服务器接收返回值也为17:(@camiletolsa:我想你误解了。没有必要告诉我返回值是什么,因为每次返回值都可能不同。你必须修改你的程序,使它能够正确地处理recv()
可以给出的每个返回值。我理解,但我使用循环缓冲来处理案例[1]&[2],案例[3]是句柄,案例[4]、[5]或[6]在我的测试中从未出现过。@CamilleTolsa:尝试使用Valgrind运行客户端和服务器。由于您尚未发布至少处理案例1和案例2的固定代码,我无法知道它是否正确。
buff = new char[bav];
socket->read(buff, bav);
hexdump("buff", buff, bav);
socket->read() :
int TcpSocket::read(char *buff, int len)
{
return recv(this->_socket, buff, len, 0);
}
RETURN VALUES
These calls return the number of bytes received, or -1 if an error
occurred.
For TCP sockets, the return value 0 means the peer has closed its half
side of the connection.