一、介绍
- C++开发服务端,使用websocketpp库(0.8.2),直接加载头文件即可,下载地址:https://github.com/zaphoyd/websocketpp.git
- 依赖boost库、openssl库
- JavaScript开发客户端
- 该测试例子支持websocket ws和websocket wss协议
二、测试代码
-
server.cpp
#include <websocketpp/config/asio.hpp> #include <websocketpp/server.hpp> #include <iostream>typedef websocketpp::server<websocketpp::config::asio> server; typedef websocketpp::server<websocketpp::config::asio_tls> server_tls; typedef websocketpp::config::asio::message_type::ptr message_ptr; typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context> context_ptr;using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; using websocketpp::lib::bind;void on_message_ws(server* s, websocketpp::connection_hdl hdl, message_ptr msg) {std::cout << "on_message_ws called with hdl: " << hdl.lock().get() << " and message: " << msg->get_payload() << std::endl;try {s->send(hdl, msg->get_payload(), msg->get_opcode());} catch (websocketpp::exception const & e) {std::cout << "Echo failed because: "<< "(" << e.what() << ")" << std::endl;} }void on_message_wss(server_tls* s, websocketpp::connection_hdl hdl, message_ptr msg) {std::cout << "on_message_wss called with hdl: " << hdl.lock().get()<< " and message: " << msg->get_payload()<< std::endl;try {s->send(hdl, msg->get_payload(), msg->get_opcode());} catch (websocketpp::exception const & e) {std::cout << "Echo failed because: "<< "(" << e.what() << ")" << std::endl;} } void on_open_wss(server_tls* s, websocketpp::connection_hdl hdl) {server_tls::connection_ptr con = s->get_con_from_hdl(hdl);std::cout << "on_open_wss client addr:" << con->get_remote_endpoint() << std::endl; } void on_close_wss(server_tls* s, websocketpp::connection_hdl hdl) {std::cout << "on_close_wss" << std::endl; }std::string get_password() {return "test"; }// See https://wiki.mozilla.org/Security/Server_Side_TLS for more details about // the TLS modes. The code below demonstrates how to implement both the modern enum tls_mode {MOZILLA_INTERMEDIATE = 1,MOZILLA_MODERN = 2 };context_ptr on_tls_init(tls_mode mode, websocketpp::connection_hdl hdl) {namespace asio = websocketpp::lib::asio;std::cout << "on_tls_init called with hdl: " << hdl.lock().get() << std::endl;std::cout << "using TLS mode: " << (mode == MOZILLA_MODERN ? "Mozilla Modern" : "Mozilla Intermediate") << std::endl;context_ptr ctx = websocketpp::lib::make_shared<asio::ssl::context>(asio::ssl::context::sslv23);try {if (mode == MOZILLA_MODERN) {// Modern disables TLSv1ctx->set_options(asio::ssl::context::default_workarounds |asio::ssl::context::no_sslv2 |asio::ssl::context::no_sslv3 |asio::ssl::context::no_tlsv1 |asio::ssl::context::single_dh_use);} else {ctx->set_options(asio::ssl::context::default_workarounds |asio::ssl::context::no_sslv2 |asio::ssl::context::no_sslv3 |asio::ssl::context::single_dh_use);}// ctx->set_password_callback(bind(&get_password));ctx->use_certificate_chain_file("server.pem");ctx->use_private_key_file("server.pem", asio::ssl::context::pem);// Example method of generating this file:// `openssl dhparam -out dh.pem 2048`// Mozilla Intermediate suggests 1024 as the minimum size to use// Mozilla Modern suggests 2048 as the minimum size to use.// ctx->use_tmp_dh_file("dh.pem");std::string ciphers;if (mode == MOZILLA_MODERN) {ciphers = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK";} else {ciphers = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA";}if (SSL_CTX_set_cipher_list(ctx->native_handle() , ciphers.c_str()) != 1) {std::cout << "Error setting cipher list" << std::endl;}} catch (std::exception& e) {std::cout << "Exception: " << e.what() << std::endl;}return ctx; }int main() {// Create a server endpointserver ws_server;server_tls wss_server;wss_server.set_access_channels(websocketpp::log::alevel::all);wss_server.set_error_channels(websocketpp::log::alevel::all);// Initialize ASIOws_server.init_asio();wss_server.init_asio();// Register our message handlerws_server.set_message_handler(bind(&on_message_ws,&ws_server,::_1,::_2));wss_server.set_message_handler(bind(&on_message_wss,&wss_server,::_1,::_2));wss_server.set_open_handler(bind(&on_open_wss,&wss_server,::_1));wss_server.set_close_handler(bind(&on_close_wss,&wss_server,::_1));wss_server.set_tls_init_handler(bind(&on_tls_init,MOZILLA_INTERMEDIATE,::_1));std::cout << "listen..." << std::endl;// Listen on port 9002ws_server.listen(9002);wss_server.listen(9003);// Start the server accept loopstd::cout << "accept..." << std::endl;ws_server.start_accept();wss_server.start_accept();// Start the ASIO io_service run loopstd::cout << "run..." << std::endl;websocketpp::lib::thread ws_thread(&server::run, &ws_server);websocketpp::lib::thread wss_thread(&server_tls::run, &wss_server);while(1){sleep(1);}std::cout << "run over" << std::endl; }
-
client代码
- C++的客户端去websocketpp库中examples找个合适的
- 以下是js代码
-
websocket.js
var ws_socket; var wss_socket;function ws_connect() {console.log('WebSocket ws connect...');ws_socket = new WebSocket('ws://hslong.com:9002');// 当WebSocket打开时执行ws_socket.onopen = function(event) {// 发送一个初始化消息console.log('WebSocket ws onopen');};// 当接收到消息时执行ws_socket.onmessage = function(event) {// 打印出服务器返回的数据console.log('WebSocket wss Received: ', event.data);};// 当WebSocket关闭时执行ws_socket.onclose = function(event) {console.log('WebSocket ws connection closed');};// 当有错误发生时执行ws_socket.onerror = function(error) {console.error('WebSocket ws error observed:', error);}; } function ws_disconnect() {console.log('WebSocket ws close...');ws_socket.close(); }function ws_send() {console.log('WebSocket ws send...');ws_socket.send('Hello, Server!'); }//wss function wss_connect() {console.log('WebSocket wss connect...');wss_socket = new WebSocket('wss://hslong.com:9003');// 当WebSocket打开时执行wss_socket.onopen = function(event) {// 发送一个初始化消息console.log('WebSocket wss onopen');};// 当接收到消息时执行wss_socket.onmessage = function(event) {// 打印出服务器返回的数据console.log('WebSocket wss Received: ', event.data);};// 当WebSocket关闭时执行wss_socket.onclose = function(event) {console.log('WebSocket wss connection closed');};// 当有错误发生时执行wss_socket.onerror = function(error) {console.error('WebSocket wss error observed:', error);}; } function wss_disconnect() {console.log('WebSocket wss close...');wss_socket.close(); }function wss_send() {console.log('WebSocket wss send...');wss_socket.send('Hello, Server!'); }
-
index.html
<!DOCTYPE html> <html lang="en"> <head><title>Websocket Test</title> <script>function checkLeave(){disconnect();} </script> <script src="websocket.js"></script> </head> <body onbeforeunload="checkLeave()"><p>Websocket Test</p><input id="ws_connect" type="button" value="ws连接" onclick="ws_connect()" /><input id="ws_disconnect " type="button" value="ws断开" onclick="ws_disconnect()" /><input id="ws_send" type="button" value="ws发送" onclick="ws_send()" /><br><br><input id="wss_connect" type="button" value="wss连接" onclick="wss_connect()" /><input id="wss_disconnect " type="button" value="wss断开" onclick="wss_disconnect()" /><input id="wss_send" type="button" value="wss发送" onclick="wss_send()" /></body> </html>
三、证书(wss协议使用,不涉及wss协议忽略)
-
server使用证书
将server.pem按照代码写的放入运行时对应的目录下 -
client使用
将server.crt安装到系统中(win10为例):win+r–>输入:certmgr.msc回车–>受信任的根证书颁发机构–>证书–>右击–>所有任务–>导入server.crt文件 -
证书生成
第1步,生成私钥文件 openssl genpkey -algorithm RSA -out server.key第2步,生成证书请求文件 openssl req -new -key server.key -out server.crs第3步,生成证书文件,其中days表示有效天数 openssl x509 -req -days 36500 -in server.crs -signkey server.key -out server.crt第4步,生成pfx证书,需要设定密码 openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt第5步,生成pem 创建server.pem文件,将server.key内容放到开头,将server.crt放到后面server.crs生成输入:Country Name (2 letter code) [AU]:CHState or Province Name (full name) [Some-State]:HeBeiLocality Name (eg, city) []:Sjz Organization Name (eg, company) [Internet Widgits Pty Ltd]:hslOrganizational Unit Name (eg, section) []:hslCommon Name (e.g. server FQDN or YOUR name) []:hslong.comEmail Address []:hslong@hsl.comPlease enter the following 'extra' attributesto be sent with your certificate requestA challenge password []:hslongAn optional company name []:hslongserver.pfx生成输入密码:hslong
四、运行和配置
-
我的测试运行环境
- server运行在debian11
- client运行在win10
-
本地域名解析
C盘-->Windows-->System32-->drivers-->etc-->hosts管理员记事本打开-->最后写入 192.168.1.108 hslong.com
五、注意事项
- Chrome浏览器和Microsoft Edge浏览器ws方式都正常
- Chrome浏览器wss方式连接一直失败,不知道哪里限制了
- Microsoft Edge浏览器wss方式连接正常,前提条件:(否则不通)
- new WebSocket(‘wss://hslong.com:9003’); 必须使用证书生成时添加的域名
- 已经安装证书