前面介绍了服务器端如何监听和增量读取文件,这里通过基于boost asio的websocketpp,实现了一个简单的websocket服务端,能够和浏览器进行通信,将读取到的文件通过websocket协议进行实时传送。
关于websocket的简单介绍,可以参考维基百科。websocket协议,在rfc6455中定义。
websocketpp对websocket和简单的http都进行了比较好的封装,只要实现几个handler,就可以完成对连接、消息等的操作和控制。主要需要处理的,可能有以下的handler:
[cce lang="cpp"]
typedef lib::function<void(connection_hdl)> open_handler;
typedef lib::function<void(connection_hdl)> close_handler;
typedef lib::function<void(connection_hdl)> http_handler;
typedef lib::function<void(connection_hdl,message_ptr)> message_handler
[/cce]
分别处理连接创建,连接关闭,http请求和消息请求。其中connection_hdl是连接的weak_ptr。
这里对websocket使用很简单,唯一的需求,就是维护已经建立的连接(既创建连接的时候记录,关闭连接的时候移出),然后通过将自己的回调注册到文件监控类中,实时的将消息推送到websocket客户端。
首先,维护一个set,用来保存当前已经建立的所有连接:
[cce lang="cpp"]
typedef std::set<websocketpp::connection_hdl,boost::owner_less<websocketpp::connection_hdl> > ConnectionSet;
[/cce]
然后在建立连接的时候插入到这个set中:
[cce lang="cpp"]
void WebSocketServer::onOpen ( websocketpp::connection_hdl hdl )
{
boost::lock_guard<boost::mutex> lock(_mutex);
_conns.insert(hdl);
}
[/cce]
在连接关闭的时候,移除连接:
[cce lang="cpp"]
void WebSocketServer::onClose ( websocketpp::connection_hdl hdl )
{
boost::lock_guard<boost::mutex> lock(_mutex);
_conns.erase(hdl);
}
[/cce]
另外,定义一个让filewatcher调用的回调:
[cce lang="cpp"]
void WebSocketServer::write ( const std::string& content )
{
boost::lock_guard<boost::mutex> lock(_mutex);
BOOST_AUTO(it, _conns.begin());
for(; it != _conns.end(); ++it) {
_s.send(*it, content, websocketpp::frame::opcode::text);
}
}
[/cce]
这个回调很简单,就是当有读取到内容的时候,遍历连接集合,向每个连接发送具体的内容。
最后,为了方便用户访问,当发现是标准http请求的时候,我们返回一个简单的html,用于显示和建立websocket连接。如果不指定具体的http_handler,websocketpp在发现请求是http的时候,会返回http的426错误(Upgrade Required)。
[cce lang="cpp"]
void WebSocketServer::httpHandler ( websocketpp::connection_hdl hdl )
{
server::connection_ptr connPtr = _s.get_con_from_hdl(hdl);;
connPtr->set_status(websocketpp::http::status_code::ok);
connPtr->set_body(htmlContent);
}
[/cce]
另外,还有一个小坑。按照websocketpp的example,在启动的时候都是直接调用server的listen函数,而且使用的都是只有端口号的那个实现。实际使用过程中,发现这个只有端口号的实现,直接使用了ipv6协议。虽说如果本机同时支持ipv6和ipv4的情况下,两个协议对应的端口都会监听,但是遇到了服务器上关闭了ipv6,会导致boost asio抛出address_family_not_supported异常,导致应用被迫退出。为了兼容这种方式,对这个异常进行了抓取,重新降级尝试ipv4协议,这样能够很好的在只有ipv4的服务器上进行使用。
[cce lang="cpp"]
try{
_s.listen(port);
} catch(boost::system::system_error const& e) {
if(e.code() == boost::asio::error::address_family_not_supported) {
_s.listen(boost::asio::ip::tcp::v4(), port);
}
} catch (...) {
throw;
}
[/cce]