http 通常只是一种client/server的通信方式,只能由client 请求时,服务器端才能够向client发送响应数据。这在某些场合是不方便的。比如在嵌入式系统的web服务器中,服务器需要将硬件采集的数据定时发送给客户端的HTML 界面上。这是可以使用websocket 机制。
Websocket 建立浏览器和web服务器之间一个socket 连接。任何一方都可以在任何时候开始发送数据。
该程序每隔2秒发送一个模拟温度的随机数到Client 端。
几点提示:
产生一个随机数据来模拟物理传感器的值。
#include <iostream>
#include <random>
using std::cout; using std::endl;
using std::default_random_engine;
using std::uniform_real_distribution;
int main()
{
default_random_engine e;
uniform_real_distribution<double> u(0, 1); //随机数分布对象
for (size_t i = 0; i < 5; ++i) //生成范围为0.0-1.0的随机数序列
cout << u(e) << " ";
cout << endl;
return 0;
}
#include <iomanip> // setprecision
#include <sstream> // stringstream
double pi = 3.14159265359;
stringstream stream;
stream << fixed << setprecision(2) << pi;
string s = stream.str();
服务器端C++
程序来源于:https://github.com/Corvusoft/restbed/blob/master/documentation/example/WEB_SOCKET.md
#include <map>
#include <chrono>
#include <string>
#include <cstring>
#include <iomanip> // setprecision cout
#include <memory>
#include <utility>
#include <cstdlib>
#include <fstream>
#include <restbed>
#include <system_error>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <random>
using namespace std;
using namespace restbed;
using namespace std::chrono;
using std::default_random_engine;
using std::uniform_real_distribution;
default_random_engine temperature;
uniform_real_distribution<double> u(0, 1);
shared_ptr< Service > service = nullptr;
map< string, shared_ptr< WebSocket > > sockets = { };
string base64_encode( const unsigned char* input, int length )
{
BIO* bmem, *b64;
BUF_MEM* bptr;
b64 = BIO_new( BIO_f_base64( ) );
bmem = BIO_new( BIO_s_mem( ) );
b64 = BIO_push( b64, bmem );
BIO_write( b64, input, length );
( void ) BIO_flush( b64 );
BIO_get_mem_ptr( b64, &bptr );
char* buff = ( char* )malloc( bptr->length );
memcpy( buff, bptr->data, bptr->length - 1 );
buff[ bptr->length - 1 ] = 0;
BIO_free_all( b64 );
return buff;
}
multimap< string, string > build_websocket_handshake_response_headers( const shared_ptr< const Request >& request )
{
auto key = request->get_header( "Sec-WebSocket-Key" );
key.append( "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" );
Byte hash[ SHA_DIGEST_LENGTH ];
SHA1( reinterpret_cast< const unsigned char* >( key.data( ) ), key.length( ), hash );
multimap< string, string > headers;
headers.insert( make_pair( "Upgrade", "websocket" ) );
headers.insert( make_pair( "Connection", "Upgrade" ) );
headers.insert( make_pair( "Sec-WebSocket-Accept", base64_encode( hash, SHA_DIGEST_LENGTH ) ) );
return headers;
}
void ping_handler( void )
{
for ( auto entry : sockets )
{
auto key = entry.first;
auto socket = entry.second;
if ( socket->is_open( ) )
{
socket->send( WebSocketMessage::PING_FRAME );
}
else
{
socket->close( );
}
}
}
void data_handler( void )
{
for ( auto entry : sockets )
{
auto key = entry.first;
auto socket = entry.second;
if ( socket->is_open( ) )
{ stringstream stream;
stream << fixed << setprecision(2)<<u(temperature);
socket->send( "Temperature:"+stream.str(), [ ]( const shared_ptr< WebSocket > socket )
{
const auto key = socket->get_key( );
sockets.insert( make_pair( key, socket ) );
fprintf( stderr, "Send Data message to %s.\n", key.data( ) );
} );
}
else
{
socket->close( );
}
}
}
void close_handler( const shared_ptr< WebSocket > socket )
{
if ( socket->is_open( ) )
{
auto response = make_shared< WebSocketMessage >( WebSocketMessage::CONNECTION_CLOSE_FRAME, Bytes( { 10, 00 } ) );
socket->send( response );
}
const auto key = socket->get_key( );
sockets.erase( key );
fprintf( stderr, "Closed connection to %s.\n", key.data( ) );
}
void error_handler( const shared_ptr< WebSocket > socket, const error_code error )
{
const auto key = socket->get_key( );
fprintf( stderr, "WebSocket Errored '%s' for %s.\n", error.message( ).data( ), key.data( ) );
}
void message_handler( const shared_ptr< WebSocket > source, const shared_ptr< WebSocketMessage > message )
{
const auto opcode = message->get_opcode( );
if ( opcode == WebSocketMessage::PING_FRAME )
{
auto response = make_shared< WebSocketMessage >( WebSocketMessage::PONG_FRAME, message->get_data( ) );
source->send( response );
}
else if ( opcode == WebSocketMessage::PONG_FRAME )
{
//Ignore PONG_FRAME.
//
//Every time the ping_handler is scheduled to run, it fires off a PING_FRAME to each
//WebSocket. The client, if behaving correctly, will respond with a PONG_FRAME.
//
//On each occasion the underlying TCP socket sees any packet data transfer, whether
//a PING, PONG, TEXT, or BINARY... frame. It will automatically reset the timeout counter
//leaving the connection active; see also Settings::set_connection_timeout.
return;
}
else if ( opcode == WebSocketMessage::CONNECTION_CLOSE_FRAME )
{
source->close( );
}
else if ( opcode == WebSocketMessage::BINARY_FRAME )
{
//We don't support binary data.
auto response = make_shared< WebSocketMessage >( WebSocketMessage::CONNECTION_CLOSE_FRAME, Bytes( { 10, 03 } ) );
source->send( response );
}
else if ( opcode == WebSocketMessage::TEXT_FRAME )
{
auto response = make_shared< WebSocketMessage >( *message );
response->set_mask( 0 );
for ( auto socket : sockets )
{
auto destination = socket.second;
destination->send( response );
}
const auto key = source->get_key( );
const auto data = String::format( "Received message '%.*s' from %s\n", message->get_data( ).size( ), message->get_data( ).data( ), key.data( ) );
fprintf( stderr, "%s", data.data( ) );
}
}
void get_method_handler( const shared_ptr< Session > session )
{
const auto request = session->get_request( );
const auto connection_header = request->get_header( "connection", String::lowercase );
if ( connection_header.find( "upgrade" ) not_eq string::npos )
{
if ( request->get_header( "upgrade", String::lowercase ) == "websocket" )
{
const auto headers = build_websocket_handshake_response_headers( request );
session->upgrade( SWITCHING_PROTOCOLS, headers, [ ]( const shared_ptr< WebSocket > socket )
{
if ( socket->is_open( ) )
{
socket->set_close_handler( close_handler );
socket->set_error_handler( error_handler );
socket->set_message_handler( message_handler );
socket->send( "Welcome to Corvusoft Chat!", [ ]( const shared_ptr< WebSocket > socket )
{
const auto key = socket->get_key( );
sockets.insert( make_pair( key, socket ) );
fprintf( stderr, "Sent welcome message to %s.\n", key.data( ) );
} );
}
else
{
fprintf( stderr, "WebSocket Negotiation Failed: Client closed connection.\n" );
}
} );
return;
}
}
session->close( BAD_REQUEST );
}
void get_index( const shared_ptr< Session > session )
{
// const auto request = session->get_request( );
ifstream stream( "./views/websocketClient.html", ifstream::in );
if ( stream.is_open( ) )
{
const string body = string( istreambuf_iterator< char >( stream ), istreambuf_iterator< char >( ) );
const multimap< string, string > headers
{
{ "Content-Type", "text/html" },
{ "Content-Length", ::to_string( body.length( ) ) }
};
session->close( OK, body, headers );
}
else
{
session->close( NOT_FOUND );
}
}
int main( const int, const char** )
{
auto resource = make_shared< Resource >( );
resource->set_path( "/chat" );
resource->set_method_handler( "GET", get_method_handler );
auto resource1 = make_shared< Resource >( );
resource1->set_path( "/index" );
resource1->set_method_handler( "GET", get_index );
auto settings = make_shared< Settings >( );
settings->set_port( 1984 );
service = make_shared< Service >( );
service->publish( resource );
service->publish( resource1 );
//service->schedule( ping_handler, milliseconds( 5000 ) );
service->schedule( data_handler, milliseconds( 2000 ) );
service->start( settings );
return EXIT_SUCCESS;
}
client 端的html/javascript
<!DOCTYPE HTML>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<style>
html, body {
margin: 0;
}
ul {
list-style: none;
}
li {
text-align: left;
}
input {
width: 40%;
height: 40px;
line-height: 40px;
margin-bottom: 10px;
}
a {
margin: 10px;
}
.disabled {
color: #000;
pointer-events: none;
}
#controls {
width: 100%;
bottom: 10%;
position: absolute;
text-align: center;
}
</style>
<script type="text/javascript">
function on_return_submit( evt )
{
if ( window.restbed.ws === null || window.restbed.ws.readyState !== window.restbed.ws.OPEN )
{
return;
}
if( evt && evt.keyCode == 13 )
{
var message = document.getElementById( "message" );
window.restbed.ws.send( message.value );
message.value = "";
}
}
function toggle_control_access( )
{
var open = document.getElementById( "open" );
open.disabled = !open.disabled;
var message = document.getElementById( "message" );
message.disabled = !message.disabled;
var close = document.getElementById( "close" );
close.className = ( close.className === "disabled" ) ? "" : "disabled";
}
function add_message( message )
{
var li = document.createElement( "li" );
li.appendChild( document.createTextNode( "> " + message ) );
var ul = document.getElementById( "messages" );
ul.appendChild( li );
}
function open( )
{
if ( "WebSocket" in window )
{
var ws = new WebSocket( "ws://localhost:1984/chat" );
ws.onopen = function( )
{
add_message( "Established connection." );
toggle_control_access( );
};
ws.onmessage = function( evt )
{
//add_message( evt.data );
document.getElementById('value').innerHTML=evt.data;
};
ws.onclose = function( evt )
{
add_message( "Connection closed with RFC6455 code " + evt.code + "." );
toggle_control_access( );
};
ws.onerror = function( evt )
{
add_message( "Error: socket connection interrupted." );
};
window.restbed.ws = ws;
}
else
{
alert( "WebSockets NOT supported by your Browser!" );
}
}
function close( )
{
window.restbed.ws.close( );
}
( function( )
{
window.restbed = { ws: null };
} )( );
</script>
</head>
<body>
<h2>restbed webSocket example</h1>
<p span id="value">0</p>
<div>
<ul id="messages"></ul>
<div id="controls">
<input id="message" type="text" onKeyPress="return on_return_submit( event )" disabled/>
<div>
<a id="open" href="javascript:open( )">Open Chat</a>
<a id="close" href="javascript:close( )" class="disabled">Close Chat</a>
<div>
</div>
</div>
</body>
</html>