Migrating from socket.io-client-cpp#
This guide is for applications moving from the official
socket.io-client-cpp
library to sioxx. The client/socket split and most method names are
deliberately familiar. The main source change is replacing the
sio::message class hierarchy with nlohmann::json values.
Before changing code, confirm that the server speaks Engine.IO 4 (normally a
Socket.IO 3.x or 4.x server). sioxx does not provide the allowEIO3
compatibility mode needed by older servers.
Replace the dependency#
sioxx requires C++17. After installing it, replace the old include and
CMake target:
# Before
target_link_libraries(my_app PRIVATE sioclient)
# After
find_package(sioxx CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE sioxx::sioxx)
Use the aggregate public header in application code:
// Before
#include <sio_client.h>
// After
#include <sioxx/sioxx.hpp>
The exported target supplies the required include and link settings. For a static sioxx installation, its package configuration also locates the OpenSSL and Threads dependencies.
Port a small client#
The following socket.io-client-cpp client sends two arguments and reads
an acknowledgement:
sio::client client;
client.set_reconnect_attempts(5);
client.set_reconnect_delay(1000);
auto socket = client.socket("/chat");
socket->on("message", [](sio::event& event) {
const auto& args = event.get_messages();
std::cout << args.at(0)->get_string() << '\n';
});
client.set_socket_open_listener([socket](const std::string& nsp) {
if (nsp != "/chat") return;
sio::message::list args;
args.push("Ada");
args.push(sio::int_message::create(42));
socket->emit("hello", args, [](const sio::message::list& reply) {
std::cout << reply.at(0)->get_string() << '\n';
});
});
client.connect("https://example.com");
The equivalent sioxx code is:
#include <chrono>
#include <iostream>
#include <memory>
#include <sioxx/sioxx.hpp>
using namespace std::chrono_literals;
sioxx::client_options options;
options.reconnect_attempts = 5;
options.reconnect_delay = 1s;
sioxx::client client(options);
auto socket = client.socket("/chat");
socket->on("message", [](const std::string&, sioxx::message args) {
std::cout << args.at(0).get<std::string>() << '\n';
});
std::weak_ptr<sioxx::socket> weak_socket = socket;
socket->on_connect([weak_socket] {
if (auto socket = weak_socket.lock()) {
socket->emit(
"hello", sioxx::json::array({"Ada", 42}),
[](sioxx::message reply) {
std::cout << reply.at(0).get<std::string>() << '\n';
});
}
});
client.connect("https://example.com");
Create sockets and register their listeners before client.connect(). This
ensures that namespace lifecycle and early server events cannot arrive before
their handlers are installed.
Translate message values#
sioxx::message and sioxx::json are both aliases for
nlohmann::json. Values are owned directly, so there is no message::ptr
and no factory hierarchy.
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
iterate over |
|
use |
|
|
Object construction becomes much shorter:
// Before
auto user = sio::object_message::create();
user->get_map()["name"] = sio::string_message::create("Ada");
user->get_map()["active"] = sio::bool_message::create(true);
// After
sioxx::json user = {{"name", "Ada"}, {"active", true}};
Listener payloads and acknowledgement payloads are always JSON arrays holding all Socket.IO arguments. Preserve the array when an event has multiple arguments:
socket->on("position", [](const std::string&, sioxx::message args) {
if (args.size() < 2) return;
const double x = args.at(0).get<double>();
const double y = args.at(1).get<double>();
// Use x and y.
});
For emission, passing an array expands its elements into separate Socket.IO
arguments. Passing a scalar or object sends one argument. For example,
emit("position", sioxx::json::array({10, 20})) sends two arguments, while
emit("position", sioxx::json{{"x", 10}, {"y", 20}}) sends one object.
Create binary values with sioxx::binary_message(...) and place them at any
depth in an event or acknowledgement payload. Both the default JSON parser and
the optional MessagePack parser support binary values; the selected parser
must still match the server.
Port acknowledgements#
An outgoing acknowledgement callback changes only from
sio::message::list to sioxx::message:
socket->emit(
"save", sioxx::json{{"id", 42}},
[](sioxx::message reply) {
if (!reply.empty() && reply.at(0) == "saved") {
// The server acknowledged the event.
}
});
For an event that the client must acknowledge, use the acknowledgement-aware
on overload. It replaces event.need_ack() and
event.put_ack_message(...):
socket->on(
"question",
[](const std::string&, sioxx::message args,
sioxx::socket::ack_callback acknowledge) {
if (acknowledge) {
acknowledge(sioxx::json::array({"answer", 42}));
}
});
acknowledge is empty when the server did not request a response. A valid
callback sends at most one response, even if application code invokes it more
than once.
Move connection settings into client_options#
Most settings that were passed to connect() or changed through setters are
fixed when a sioxx::client is constructed:
|
|
|---|---|
|
|
|
|
|
|
|
set |
|
set |
root authentication passed to |
|
namespace authentication passed when obtaining a socket |
|
|
set |
For example:
sioxx::client_options options;
options.query = {{"device", "desktop"}};
options.extra_headers = {{"Authorization", "Bearer example-token"}};
options.engineio_path = "/realtime/";
sioxx::client client(options);
auto private_socket = client.socket(
"/private", sioxx::json{{"token", "example-token"}});
client.connect("wss://example.com");
Do not embed an application path or query in the URI passed to connect().
client_options::engineio_path and client_options::query replace them;
the path defaults to /socket.io/. The reserved EIO, transport, and
sid query keys are managed by the library.
Update lifecycle handling#
Client-level listener names are similar, but close and error details differ:
client.set_open_listener([] {
// Engine.IO and the root namespace are connected.
});
client.set_close_listener([](const std::string& reason) {
std::cerr << "closed: " << reason << '\n';
});
client.set_fail_listener([] {
// The configured reconnect attempts were exhausted.
});
client.set_reconnect_listener([](unsigned attempt, unsigned delay_ms) {
// A retry was scheduled. attempt is zero-based.
});
client.set_reconnecting_listener([] {
// The back-off elapsed and the retry is starting.
});
client.set_error_listener([](const std::string& error) {
std::cerr << error << '\n';
});
Use per-socket callbacks instead of the old client-wide socket open/close listeners:
socket->on_connect([] { /* /chat connected */ });
socket->on_disconnect([](const std::string& reason) {
// /chat disconnected.
});
The remaining namespace method translations are direct:
|
|
|---|---|
|
|
|
|
|
|
reconnect a namespace |
|
|
unchanged |
|
unchanged |
|
unchanged |
|
unchanged |
client.close() requests shutdown without waiting, whereas
client.sync_close() waits for all library workers to stop. Call only the
non-blocking form from a library callback. Callbacks run on sioxx’s connection
background thread; continue dispatching to the UI or application executor
before accessing thread-affine state.
Applications that passed an asio::io_context to socket.io-client-cpp can
retain the same ownership model:
boost::asio::io_context io_context;
sioxx::client client(io_context, options);
The supplied context must outlive the client. The application runs it; sioxx does not run or stop it. WebSocket and HTTP polling networking and callbacks use that context.
The connection-state and session accessors translate directly:
if (client.opened()) {
std::cout << "Engine.IO session: " << client.get_sessionid() << '\n';
}
get_sessionid() returns a value rather than a reference so callers can
safely retain the result while a connection transition replaces the session.
Check unsupported or different features#
Do not treat the migration as a namespace-only rename if the old application uses one of these APIs:
Built-in log-level setters have no direct equivalents. Use the lifecycle and error listeners with the application’s logging.
sioxxcan use HTTP long-polling and automatically falls back to it when the initial WebSocket connection fails. Setoptions.force_http_polling = truewhen polling must be used from the start.
Migration checklist#
Change the project to C++17 and link
sioxx::sioxx.Replace
sio::types and message factories withsioxx::jsonvalues.Keep every event’s arguments in a JSON array when receiving or sending multiple arguments.
Move query parameters, headers, authentication, reconnection, and path settings into
client_optionsor the relevant namespace socket.Replace incoming acknowledgement mutation with the acknowledgement-aware listener overload.
Audit the unsupported APIs above.
Run the application against the same Socket.IO server and verify connect, namespace authentication, representative events, acknowledgements, reconnection, and clean shutdown.
See Examples for more sioxx patterns and sioxx API Reference for the complete public API.