WebSocket Client Example

Here we demonstrate how to create a WebSocket client using Libwebsockets in Julia. The client connects to data stream and receives updates for a specified trading pair. A callback function processes each incoming message, making it easy to handle data as it arrives. The client is configured with SSL support for secure communication, and you can customize the handler function to suit your data-processing needs.

  • Firstly we need to define User Data Structure:

UserData is a struct holding a function callback, which will process the incoming WebSocket messages.

using Libwebsockets

mutable struct UserData
    callback::Function
end
  • Then we define WebSocket Callback Function:

ws_callback is a function that gets triggered on various WebSocket events and calls the user-defined function callback on the message data.

function ws_callback(wsi::Ptr{Cvoid}, reason::Cint, user::Ptr{Cvoid}, data::Ptr{Cvoid}, len::Csize_t)
    if reason == LWS_CALLBACK_CLIENT_RECEIVE && data != C_NULL
        ctx = lws_get_context(wsi)
        user_ctx = unsafe_pointer_to_objref(lws_context_user(ctx))
        user_ctx.callback(unsafe_wrap(Vector{UInt8}, Ptr{UInt8}(data), len))
    end
    return lws_callback_http_dummy(wsi, reason, user, data, len)
end

Now we can define main ws_open function to open and initialize WebSocket connection. It is possible to set the log level using the lws_set_log_level function; in this example, logging is disabled. The ws_open function consists of three main steps:

  • Setup WebSocket Protocol and Callback:

It defines a WebSocket callback function ws_callback that handles incoming WebSocket events. A protocol structure is created to specify which function should be used for WebSocket events. User data is also set up, containing the callback function the user wants to use to process incoming messages.

  • Configure and Create WebSocket Context:

LwsContextCreationInfo is initialized with options for SSL and non-listening mode (indicating this is a client). This configuration is passed to lws_create_context, which creates the WebSocket context needed to manage the connection.

  • Establish the WebSocket Connection and Event Loop:

LwsClientConnectInfo is set up with server details like the address, port, path, user, and SSL options, and the connection is initiated using lws_client_connect_via_info. The function then enters an infinite loop, continuously calling lws_service to handle WebSocket events (such as receiving messages) and process them via the user-defined callback.

function ws_open(callback::Function, addr::String, port::Int, path::String)
    lws_set_log_level(0, C_NULL)
    
    callback_ptr = @cfunction(ws_callback, Cint, (Ptr{Cvoid}, Cint, Ptr{Cvoid}, Ptr{Cvoid}, Csize_t))
    protocols = [
        LwsProtocols(pointer("ws"), callback_ptr, 0, 0, 0, C_NULL, 0),
        LwsProtocols(C_NULL, C_NULL, 0, 0, 0, C_NULL, 0)
    ]
    user = UserData(callback)

    ctx_info = LwsContextCreationInfo()
    ctx_info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT
    ctx_info.port = CONTEXT_PORT_NO_LISTEN
    ctx_info.user = Base.unsafe_convert(Ptr{UserData}, Ref(user))
    ctx_info.protocols = pointer(protocols)
    ws_ctx = lws_create_context(Ref(ctx_info))

    conn_info = LwsClientConnectInfo()
    conn_info.context = ws_ctx
    conn_info.port = port
    conn_info.address = pointer(addr)
    conn_info.path = pointer(path)
    conn_info.host = conn_info.address
    conn_info.ssl_connection = LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED
    lws_client_connect_via_info(Ref(conn_info))

    while true
        lws_service(ws_ctx, 0)
    end
end
  • User-defined Callback:

Finally ws_open is called with a user-defined callback function that simply prints any received messages.

ws_open("stream.binance.com", 9443, "/stream?streams=adausdt@depth5@100ms") do message
    println("Received message: ", String(message))
end