Photo by Jeremy Foster on Unsplash

In our previous publication entitled “2026: The Tipping Point for Rust in Crypto Trading”, we introduced the official launch of a handcrafted crypto trading connectivity library CCRS which is super simple to use and offers unified API across different exchanges. One of the critical techniques that CCRS employs is the tokio runtime for Rust. Since this machinary is so important and sits in the center of our library’s networking layer, we will write several articles about it and spend some time to deep dive into various aspects of the tokio runtime. In particular we will focus our discussions on latency related issues. And for today’s article, let us talk about tokio runtime’s application in and implication to the WebSocket API. We will also use Bybit’s market data WebSocket API to present concrete examples and gists so that the reader can readily get their hands dirty and learn something by directly “touching it”.

Our goal is to code a WebSocket client from scratch that listens to the top-of-book market data of Bybit’s spot instrument BTCUSDT in the most performant way. Crypto market is notorious for its extreme volatility and this kind of coding “exercise” is highly relevant in the context of high frequency trading: it is exactly when market conditions become highly volatile when low latency is most needed. For any programming language, in general there are two styles to code a WebSocket client: the blocking style (a.k.a synchronous style) and the non-blocking style (a.k.a asynchronous style).

In the blocking programming model, each operation waits until it finishes and the current thread is blocked while reading/writing. The logic is purely linear and therefore easier to understand. It is only good for simple tools, scripts, prototypes, and definitely not suitable for building high frequency trading systems. We are very surprised (in fact a bit astonished) to discover that ccxt maintained Binance API wrapper binance-rs uses the blocking programming model. As a result, each of its WebSocket connections occupies a dedicated thread. This is undesirable because in crypto high frequency trading we establish many WebSocket connections to gain latency edges. When using binance-rs that translates to many threads which mean many context switching (a CPU core changes threads) and many CPU L1/L2 cache misses (a thread changes CPU core).

In the non-blocking programming model, operations do not wait for completion and the current thread is never stalled while reading/writing. Instead, I/O tasks are delegated to an event loop, allowing a small number of threads (or even a single thread) to manage a large number of concurrent connections efficiently. The control flow is more event-driven and asynchronous, which might make the implementation harder to reason about compared to purely linear blocking code. However, this tradeoff is essential for building modern low-latency and high-throughput systems such as crypto high frequency trading infrastructure. With the non-blocking model, thousands of WebSocket connections can be multiplexed over only a handful of threads, dramatically reducing context switching and minimizing CPU L1/L2 cache invalidation caused by thread migration across CPU cores. This leads to better scalability, lower latency jitter, and more predictable performance under heavy network loads. Our library CCRS employs the non-blocking programming model and it is based on a popular asynchronous runtime in Rust called Tokio.

Does it sound challenging to you? Actually it is super easy to get along with the non-blocking programming model. Below is a simple Rust script that demonstrates how to stream top-of-book market data using multiple Bybit WebSocket connections running on a single thread.

#!/usr/bin/env rust-script
//! “`cargo
//! [dependencies]
//! tokio = { version = “1”, features = [“full”] }
//! tokio-tungstenite = { version = “0.24”, features = [“native-tls”] }
//! futures-util = “0.3”
//! serde_json = “1”
//! “`

use futures_util::{SinkExt, StreamExt};
use tokio::task::LocalSet;
use tokio_tungstenite::{connect_async, tungstenite::Message};

async fn subscribe(url: &’static str, id: usize) {
let (ws_stream, _) = connect_async(url).await.expect(“WebSocket handshake failed”);
let (mut write, mut read) = ws_stream.split();

let sub = serde_json::json!({ “op”: “subscribe”, “args”: [“orderbook.1.BTCUSDT”] });
write.send(Message::Text(sub.to_string().into())).await.unwrap();

while let Some(msg) = read.next().await {
match msg {
Ok(Message::Text(text)) => println!(“[conn-{id}] {text}”),
Ok(Message::Close(frame)) => {
eprintln!(“[conn-{id}] closed: {frame:?}”);
break;
}
Err(e) => {
eprintln!(“[conn-{id}] error: {e}”);
break;
}
_ => {}
}
}
}

#[tokio::main(flavor = “current_thread”)]
async fn main() {
let local = LocalSet::new();
let url = “wss://stream.bybit.com/v5/public/spot”;

for id in 1..=5 {
local.spawn_local(subscribe(url, id));
}

local.await;
}

If you have difficulty understanding what is happening inside this script, copy and paste it into ChatGPT and ask it to explain the code to you piece-by-piece. Now we will leave an exercise to the reader of this article: modify the above-shown script so that it establishes 50 WebSocket connections to Binance instead of Bybit, then do the same thing using ccxt maintained Binance API wrapper binance-rs, and compare the performances.

To summarize, in this article we introduced the fundamental idea behind Tokio’s non-blocking runtime and demonstrated how a single thread can efficiently multiplex many WebSocket connections with minimal overhead. While the example shown here is intentionally simple, the underlying concepts form the backbone of modern low-latency trading infrastructure. Our library CCRS is built around exactly these principles: simplicity, performance, and predictable behavior under extreme market conditions. CCRS supports Binance, Bitget, Bybit, Coinbase, Gate, HTX, Hyperliquid, OKX, and many more coming soon. 🎉

The Tokio Runtime For Rust: Part I, WebSocket. was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

By

Leave a Reply

Your email address will not be published. Required fields are marked *