Connection health
Clients disappear without warning: a laptop lid closes, a phone switches networks, a browser tab is killed. Each vanished client leaves a connection behind that the server keeps serving until the operating system gives up on it, which can take a long time. The WebSocket protocol provides ping and pong control frames to test liveness in both directions, and a close frame for an orderly goodbye. This chapter covers how the server answers client pings, how to take over that reply, how to ping clients yourself and drop the ones that stopped answering, and which events report a client leaving. Use it for any server that runs unattended or holds many long-lived connections.
Answering client pings
When a client sends a ping, the server answers it automatically with a pong
that carries the same payload and then raises
OnPing with that payload in
AData. Nothing is required to keep clients happy.
Replying to pings manually
Add twsoManualPong to
Options while the server is
inactive to take over the reply — for example to throttle clients that ping
too often. OnPing is then where you answer, by sending a pong frame with
SendSimpleFrame
and the ping's payload. Send focPong, not focPing: answering a ping with
another ping never satisfies the client.
procedure TForm1.FormCreate(Sender: TObject);
begin
// Options can only change while the server is not active.
TMSFNCWebSocketServer1.Options := TMSFNCWebSocketServer1.Options + [twsoManualPong];
end;
procedure TForm1.TMSFNCWebSocketServer1Ping(Sender: TObject;
AConnection: TTMSFNCWebSocketConnection; const AData: TBytes);
begin
// Reply with a pong (not a ping) carrying the same payload.
AConnection.SendSimpleFrame(focPong, AData);
end;
Pinging clients
The server component has no Ping method of its own; send a ping frame
through the client's connection with SendSimpleFrame(focPing, ...). The
client answers with a pong, which raises
OnPong. Recording the time of
the last pong in each connection's
UserData tells you
which clients are still alive.
Detecting clients that leave
Three events report a client leaving:
OnClosefires when a client sends a close frame.ADatacarries the close status code and an optional reason, whichTransformCloseDatadecodes. The server replies with its own close frame automatically unlesstwsoManualCloseis inOptions.OnDisconnectfires when the connection is gone, whether it was closed properly or dropped.- A client that vanished without closing is only noticed when the operating system reports the broken connection — or when it stops answering your pings.
Combining pings, connection data, and client removal
The example keeps the time of each client's last pong in its connection data,
pings all clients from a timer, and closes the clients that have not answered
for a minute with
DisconnectClient. The
ping itself is sent from a SendMessageTo selector, which visits every
connected client; the selector returns False, so no message is broadcast.
type
TClientState = class
public
LastPong: TDateTime;
end;
procedure TForm1.TMSFNCWebSocketServer1HandshakeResponseSent(Sender: TObject;
AConnection: TTMSFNCWebSocketServerConnection);
begin
AConnection.OwnsUserData := True;
AConnection.UserData := TClientState.Create;
TClientState(AConnection.UserData).LastPong := Now;
end;
procedure TForm1.HeartbeatTimerTimer(Sender: TObject);
var
Deadline: TDateTime;
begin
Deadline := IncSecond(Now, -60);
// Close clients that have not answered for a minute.
TMSFNCWebSocketServer1.DisconnectClient(
function(AConnection: TTMSFNCWebSocketServerConnection): Boolean
begin
Result := Assigned(AConnection.UserData) and
(TClientState(AConnection.UserData).LastPong < Deadline);
end);
// Ping the others. The selector visits every connected client; it sends
// the ping itself and returns False, so no message is broadcast.
TMSFNCWebSocketServer1.SendMessageTo('',
function(AConnection: TTMSFNCWebSocketServerConnection): Boolean
begin
try
AConnection.SendSimpleFrame(focPing, TEncoding.UTF8.GetBytes('heartbeat'));
except
// The socket is already gone; the server cleans it up.
end;
Result := False;
end);
end;
procedure TForm1.TMSFNCWebSocketServer1Pong(Sender: TObject;
AConnection: TTMSFNCWebSocketConnection; const AData: TBytes);
var
Conn: TTMSFNCWebSocketServerConnection;
begin
Conn := AConnection as TTMSFNCWebSocketServerConnection;
if Assigned(Conn.UserData) then
TClientState(Conn.UserData).LastPong := Now;
end;
Pitfalls
- Answer a ping with a pong.
SendSimpleFrame(focPing)inOnPingsends a new ping instead of an answer; usefocPong. - Echo the payload. Pass the received
ADatato the pong. twsoManualPongmeans you must answer. Clients disconnect servers that stop answering their pings.- Change
Optionswhile inactive. SettingOptionson an active server raises an exception. - Treat
OnDisconnectas final. The connection object is released after it; do not keep references to it.
Related API
OnPing,OnPong,OnClose,OnDisconnectSendSimpleFrame,Options,TransformCloseData,DisconnectClient
See also
- Running a server — the full connection lifecycle.
- Custom connection data — store per-client state.
- Client connection health — the client side of ping and pong.