如何避免使用 TCP 进行序列化/反序列化时的跨语言依赖?
Posted
技术标签:
【中文标题】如何避免使用 TCP 进行序列化/反序列化时的跨语言依赖?【英文标题】:How to avoid cross language dependency in serialization/deserialization using TCP? 【发布时间】:2012-12-04 22:34:11 【问题描述】:我需要创建与 C++ 客户端和 Python 服务器的 TCP 聊天(已经开始),我在 c++ 类中有消息,例如
class Message
public:
uint64 utc_time;
uint64 token;
string content;
;
我将这个从客户端发送到服务器,在服务器上我有 utc_time 的优先级队列,需要广播给其他人。我的问题是如何序列化它,使用哪种格式以避免对大小类型 size 的任何跨语言依赖? (也许将来会有更多元数据,所以需要有点通用)?谁能给我建议用于序列化的格式(或仅刷新字节)?
class Persistent:
public:
Persistent(int sz):objSize(sz)
void write(std::ostream& out)constout.write((char*)this, objSize);
void read(std::istream& in)in.read((char*)this, objSize);
private:
int objSize;
;
我想到了在服务器上使用 c++ 反序列化器并在可能的情况下从 python 调用的其他可能性。这个问题有什么优雅的解决方案吗?
【问题讨论】:
您是否查看过 Protocol Buffers、Thrift、JSON 或您选择的搜索引擎建议的其他一百种常见解决方案中的任何一种? 语言依赖不是问题,不同的机器,即字长、字节序等,都是问题。使用上面建议的已知解决方案,而不是重新发明这个***。只要确保解决方案处理 python 和 c++。可能不是问题。 谷歌协议缓冲区code.google.com/p/protobuf @willglynn 我已经有了 JSON,但我认为问题可能是当我从同一条消息发送两条消息,或者从服务器接收几条消息(每条消息都是 JSON)时如何知道一条消息的结尾在哪里如果我得到的只是字节数组。 (也许要使用数组,但我会在 99% 中只发送一个,而这 1% 很关键) @willglynn:“如果我得到的只是字节数组,如何知道结尾在哪里。”这就是所谓的“解析 JSON”。无论你用什么工具来解析它都会知道终点在哪里。 【参考方案1】:如果你真的想跨语言跨平台而不必担心消息在哪里结束,看看Google Protobuf和ZeroMQ的组合。
当使用常规套接字时,您将首先读取消息的大小(您将在前面添加它),然后您会知道字节数组从哪里到哪里是一个完整的消息。
protobuf + zmq 用法示例:
message Message
optional uint64 utc_time = 1;
required uint64 token = 2;
optional string content = 3;
使用 protobuf 编译器生成 C++ 代码(或 ruby/python/etc)。
在您的代码中使用它:
#include <Message.pb.h>
Message msg;
msg.set_token(1);
msg.set_content("Hello world");
使用 zmq 发送:
std::string serialized = msg.SerializeAsString();
zmq::message_t reply(serialized.size());
memcpy(reply.data(), serialized.data(), serialized.size());
zmq_socket.send(reply);
使用 zmq 接收它:
zmq::message_t request;
zmq_socket.recv(&request); // blocking
Message recv_msg;
recv_msg.ParseFromArray(request.data(), request.size());
【讨论】:
【参考方案2】:使用 ZeroMQ 是一个好的开始,因为它会为您处理所有传输工作。序列化的最佳方式取决于您正在做的工作类型。由于您正在做一个聊天应用程序,效率不是问题,所以我会使用自描述文本格式,它最易于调试、跟踪、记录和使用。像 protobufs 或 msgpack 这样的任何东西都将是额外的工作,没有可衡量的回报。您可以使用 XML、JSON、HTTP 样式的标头、名称=值对等。
当您开始制作非常大量的消息(每秒数十万条)或非常大的消息(例如 1K 字节以上)时,您可以开始考虑减少消息大小的不同方法。我个人建议坚持使用最简单的文本格式,直到您确实遇到性能问题,然后在需要它的情况下切换到最有效的二进制格式。但不是在聊天应用中...
【讨论】:
我不同意这是更多的工作。也需要解析 JSON 或 XML。序列化/反序列化 protobuf 消息是内置的。您甚至可以将其作为文本发送(在对象上调用.DebugString()
)并再次将其解析到 protobuffer 中。您可以将消息作为常规对象处理,而无需自己编写任何访问器代码。
@gvd:但这使得使用 TCP/IP 嗅探工具在网络上调试协议变得更加困难。
@jmucchiello 是真的,因为它不是人类可读的,但如果你必须这样做,那么你一开始就做错了。添加一些单元测试。您可以在发送之前和收到之后立即在 protobuf 上调用 .PrintDebugString()
以检查内容是否相同。【参考方案3】:
我喜欢使用 JSON,假设您有一个良好的套接字缓冲接口和一个基于流的 JSON 解析器。 JSON 的好处是您不需要指定每条消息的长度。正确编写的 JSON 解析器可以判断它何时到达“对象”的末尾。因此,您的阅读器对象只需解析通过网络传输的 JSON,当您到达初始对象的末尾时,将其作为一条消息返回给系统。
如果 JSON 对您的数据来说太过分了,那么总会有纯文本。大多数互联网以纯文本(POP、IMAP、HTTP、FTP 等)运行。这是因为纯文本是使用跨平台/跨语言最简单的方法。
【讨论】:
命令结构的纯文本仍然是最简单的。使用 Unicode 字符串的情况更糟,您可以将它们作为十六进制转储(或 base64,如果您愿意)发送。但 HELO 的简约之美不容忽视。 我明白你在说什么,但“容易”是主观的。例如,“H”比“HELO”更简单,但我不能说没有更多上下文更容易。以上是关于如何避免使用 TCP 进行序列化/反序列化时的跨语言依赖?的主要内容,如果未能解决你的问题,请参考以下文章
从其他实体检索ID时的Spring Boot JPA反序列化问题