你不能只是“发送”你的类。你必须发送你的类数据的表示。同时,发送指向你的类的指针也不起作用。当网络上的另一个应用程序接收到你的指针时,它对他们来说毫无意义。
考虑以下内容:
class Data
{
std::string name_;
unsigned value_;
} data;
你不能直接将“类”发送到网络中。如果你尝试这样做:
send(&data, sizeof(data));
你最终会向下游客户端发送无意义的内容。
value_
可能会被正确接收,但
name_
则肯定不会。这是因为
std::string
对象包含的远不止构成字符串的字符。还有计数器、指向数据缓冲区的指针以及谁知道还有什么其他东西。这些字符本身可能甚至不会在
(&data, &data[sizeof(data)])
指示的内存空间中——这些字符将完全在其他地方。因此,在上述
send
伪代码中,你不仅会发送一堆客户端无法理解的东西,而且很多时候你甚至没有发送他们可以理解的内容。
进入
序列化。序列化只是意味着创建一个数据对象的表示形式,该表示形式可以存储、保存或发送到某个地方,并稍后重新组装。你决定这种序列化的形状。对于上面的
data
,我们可以决定像这样进行序列化:
NNNNCCCCCCCC...VVVV
每个字符占用1字节,且:
- N是一个4位数字,表示ASCII格式下
name_
中的字符数
- C是N字节,每个字节都是
name_
中的一个字符
- V是一个4位无符号整数,表示ASCII格式下
value_
的值
将上述
data
序列化并发送的一种方法可能如下(警告:非生产质量,未经测试):
stringstream ss;
ss
<< setw(4) << setfill('0') << right << data.name_.length() << setw(0)
<< data.name_
<< setw(4) << data.value_;
string buf = ss.str();
send(buf.c_str(), buf.length());
现在,我们不再尝试发送
data
,而是发送一个代表
data
的字符字符串。如果
data
是这样创建的:
data.name_ = "foo"
data.value_ = 42
如果这样做,通过套接字发送的字符串将是:
0003foo0042
这可以被客户端接收并在客户端上重新组装成一个新的
Data
对象,该对象模仿了服务器端的内容。
我们使用的映射 -
NNNNCCCCCCCC...VVVV
- 必须被双方理解。这通常被称为
通信协议。
在所有应用程序领域中都有无数的协议和序列化方法。它们从超级简单的方法(如上面概述的方法)到高度压缩和复杂的方法(如
FIX/FAST)。许多库提供序列化功能,满足各种应用程序的需求。
Boost.Serialization值得一试,建议您研究一下。
这里只是简单地概括了一些内容,实际上还有很多需要注意的地方,比如字节序(Endianness), 安全性,会话控制等等。如果你要进行任何重要的网络编程,无论是客户端还是服务器端,你都需要做出很多学习努力。