使用WebSocket++连接Render远程服务器失败,本地及Qt客户端正常
WebSocket++客户端无法连接Render.com上的WebSocket服务器问题排查
环境
- Windows 10 + MinGW 64-bit
- WebSocket++ 0.8.2
- ASIO 1.24.0
- 服务器部署在Render.com,地址:
wss://connection-js.onrender.com
遇到的问题
- 使用
wss://协议时,报错:could not create connection because: endpoint not secure - 使用
ws://协议时,报错:
[2022-10-07 00:33:09] [connect] Successful connection [2022-10-07 00:33:09] [error] Server handshake response error: websocketpp.processor:20 (Invalid HTTP status.) [2022-10-07 00:33:09] [fail] WebSocket Connection 216.24.57.3:80 - "WebSocket++/0.8.2" / 301 websocketpp.processor:20 Invalid HTTP status.
- 本地连接
ws://localhost:3000正常;Qt6客户端使用wss://可正常连接该远程服务器,但ws://不行。
相关代码与配置
初始客户端main.cpp
#define ASIO_STANDALONE #define _WEBSOCKETPP_CPP11_THREAD_ #define _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ #define _WEBSOCKETPP_CPP11_STRICT_ #define _WEBSOCKETPP_CPP11_TYPE_TRAITS_ #include <websocketpp/config/asio_no_tls_client.hpp> #include <websocketpp/client.hpp> typedef websocketpp::client<websocketpp::config::asio_client> client; using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; using websocketpp::lib::bind; // pull out the type of messages sent by our config typedef websocketpp::config::asio_client::message_type::ptr message_ptr; // This message handler will be invoked once for each incoming message. It // prints the message and then sends a copy of the message back to the server. void on_message(client* c, websocketpp::connection_hdl hdl, message_ptr msg) { std::cout << "on_message called with hdl: " << hdl.lock().get() << " and message: " << msg->get_payload() << std::endl; websocketpp::lib::error_code ec; c->send(hdl, msg->get_payload(), msg->get_opcode(), ec); if (ec) { std::cout << "Echo failed because: " << ec.message() << std::endl; } } int main() { // Create a client endpoint client c; // std::string uri = "ws://localhost:3000"; std::string uri = "wss://connection-js.onrender.com"; try { // Set logging to be pretty verbose (everything except message payloads) c.set_access_channels(websocketpp::log::alevel::all); c.clear_access_channels(websocketpp::log::alevel::frame_payload); // Initialize ASIO c.init_asio(); // Register our message handler c.set_message_handler(bind(&on_message,&c,::_1,::_2)); websocketpp::lib::error_code ec; client::connection_ptr con = c.get_connection(uri, ec); if (ec) { std::cout << "could not create connection because: " << ec.message() << std::endl; return 0; } // Note that connect here only requests a connection. No network messages are // exchanged until the event loop starts running in the next line. c.connect(con); // Start the ASIO io_service run loop // this will cause a single connection to be made to the server. c.run() // will exit when this connection is closed. c.run(); } catch (websocketpp::exception const & e) { std::cout << e.what() << std::endl; } return 0; }
初始makefile
CC = g++ INC = -I"E:\Libs\websocketpp-0.8.2-headers" \ -I"E:\Libs\asio-1.24.0\include" all: main.o $(CC) main.o -lws2_32 -o app main.o: main.cpp $(CC) -c $(INC) main.cpp -o main.o
服务端app.js
const express = require("express"); const http = require("http"); const ws = require("ws"); const path = require("path"); const app = express(); app.use(express.static(path.join(process.cwd(), "public"))); const httpServer = http.createServer(app); const wss = new ws.Server( { server: httpServer }); const port = process.env.PORT || 3000; httpServer.listen(port, () => console.log("Listening at port: " + port)); wss.on("connection", socket => { console.log("client was connected"); });
可正常连接的Qt6客户端main.cpp
#include <QtCore/QDebug> #include <QtWidgets/QApplication> #include <QtWidgets/QWidget> #include <QtWebSockets/QWebSocket> class Window : public QWidget { Q_OBJECT private: QWebSocket m_webSocket; public: Window(QWidget *parent = nullptr) : QWidget(parent) { setWindowTitle("Qt6 C++ Client"); resize(300, 300); connect(&m_webSocket, &QWebSocket::connected, this, &Window::onConnected); // m_webSocket.open(QUrl("ws://localhost:3000")); m_webSocket.open(QUrl("wss://connection-js.onrender.com")); qDebug() << "Waiting for connection..."; } private slots: void onConnected() { qDebug() << "Client was connected to server"; } }; #include "main.moc" #ifdef _WIN32 #include <Windows.h> #endif int main(int argc, char *argv[]) { #ifdef _WIN32 if (AttachConsole(ATTACH_PARENT_PROCESS)) { freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); } #endif QApplication a(argc, argv); Window w; w.show(); return a.exec(); }
Qt6客户端.pro文件
# Build commands for CMD: # qmake -makefile # mingw32-make # Command to run: "./release/app" QT += core gui widgets websockets CONFIG += c++11 CONFIG += console SOURCES += \ main.cpp TARGET = app
尝试添加OpenSSL后的配置与代码
修改后的makefile
# Building Command: mingw32-make # Command to run: app CC = g++ INC = -I"E:\Libs\websocketpp-0.8.2-headers" \ -I"E:\Libs\asio-1.24.0\include" \ -I"E:\Libs\openssl-master\include" LIB = -L"E:\Libs\openssl-master\lib\mingw\x64" all: main.o $(CC) main.o $(LIB) -lssl -lcrypto -lws2_32 -o app main.o: main.cpp $(CC) -c $(INC) main.cpp -o main.o
修改后的main.cpp
#define ASIO_STANDALONE #define _WEBSOCKETPP_CPP11_THREAD_ #define _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ #define _WEBSOCKETPP_CPP11_STRICT_ #define _WEBSOCKETPP_CPP11_TYPE_TRAITS_ #include <websocketpp/client.hpp> #include <websocketpp/config/asio_client.hpp> // typedef websocketpp::client<websocketpp::config::asio_client> client; typedef websocketpp::client<websocketpp::config::asio_tls_client> client; using websocketpp::lib::bind; using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; // pull out the type of messages sent by our config typedef websocketpp::config::asio_client::message_type::ptr message_ptr; typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context> context_ptr; context_ptr on_tls_init(const char *hostname, websocketpp::connection_hdl) { // context_ptr ctx = websocketpp::lib::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23); // return ctx; } // This message handler will be invoked once for each incoming message. It // prints the message and then sends a copy of the message back to the server. void on_message(client *c, websocketpp::connection_hdl hdl, message_ptr msg) { std::cout << "on_message called with hdl: " << hdl.lock().get() << " and message: " << msg->get_payload() << std::endl; websocketpp::lib::error_code ec; c->send(hdl, msg->get_payload(), msg->get_opcode(), ec); if (ec) { std::cout << "Echo failed because: " << ec.message() << std::endl; } } int main() { // Create a client endpoint client c; std::string hostname = "connection-js.onrender.com"; std::string uri = "ws://" + hostname; try { // Set logging to be pretty verbose (everything except message payloads) c.set_access_channels(websocketpp::log::alevel::all); c.clear_access_channels(websocketpp::log::alevel::frame_payload); // Initialize ASIO c.init_asio(); // Register our message handler c.set_message_handler(bind(&on_message, &c, ::_1, ::_2)); c.set_tls_init_handler(bind(&on_tls_init, hostname.c_str(), ::_1)); websocketpp::lib::error_code ec; client::connection_ptr con = c.get_connection(uri, ec); if (ec) { std::cout << "could not create connection because: " << ec.message() << std::endl; return 0; } // Note that connect here only requests a connection. No network messages are // exchanged until the event loop starts running in the next line. c.connect(con); // Start the ASIO io_service run loop // this will cause a single connection to be made to the server. c.run() // will exit when this connection is closed. c.run(); } catch (websocketpp::exception const &e) { std::cout << e.what() << std::endl; } return 0; }
问题原因与解决方案
问题1:wss://协议报错endpoint not secure
初始客户端使用的asio_no_tls_client配置不支持TLS加密,无法处理wss://类型的请求,必须切换到支持TLS的asio_tls_client配置,同时完整实现TLS上下文初始化逻辑。
问题2:ws://协议返回301错误
Render.com平台默认会将所有HTTP请求(包括ws://)重定向到HTTPS(wss://),而WebSocket++默认不处理HTTP重定向,导致握手时收到301响应后直接失败。
最终修复步骤
使用正确的TLS客户端配置
- 将客户端类型替换为
websocketpp::client<websocketpp::config::asio_tls_client> - 实现完整的TLS初始化函数,不能留空:
context_ptr on_tls_init(const char *hostname, websocketpp::connection_hdl) { context_ptr ctx = websocketpp::lib::make_shared<websocketpp::lib::asio::ssl::context>(websocketpp::lib::asio::ssl::context::tlsv12_client); try { // 禁用不安全的SSL/TLS版本,只保留安全的TLS 1.2+ ctx->set_options(websocketpp::lib::asio::ssl::context::default_workarounds | websocketpp::lib::asio::ssl::context::no_sslv2 | websocketpp::lib::asio::ssl::context::no_sslv3 | websocketpp::lib::asio::ssl::context::no_tlsv1 | websocketpp::lib::asio::ssl::context::no_tlsv1_1); // 设置SNI(服务器名称指示),适配现代HTTPS服务器 SSL_CTX_set_tlsext_host_name(ctx->native_handle(), hostname); } catch (std::exception& e) { std::cout << "TLS init error: " << e.what() << std::endl; } return ctx; }
- 将客户端类型替换为
仅使用wss://协议连接
由于Render.com强制HTTP重定向到HTTPS,客户端必须直接使用wss://connection-js.onrender.com,不要尝试ws://协议。修正代码中的URI与配置
- 在main函数中设置正确的wss URI:
std::string uri = "wss://connection-js.onrender.com"; - 确保注册TLS初始化处理器:
c.set_tls_init_handler(bind(&on_tls_init, hostname.c_str(), ::_1));
- 在main函数中设置正确的wss URI:
编译时链接正确的库
确保makefile中包含OpenSSL的头文件和库路径,链接-lssl -lcrypto -lws2_32,同时运行时要将OpenSSL的DLL文件(如libssl-1_1-x64.dll、libcrypto-1_1-x64.dll)放在可执行文件目录或系统PATH中。
修复后的完整客户端main.cpp示例
#define ASIO_STANDALONE #define _WEBSOCKETPP_CPP11_THREAD_ #define _WEBSOCKETPP_CPP11_RANDOM_DEVICE_ #define _WEBSOCKETPP_CPP11_STRICT_ #define _WEBSOCKETPP_CPP11_TYPE_TRAITS_ #include <websocketpp/client.hpp> #include <websocketpp/config/asio_tls_client.hpp> #include <asio/ssl.hpp> typedef websocketpp::client<websocketpp::config::asio_tls_client> client; using websocketpp::lib::bind; using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; typedef websocketpp::config::asio_tls_client::message_type::ptr message_ptr; typedef websocketpp::lib::shared_ptr<websocketpp::lib::asio::ssl::context> context_ptr; context_ptr on_tls_init(const char *hostname, websocketpp::connection_hdl) { context_ptr ctx = websocketpp::lib::make_shared<websocketpp::lib::asio::ssl::context>(websocketpp::lib::asio::ssl::context::tlsv12_client); try { ctx->set_options(websocketpp::lib::asio::ssl::context::default_workarounds | websocketpp::lib::asio::ssl::context::no_sslv2 | websocketpp::lib::asio::ssl::context::no_sslv3 | websocketpp::lib::asio::ssl::context::no_tlsv1 | websocketpp::lib::asio::ssl::context::no_tlsv1_1); SSL_CTX_set_tlsext_host_name(ctx->native_handle(), hostname); } catch (std::exception& e) { std::cout << "TLS init error: " << e.what() << std::endl; } return ctx; } void on_message(client *c, websocketpp::connection_hdl hdl, message_ptr msg) { std::cout << "Received message: " << msg->get_payload() << std::endl; websocketpp::lib::error_code ec; c->send(hdl, msg->get_payload(), msg->get_opcode(), ec); if (ec) { std::cout << "Send failed: " << ec.message() << std::endl; } } void on_open(client* c, websocketpp::connection_hdl hdl) { std::cout << "Connected to server!" << std::endl; c->send(hdl, "Hello from WebSocket++ client!", websocketpp::frame::opcode::text); } int main() { client c; std::string hostname = "connection-js.onrender.com"; std::string uri = "wss://"
相关产品推荐
相关产品推荐

