一、背景
最近的工作有一项任务就是 websocket 兼容 json 字符串和音视频流,之前都没有接触过音视频方向,网上找了找也没找到合适的博客,大部分都是 js 的代码实现,就干脆自己看 boost 库源码找找方法,正好记录一下这一次的学习过程。
二、过程
因为我的 websocket 协议是用 boost 库实现的,所以就从 boost 库中找对应的实现方法。
1.寻找分辨二进制与字符串类型的函数:
在 boost 的 stream.hpp 头文件中,我找到了设置 opcode 值的函数:
template<class NextLayer, bool deflateSupported>
void
stream<NextLayer, deflateSupported>::
binary(bool value)
{
impl_->wr_opcode = value ?
detail::opcode::binary :
detail::opcode::text;
}
该函数可以设置传输二进制流与字符串,与之对应的还有一个 text 函数:
template<class NextLayer, bool deflateSupported>
void
stream<NextLayer, deflateSupported>::
text(bool value)
{
impl_->wr_opcode = value ?
detail::opcode::text :
detail::opcode::binary;
}
虽然使用一个函数传入 true or false 就可以实现切换,但估计 boost 库开发者想更有可读性就分别实现了 binary 和 text 的函数供大家使用吧。
除此之外还有获取该 opcode 值的函数:
template<class NextLayer, bool deflateSupported>
bool
stream<NextLayer, deflateSupported>::
binary() const
{
return impl_->wr_opcode == detail::opcode::binary;
}
template<class NextLayer, bool deflateSupported>
bool
stream<NextLayer, deflateSupported>::
text() const
{
return impl_->wr_opcode == detail::opcode::text;
}
在这里博主踩了一个坑,详细后面再讲。
2.尝试实现验证
在找到对应的函数之后,博主就准备实现一个 demo 测试一下,看看功能满不满足需求。
博主先是在客户端发送数据时进行对应的切换操作:
if (msg.type == 0) //字符串数据
{
ws_->text(true); //将websocket opcode设置为text模式
//对应的数据操作
}
else if (msg.type == 1) //二进制数据
{
ws_->binary(true); //将websocket opcode设置为binary模式
//对应的数据操作
}
然后在服务端里使用 binary() 与 text() 函数获取对应的 opcode 进行报文的解析:
if (ws_.text()) // 处理字符串数据
{
std::cout << "text() result is true..." << std::endl;
std::string str = boost::beast::buffers_to_string(buffer_.data());
// demo中只是将数据打印出来查看是否正确
std::cout << "receive text data : " << str << std::endl;
buffer_.consume(buffer_.size());
}
if (ws_.binary()) // 处理二进制数据
{
std::cout << "binary() result is true..." << std::endl;
std::string str = boost::beast::buffers_to_string(buffer_.data());
uint8_t *binaryData = (uint8_t *)str.c_str();
int length = str.size();
std::cout << "receive binary data : " << std::endl;
for (int i = 0; i < length; i++)
{
std::cout << binaryData[i] << " ";
}
std::cout << std::endl;
buffer_.consume(buffer_.size());
}
实现之后运行结果如下:
二进制数据被识别成字符串了。通过进一步排查,发现无论是发送二进制数据还是字符串数据,都走到 ws_text() 分支里,如图所示:
显然, text() 函数与 binary() 函数并不能获取到报文的 opcode 进行操作,而是统一默认为字符串了,这不符合我的需求。
3.解决办法
既然客户端可以通过 binary(bool) 与 text(bool) 函数修改发送数据的模式,服务端也能获取对应的模式才对,所以我又重新看了看源码,发送在 stream.hpp 头文件中还有一个函数 got_binary() :
template<class NextLayer, bool deflateSupported>
bool
stream<NextLayer, deflateSupported>::
got_binary() const noexcept
{
return impl_->rd_op == detail::opcode::binary;
}
将该函数与 binary() 函数比较了一下,发现 binary() 函数是 return wr_opcode 的,而 got_binary() return 的是 rd_op ,即 binary() 函数是查看当前端发送的模式,而 got_binary() 是获取当前端接收的模式。那么,一切也就水落石出了。
if (ws_.got_binary()) // 接收的二进制数据
{
std::cout << "got_binary() result is true..." << std::endl;
std::string str = boost::beast::buffers_to_string(buffer_.data());
uint8_t *binaryData = (uint8_t *)str.c_str();
int length = str.size();
std::cout << "receive binary data : " << std::endl;
for (int i = 0; i < length; i++)
{
printf("%#x ", binaryData[i]);
}
std::cout << std::endl;
buffer_.consume(buffer_.size());
}
else // 接收的字符串数据
{
std::cout << "text() result is true..." << std::endl;
std::string str = boost::beast::buffers_to_string(buffer_.data());
std::cout << "receive text data : " << str << std::endl;
buffer_.consume(buffer_.size());
}
修改代码后,运行结果也如我所期望的:
分别对应处理二进制与字符串数据,满足我的需求。
三、心得
看 boost 库源码让我受益良多,我所学习的只是 boost 库的冰山一角,前进的路还很长,就以此篇博客作为我的学习之路的开端吧。
这篇博客只是我的一点小小的感悟和体会,做一个抛砖引玉之用,如果有不同的见解,可以一起交流探讨,互相学习进步。