C++基于 STL 发送二进制序列化数据以使用套接字进行网络传输,无需使用库

发布于 2024-12-10 01:54:36 字数 1924 浏览 0 评论 0原文

我需要通过网络向对等方发送许多复杂的对象。我已经编写了使用 ostream 和运算符<< 序列化它们的代码对于需要序列化的对象中的每个类成员。我编写的代码成功地用于序列化和网络发送(htonl(),htons()等。正确使用 - 我通过以二进制格式(std :: ios :: bin)写入ofstream(本地文件)来检查前述内容)。我的下一个任务是通过网络套接字编写此二进制数据,这是我遇到问题的地方,

该类将 std::string 对象转换为 C 风格的字符串,然后再通过套接字发送它们:

int Socket::send (const std::string goodies) const
{
    status = ::send (socket, goodies.c_str(), goodies.size(), 0);
            return status;
}

相同的 Socket。类,我在接收器,使用recv()将传入消息放入std::string中,然后将其传递给反序列化应用程序:

int Socket::recv (std::string& goodies)
{
    char buf [1024];
    goodies = "";
    memset (buf, 0, 1025);

    int status = ::recv (socket, buf, 1024, 0);

    if (status < 0)
    {
        return -1;
    }
    else if (status == 0)
    {
        return 0;
    }
    else
    {
        goodies = buf;
        return status;
    }
}

我使用以下代码进行发送:

ostringstream os (std::ios::binary);
GiantObject giantComplexObjectWithWholeLoadOfOtherObjects;
// Initialize and set up 
// giantComplexObjectWithWholeLoadOfOtherObjects.

// The following call works well--I tested it by writing to ofstream locally (file)
// and checked it out using a hex dump.
// Now, of course, my intent is to send it over the network, so I write to
// ostream&:
giantComplexObjectWithWholeLoadOfOtherObjects.serialize (os);

std::string someStr = os.str(); // Get me the stream in std::string format

mySocket.send(someStr); // Does not work--send sent correctly, but recv received 0 bytes

但是,如果我尝试:

std::string someStr ("Some silly string");
mySocket.send (someStr); // received by receiver (receiver is similar arch).

因此,我的调用有些不对劲发送二进制 std::string 到套接字。再次,我不想使用 Boost、protobuf 等。

PS:我花了相当多的时间查看这里的旧帖子,以及收到此类问题的第一反应是请使用 Boost——还有其他非 Boost、非 Protobuf 的方法,我想了解这些其他方法。我确实很欣赏 Boost 和 Protobuf 带来的好处,我只想按照我设置代码的方式来做到这一点。

I need to send a number of complex objects over the network to a peer. I have written the code to serialize them using ostream and the operator<< for each class member in the objects that need to be serialized. The code I have written works successfully for serialization and network sending (htonl(), htons() etc. correctly used--I checked the aforementioned by writing to an ofstream (local file) in binary format (std::ios::bin). My next task, writing this binary data over the network socket, is where I am having issues.

I have Socket class which translates std::string objects into C-style strings before sending them over the socket like so:

int Socket::send (const std::string goodies) const
{
    status = ::send (socket, goodies.c_str(), goodies.size(), 0);
            return status;
}

the same Socket class, which I use in the receiver, uses the recv() to place the incoming message into a std::string before passing it to the deserializing application:

int Socket::recv (std::string& goodies)
{
    char buf [1024];
    goodies = "";
    memset (buf, 0, 1025);

    int status = ::recv (socket, buf, 1024, 0);

    if (status < 0)
    {
        return -1;
    }
    else if (status == 0)
    {
        return 0;
    }
    else
    {
        goodies = buf;
        return status;
    }
}

I do the sending using the following code:

ostringstream os (std::ios::binary);
GiantObject giantComplexObjectWithWholeLoadOfOtherObjects;
// Initialize and set up 
// giantComplexObjectWithWholeLoadOfOtherObjects.

// The following call works well--I tested it by writing to ofstream locally (file)
// and checked it out using a hex dump.
// Now, of course, my intent is to send it over the network, so I write to
// ostream&:
giantComplexObjectWithWholeLoadOfOtherObjects.serialize (os);

std::string someStr = os.str(); // Get me the stream in std::string format

mySocket.send(someStr); // Does not work--send sent correctly, but recv received 0 bytes

However, if I try:

std::string someStr ("Some silly string");
mySocket.send (someStr); // received by receiver (receiver is similar arch).

Thereby, something is not right about my call to send binary std::string to the socket. Any help is greatly appreciated. Once again, I do not want to use Boost, protobuf etc.

PS: I have spent a considerable amount of time looking over the old posts here, and the first response these types of questions receive is to use Boost. Please--there are other non-Boost, non-Protobuf ways, and I want to understand those other ways. I do appreciate what Boost and Protobuf bring to the table, I just want to do this in the manner I have set up the code.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

苏别ゝ 2024-12-17 01:54:36

我相信一个问题是使用 std::string 作为容器。尤其是 goodies = buf; 行。 char* 中的 std::string 赋值运算符假定输入以第一个 NUL 结束。由于二进制数据可以包含任意 NUL 字符,因此它可能会比您预期的更早终止。

对于这样的应用程序,我建议使用 std::vectorstd::vector。由于缓冲区中的内存始终是连续的,因此您可以通过获取第一个元素的地址来访问底层缓冲区。

对于接收端,您将需要使用 std::copy 来复制数据。或者您可以将其设置为使用向量缓冲区,并在接收完成后进行交换。这假设您希望在状态 <= 0 时不修改好东西。即使在这些情况下,如果您可以接受更改好东西,则可以在调用 ::recv() 之前调整其大小,并避免创建额外的向量。

int Socket::send (const std::vector<char>& goodies) const {
    status = ::send (socket, &goodies[0], goodies.size(), 0);            
    return status; 

int Socket::recv (std::vector<char>& goodies)  {    
  std::vector<char> buffer(1024);
  int status = ::recv (socket, &buf[0], buf.size());
  if (status < 0) 
  {
     return -1;      
  }
  else if (status == 0)
  { 
     return 0;
  }
  else
  {
     buffer.resize(status);
     goodies.swap(buffer);
     return status;      
  } 
}

I believe one problem is the use of std::string as your container. Especially the line goodies = buf;. The std::string assignment operator from a char* assumes that the input ends at the first NUL. Since binary data can contain arbritrary NUL characters, it will likely terminate earlier than you expect.

For an application such as this, I recommend the use of std::vector<char> or std::vector<unsigned char>. Since the memory in the buffer is always contiguous, you can access the underlying buffer by taking the address of the first element.

For the receive side, you will want to use std::copy to copy the data. Or you can set it up to use a vector buffer, and swap once the receive is complete. This assumes that you want goodies unmodified if the status is <= 0. If you can accept goodies being changed even in those cases, you can resize it prior to calling ::recv(), and avoid creating the extra vector.

int Socket::send (const std::vector<char>& goodies) const {
    status = ::send (socket, &goodies[0], goodies.size(), 0);            
    return status; 

int Socket::recv (std::vector<char>& goodies)  {    
  std::vector<char> buffer(1024);
  int status = ::recv (socket, &buf[0], buf.size());
  if (status < 0) 
  {
     return -1;      
  }
  else if (status == 0)
  { 
     return 0;
  }
  else
  {
     buffer.resize(status);
     goodies.swap(buffer);
     return status;      
  } 
}
终止放荡 2024-12-17 01:54:36

上面的一个问题是您假设“send”将始终在一次调用中发送 goodies.size() 字节。不需要这样做:您需要重复调​​用 send,直到发送完 goodies.size() 字节。此外,您可能希望通过 const ref 而不是通过值来获取好东西,以避免复制巨大的对象。

同样,在recv 方面,您无法保证recv 会返回给您一个完整的对象。您将需要重复将数据消耗到缓冲区中,直到确定已提取完整的对象。这意味着当您发送对象时,您要么需要使用面向消息的协议(如 SCTP)在协议级别传递消息边界,要么使用流协议,但在消息格式中包含帧信息。这将允许消费者提取描述对象中期望的数据长度的标头。

顺便说一句,这就是为什么您会收到很多只使用现有框架的回复,因为它们会为您处理这些类型的框架和消息组装问题。

One issue in the above is that you are assuming that 'send' will always send goodies.size() bytes in one call. It is not required to do so: you need to call send repeatedly until you have sent goodies.size() bytes. In addition, you probably want to take goodies by const ref, rather than by value to avoid copying your huge object.

On the recv side, again, you have no assurances that recv will return to you a complete object. You will need to repeatedly consume data into a buffer until you have determined that you have extracted a complete object. This means that when you send your object, you will either need to use a message oriented protocol like SCTP that delivers message boundaries at the protocol level, or use a streaming protocol, but include framing information in your message format. That will allow the consumer to extract a header describing the length of data to expect in the object.

This, BTW, is why you will get lots of replies to just use an existing framework, because they take care of these sorts of framing and message assembly issues for you.

颜漓半夏 2024-12-17 01:54:36

立即弹出的一件事是,您正在使用 std::ostringstream 进行输入和输出,我相信这只是为了进行输出。您使用的是 void ostringstream::str(string) 吗?或者您正在使用提取运算符?我不确定两者是否有效,但我认为前者会有效。但是,如果您要在 stringstream 上进行输入和输出,则实际上应该使用 stringstream (它将模式设置为 ios_base::in | ios_base::out

One thing that pops up immediately is that you are doing both input and output with the std::ostringstream, which I believe is only meant to do output. Are you using void ostringstream::str(string)? Or are you using the extraction operator? I'm not sure if either would work, but I would think the former would work. However, if you're going to do input and output on the stringstream, you should really just use stringstream (which sets the mode to ios_base::in | ios_base::out.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文