Connecting
TTMSFNCWebsocketClient is a non-visual component that opens a WebSocket
connection to a server and keeps it open for two-way messaging, following the
RFC 6455 protocol. Use it in VCL and
FMX applications whenever the server pushes data to you — chat, live
dashboards, device telemetry, notifications — rather than polling an HTTP
endpoint. Connecting takes three pieces of information that map directly onto
the parts of a ws:// or wss:// URL, one call to open the connection, and a
choice between a clean close and an immediate abort when you are done. This
chapter covers addressing the server, the handshake, the connection lifecycle,
and closing the connection. For TMS WEB Core browser applications, use the
TWebSocketClient component that ships with TMS WEB Core instead.
Addressing the server
The client builds the WebSocket URL from three properties, so it helps to read
a URL as those parts. For ws://localhost:5050/socketpath:
| URL part | Property | Example value |
|---|---|---|
Scheme (ws or wss) |
UseSSL |
False for ws, True for wss |
| Host | HostName |
localhost |
| Port | Port |
5050 (default 8888) |
| Path (optional) | PathName |
socketpath |
PathName is optional; leave it empty when the server accepts connections on
its root. Origin (public, set
from code) adds the optional Origin header that some servers check for
non-browser clients, and BoundIP
selects the local network interface on machines with more than one.
All addressing and connection properties are read when the connection opens, and changing them while the client is connected raises an exception. Set them first, then connect.
Opening the connection
Set Active to True or call
Connect; both do the same work.
Connect is a blocking call: it opens the TCP connection, sends the WebSocket
handshake request, and reads the server's handshake response before it
returns. OnConnect fires
inside that call once the handshake succeeded, so by the time Connect
returns you can send immediately.
procedure TForm1.SetupClient;
begin
FClient := TTMSFNCWebsocketClient.Create(Self);
FClient.HostName := 'localhost';
FClient.Port := 5050;
FClient.OnConnect := ClientConnect;
FClient.OnDisconnect := ClientDisconnect;
FClient.OnMessageReceived := ClientMessageReceived;
// Connect blocks until the TCP connection and the WebSocket handshake are
// done. OnConnect fires before it returns; Active stays False when the
// server rejected the handshake.
FClient.Connect;
if not FClient.Active then
ShowMessage('The server did not accept the WebSocket handshake.');
end;
procedure TForm1.ClientConnect(Sender: TObject; AConnection: TTMSFNCWebSocketConnection);
begin
// Connected - it is now safe to Send.
sendBtn.Enabled := True;
end;
procedure TForm1.ClientDisconnect(Sender: TObject; AConnection: TTMSFNCWebSocketConnection);
begin
sendBtn.Enabled := False;
end;
void __fastcall TForm1::SetupClient()
{
FClient = new TTMSFNCWebsocketClient(this);
FClient->HostName = "localhost";
FClient->Port = 5050;
FClient->OnConnect = ClientConnect;
FClient->OnDisconnect = ClientDisconnect;
FClient->OnMessageReceived = ClientMessageReceived;
// Connect blocks until the TCP connection and the WebSocket handshake are
// done. OnConnect fires before it returns; Active stays False when the
// server rejected the handshake.
FClient->Connect();
if (!FClient->Active)
ShowMessage("The server did not accept the WebSocket handshake.");
}
void __fastcall TForm1::ClientConnect(TObject *Sender, TTMSFNCWebSocketConnection *AConnection)
{
// Connected - it is now safe to Send.
sendBtn->Enabled = true;
}
void __fastcall TForm1::ClientDisconnect(TObject *Sender, TTMSFNCWebSocketConnection *AConnection)
{
sendBtn->Enabled = false;
}
Two outcomes need handling:
- The server cannot be reached. The underlying socket raises an exception
(host not found, connection refused, timeout). Wrap
Connectintry..except. - The server refuses the handshake.
Connectreturns without an exception, but the client disconnects andActivestaysFalse. CheckActiveafter the call.
ConnectTimeout limits how
long the TCP connect may take, in milliseconds; 0 uses the default of the
underlying socket. When Active is set to True at design time, the client
connects as soon as the form finishes loading.
Customizing the handshake
The handshake is an HTTP upgrade request, and two events let you take part in
it. OnSendHandshake
passes the request headers as Name: Value lines just before they are sent —
the place to add an authorization token, a cookie, or a
Sec-WebSocket-Protocol header.
OnHandshakeResponse
fires after the server's response passed the protocol checks, with a
TTMSFNCWebSocketHandshakeResponse
that exposes the returned headers; set AAllow to False to reject a
response your application cannot work with.
Combining addressing, timeouts, and handshake events
A production client usually needs all of the above at once: a secure URL with
a path, a bounded connect time, an authentication header, and a check on what
the server agreed to. The example connects to
wss://chat.example.com:443/socket that way.
procedure TForm1.ConnectToChat;
begin
// wss://chat.example.com:443/socket
// HostName Port PathName
TMSFNCWebsocketClient1.HostName := 'chat.example.com';
TMSFNCWebsocketClient1.Port := 443;
TMSFNCWebsocketClient1.PathName := 'socket';
TMSFNCWebsocketClient1.UseSSL := True; // wss:// instead of ws://
TMSFNCWebsocketClient1.Origin := 'https://chat.example.com';
TMSFNCWebsocketClient1.ConnectTimeout := 5000; // milliseconds
TMSFNCWebsocketClient1.OnSendHandshake := ClientSendHandshake;
TMSFNCWebsocketClient1.OnHandshakeResponse := ClientHandshakeResponse;
try
TMSFNCWebsocketClient1.Active := True; // same as calling Connect
except
on E: Exception do
ShowMessage('Could not reach the server: ' + E.Message);
end;
end;
procedure TForm1.ClientSendHandshake(Sender: TObject; AHeaders: TStrings);
begin
// The request headers are "Name: Value" lines; add your own before they are sent.
AHeaders.Add('Authorization: Bearer ' + FAccessToken);
end;
procedure TForm1.ClientHandshakeResponse(Sender: TObject;
AResponse: TTMSFNCWebSocketHandshakeResponse; var AAllow: Boolean);
begin
// The response already passed the RFC 6455 checks; refuse it when the server
// selected a sub-protocol this application does not speak.
AAllow := (AResponse.Protocol = '') or SameText(AResponse.Protocol, 'chat.v1');
end;
void __fastcall TForm1::ConnectToChat()
{
// wss://chat.example.com:443/socket
// HostName Port PathName
TMSFNCWebsocketClient1->HostName = "chat.example.com";
TMSFNCWebsocketClient1->Port = 443;
TMSFNCWebsocketClient1->PathName = "socket";
TMSFNCWebsocketClient1->UseSSL = true; // wss:// instead of ws://
TMSFNCWebsocketClient1->Origin = "https://chat.example.com";
TMSFNCWebsocketClient1->ConnectTimeout = 5000; // milliseconds
TMSFNCWebsocketClient1->OnSendHandshake = ClientSendHandshake;
TMSFNCWebsocketClient1->OnHandshakeResponse = ClientHandshakeResponse;
try
{
TMSFNCWebsocketClient1->Active = true; // same as calling Connect
}
catch (Exception &E)
{
ShowMessage("Could not reach the server: " + E.Message);
}
}
void __fastcall TForm1::ClientSendHandshake(TObject *Sender, TStrings *AHeaders)
{
// The request headers are "Name: Value" lines; add your own before they are sent.
AHeaders->Add("Authorization: Bearer " + FAccessToken);
}
void __fastcall TForm1::ClientHandshakeResponse(TObject *Sender,
TTMSFNCWebSocketHandshakeResponse *AResponse, bool &AAllow)
{
// The response already passed the RFC 6455 checks; refuse it when the server
// selected a sub-protocol this application does not speak.
AAllow = (AResponse->Protocol == "") || SameText(AResponse->Protocol, "chat.v1");
}
Closing the connection
Choose between a clean close and an abort.
Disconnect with its default
ASendClose = True performs the WebSocket closing handshake: it sends a close
frame and waits for the server's close frame, after which the client releases
the TCP connection and raises
OnDisconnect. Setting
Active := False does the same.
Disconnect(False) aborts the connection: the TCP connection is dropped
immediately and no close frame is sent. Use it when the server is unresponsive,
when the link is known to be dead, or while the application is shutting down.
procedure TForm1.CloseBtnClick(Sender: TObject);
begin
// Sends a close frame; the TCP connection is released when the server
// answers with its own close frame, and OnDisconnect fires.
TMSFNCWebsocketClient1.Disconnect;
end;
procedure TForm1.AbortBtnClick(Sender: TObject);
begin
// Drops the TCP connection immediately, without a close frame.
TMSFNCWebsocketClient1.Disconnect(False);
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// Do not wait for a close reply while the application shuts down.
if TMSFNCWebsocketClient1.Active then
TMSFNCWebsocketClient1.Disconnect(False);
end;
void __fastcall TForm1::CloseBtnClick(TObject *Sender)
{
// Sends a close frame; the TCP connection is released when the server
// answers with its own close frame, and OnDisconnect fires.
TMSFNCWebsocketClient1->Disconnect();
}
void __fastcall TForm1::AbortBtnClick(TObject *Sender)
{
// Drops the TCP connection immediately, without a close frame.
TMSFNCWebsocketClient1->Disconnect(false);
}
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
// Do not wait for a close reply while the application shuts down.
if (TMSFNCWebsocketClient1->Active)
TMSFNCWebsocketClient1->Disconnect(false);
}
When the server closes the connection, the client answers with its own close
frame automatically and disconnects. Add twsoManualClose to
Options to suppress that
automatic reply.
Checking the connection state
Active reports whether the client considers itself connected.
IsConnected asks the
underlying socket directly. Neither can notice a connection that silently
disappeared (a network cable pulled, a server that crashed without closing);
see Connection health for detecting that case.
procedure TForm1.StatusTimerTimer(Sender: TObject);
begin
if not TMSFNCWebsocketClient1.Active then
StatusLabel.Text := 'Not connected'
else if TMSFNCWebsocketClient1.IsConnected then
StatusLabel.Text := 'Connected to ' + TMSFNCWebsocketClient1.HostName
else
// The client still considers itself connected, but the socket is closed.
StatusLabel.Text := 'Connection closed by the network';
end;
void __fastcall TForm1::StatusTimerTimer(TObject *Sender)
{
if (!TMSFNCWebsocketClient1->Active)
StatusLabel->Text = "Not connected";
else if (TMSFNCWebsocketClient1->IsConnected())
StatusLabel->Text = "Connected to " + TMSFNCWebsocketClient1->HostName;
else
// The client still considers itself connected, but the socket is closed.
StatusLabel->Text = "Connection closed by the network";
}
Pitfalls
Connectblocks the calling thread. A slow or unreachable server freezes the user interface for up toConnectTimeout; keep the timeout short or connect from a background thread.- A refused handshake does not raise. Always test
ActiveafterConnect. - Configure before connecting. Setting
HostName,Port,PathName,UseSSL,Options,ConnectTimeout,CheckTimeout,AutoCheckMessages,AutoCheckInterval,OriginorBoundIPwhile connected raises an exception. IsConnectedneeds a connection object. Call it only after the firstConnect.- Match
UseSSLto the server. A plain client cannot talk to awss://server, and a TLS client cannot talk to a plainws://server.
Related API
TTMSFNCWebsocketClientHostName,Port,PathName,UseSSL,Origin,BoundIPActive,Connect,Disconnect,ConnectTimeout,IsConnectedOnConnect,OnDisconnect,OnSendHandshake,OnHandshakeResponseTTMSFNCWebSocketHandshakeResponse
See also
- Sending and receiving — exchange text and binary messages.
- Connection health — ping, pong, and detecting a lost connection.
- Secure connections —
wss://servers and OpenSSL. - Running a server — the server side of the connection.