How I Stress‑Tested a Socket.IO Service with Fixed QPS and Uncovered Hidden Bugs
After mastering the Socket protocol and a fixed‑QPS load‑testing model, I built a comprehensive Socket.IO performance test that simulates a teacher‑student scenario, measures message latency, identifies calculation errors and logging overhead, and reveals several bugs and optimization opportunities in the asynchronous handling and compensation threads.
Test Objective
The goal is to evaluate the performance of a Socket.IO service using a fixed‑QPS load‑testing model. Two logical clients – a teacher and a student – connect to the same room. The teacher repeatedly sends a data‑fetch command while the student measures round‑trip latency. Latency is the primary metric.
Latency Monitoring Thread
A dedicated ListenThread runs in the background on the student client. It sends a dummy message to keep the connection alive, then repeatedly:
Generate a random 20‑character payload.
Record the start timestamp.
Send the payload via listen.send(OkayScoketConstant.EVENT, …).
Poll the inbound message list for the echoed payload (up to 10 attempts).
Record the end timestamp and compute the round‑trip time.
Append the latency to delays and log the value.
Sleep 3 seconds before the next iteration.
The thread stops when key is set to false and writes the collected latencies to a file.
static class ListenThread extends SourceCode implements Runnable {
public boolean key = true;
public ScoketIOFunClient listen;
public List<Long> delays = [];
ListenThread(ScoketIOFunClient listen) { this.listen = listen }
@Override
void run() {
// keep‑alive message
listen.send(OkayScoketConstant.EVENT, getChat(roomId+1, sbase.uname as long, tbase.uid, kid, ktype, DEFAULT_STRING, true))
sleep(3.0)
while (key) {
def msg = StringUtil.getString(20)
def start = Time.getTimeStamp()
listen.send(OkayScoketConstant.EVENT, getChat(roomId+1, sbase.uname as long, tbase.uid, kid, ktype, msg, true))
// wait for echo
for (int i = 0; i < 10; i++) {
def last = listen.msgs.getLast()
if (last.contains(msg)) break
}
def end = Time.getTimeStamp()
def l = end - start
delays << l
logger.info("响应延迟时间:{}ms", l)
sleep(3.0)
}
}
public void stop() { key = false; Save.saveLongList(delays, "delays") }
}Fixed‑QPS Sender
The load generator uses a FixedQpsThread<ScoketIOFunClient> implementation named SendMsg. A single global Socket instance is shared among all threads, so requests are fully asynchronous and do not apply back‑pressure.
Printing of incoming Socket.IO responses is disabled to avoid log‑induced slowdown.
static class SendMsg extends FixedQpsThread<ScoketIOFunClient> {
SendMsg(ScoketIOFunClient client) {
super(client, getNum(), getQps(), null, true)
}
@Override
protected void doing() throws Exception {
t.send(OkayScoketConstant.EVENT, getCorrect(sbase.getUid(), 1, 24, 4))
}
@Override
FixedQpsThread clone() { new SendMsg(this.t) }
}Test Script
The entry point creates teacher and student sockets, joins a common room, starts the ListenThread on the student, and launches a FixedQpsConcurrent instance that repeatedly executes SendMsg. After the configured duration, the monitoring thread is stopped, the room is left, and all sockets are closed.
class ST1 extends SocketBase {
static int roomId = 44465
static int kid = 540
static int ktype = 1
static IBase tbase
static IBase sbase
static int qps = 200
static int num = 4000
public static void main(String[] args) {
tbase = getTeaBase()
sbase = getStuBase()
ScoketIOFunClient teacher = getSocket(tbase)
ScoketIOFunClient student = getSocket(sbase)
initAll()
registerAll()
joinRoom(roomId)
teacher.send(OkayScoketConstant.EVENT, getCorrect(sbase.getUid(), 1, 24, 4))
def monitor = new ListenThread(student)
def monitorThread = new Thread(monitor)
monitorThread.start()
new FixedQpsConcurrent(new SendMsg(teacher), "Test latency under varying load").start()
monitor.stop()
monitorThread.join()
leaveRoom(roomId)
ScoketIOFunClient.closeAll()
}
}Observed Results
Typical log output shows successful logins, socket connections, and latency values mostly between 1 ms and 2 ms. The test aimed for 4 000 executions (200 QPS × 20 s) but the compensation thread produced 4 133 executions, completing in 23.7 s with zero errors.
Identified Issues
QPS mis‑calculation: The compensation thread runs faster than intended, causing the actual execution count to exceed the target.
Progress‑bar artifact: After the main task finishes, the execution counter is reset to zero before the progress‑bar thread terminates, resulting in an extra “0%” log line.
Compensation timing: The current loop‑compensation logic does not reliably stop at the configured number of iterations, leading to overshoot.
Conclusion
The fixed‑QPS framework successfully exposed hidden bugs in the Socket.IO client implementation and demonstrated that asynchronous socket handling can sustain high request rates with sub‑millisecond latency. Remaining work includes tightening QPS accounting, reducing logging overhead, and refining the compensation mechanism to guarantee exact execution counts.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
