我有一个客户端和一个服务器应用程序,它们将通过使用Asio(独立)库相互发送数据。这两个应用程序都包含两个(逻辑)部分:
假设已经使用Protocoll Buffers对复杂对象进行了序列化,并且应用程序的底层部分从高层部分接收数据,如std :: string。我想使用Protocoll Buffers中的此功能来完成此工作:
bool SerializeToString(字符串*输出)const ;:对消息进行序列化并将字节存储在给定的字符串中。注意字节是二进制的,不是文本;我们仅将字符串类用作方便的容器。
并说我在客户端使用async_write传输此数据:
size_t dataLength = strlen(data);
//writes a certain number of bytes of data to a stream.
asio::async_write(mSocket,
asio::buffer(data, dataLength),
std::bind(&Client::writeCallback, this,
std::placeholders::_1,
std::placeholders::_2));
如何在服务器端读取此数据?我不知道我将要读取多少数据。因此,这将不起作用(长度未知):
asio::async_read(mSocket,
asio::buffer(mResponse, length),
std::bind(&Server::readCallback, this,
std::placeholders::_1,
std::placeholders::_2));
解决此问题的最佳方法是什么?我可以想到两种解决方案:
data
并读取,直到到达“数据信号结尾”为止。问题是,如果此字符以data
某种方式出现怎么办?我不知道Protocoll Buffers如何序列化我的数据。size_of_data + data
而不是的二进制字符串data
。但是我不知道如何以独立于平台的方式序列化大小,将其添加到二进制数据并再次提取。编辑:也许我可以用这个:
uint64_t length = strlen(data);
uint64_t nwlength = htonl(length);
uint8_t len[8];
len[0] = nwlength >> 56;
len[1] = nwlength >> 48;
len[2] = nwlength >> 40;
len[3] = nwlength >> 32;
len[4] = nwlength >> 24;
len[5] = nwlength >> 16;
len[6] = nwlength >> 8;
len[7] = nwlength >> 0;
std::string test(len);
mRequest = data;
mRequest.insert(0, test);
并发送mRequest到服务器?此代码有任何陷阱或警告吗?之后如何读取服务器端的长度和内容?可能是这样的:
void Server::readHeader(){
asio::async_read(mSocket,
asio::buffer(header, HEADER_LENGTH),
std::bind(&Server::readHeaderCallback, this,
std::placeholders::_1,
std::placeholders::_2),
asio::transfer_exactly(HEADER_LENGTH));
}
void Server::readHeaderCallback(const asio::error_code& error,
size_t bytes_transferred){
if(!error && decodeHeader(header, mResponseLength)){
//reading header finished, now read the content
readContent();
}
else{
if(error) std::cout << "Read failed: " << error.message() << "\n";
else std::cout << "decodeHeader failed \n";
}
}
void Server::readContent(){
asio::async_read(mSocket,
asio::buffer(mResponse, mResponseLength),
std::bind(&Server::readContentCallback, this,
std::placeholders::_1,
std::placeholders::_2),
asio::transfer_exactly(mResponseLength));
}
void Server::readContentCallback(const asio::error_code& error,
size_t bytes_transferred){
if (!error){
//handle content
}
else{
//@todo remove this cout
std::cout << "Read failed: " << error.message() << "\n";
}
}
请注意,我尝试使用transfer_exactly
。这样行吗?
通过基于流的协议发送可变长度消息时,通常存在三种指示消息边界的解决方案:
async_read_until()
操作提供了一种读取可变长度定界消息的便捷方法。使用定界符时,需要考虑定界符冲突的可能性,其中定界符出现在消息的内容之内,但并不表示边界。有多种处理定界符冲突的技术,例如转义字符或转义序列。将固定长度的标头与可变长度的主体协议一起使用。标头将提供有关消息的元信息,例如正文的长度。官方的Asio聊天示例演示了一种处理固定长度报头和可变长度主体协议的方法。
如果要发送二进制数据,则需要考虑处理字节顺序。在hton()
和ntoh()
系列函数可以用字节序帮助。例如,考虑一种协议,该协议将字段定义为网络字节顺序(big-endian)的两个字节,并且客户端将字段读取为uint16_t
。如果10
发送了该值,并且little-endian机器读取了该值而不将其从网络顺序转换为本地顺序,则客户端会将值读取为2560
。Asio聊天示例通过将主体长度编码为字符串而不是二进制形式来避免处理字节序。
使用连接的文件结尾来指示消息的结尾。虽然这使发送和接收消息变得容易,但将发件人限制为每个连接仅发送一条消息。要发送其他消息,则需要建立另一个连接。
关于代码的一些观察:
SerializeToString()
功能将消息序列化为二进制形式。应该避免在序列化字符串上使用基于文本的函数,例如strlen()
。例如,strlen()
可能会错误地确定长度,因为它将把值为的第一个字节0
视为终止的空字节,即使该字节是编码值的一部分。当通过提供一个明确大小的缓冲区给操作时asio::buffer(buffer, n)
,默认的完成条件的transfer_all
功能与相同transfer_exactly(n)
。因此,可以删除重复使用的变量:
asio::async_read(mSocket,
asio::buffer(header, HEADER_LENGTH),
std::bind(&Server::readHeaderCallback, this,
std::placeholders::_1,
std::placeholders::_2));
在htonl()
重载支持uint16_t
和uint_32t
不uint64_t
。
Asio支持分散/聚集操作,允许接收操作分散读取到多个缓冲区中,而发送操作可以从多个缓冲区收集-写入。因此,不一定必须将固定长度的标头和消息正文都包含在单个缓冲区中。
std::string body_buffer;
body.SerializeToString(&body_buffer);
std::string header_buffer = encode_header(body_buffer.size());
// Use "gather-write" to send both the header and data in a
// single write operation.
std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(header_buffer));
buffers.push_back(boost::asio::buffer(body_buffer));
boost::asio::write(socket_, buffers);
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句