Skip to main content
Table of Contents

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.

Connect to a WebSocket serverPascalC++
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 Connect in try..except.
  • The server refuses the handshake. Connect returns without an exception, but the client disconnects and Active stays False. Check Active after 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.

Address a server path and customize the handshakePascalC++
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.

Close or abort a connectionPascalC++
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.

Show the connection statePascalC++
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

  • Connect blocks the calling thread. A slow or unreachable server freezes the user interface for up to ConnectTimeout; keep the timeout short or connect from a background thread.
  • A refused handshake does not raise. Always test Active after Connect.
  • Configure before connecting. Setting HostName, Port, PathName, UseSSL, Options, ConnectTimeout, CheckTimeout, AutoCheckMessages, AutoCheckInterval, Origin or BoundIP while connected raises an exception.
  • IsConnected needs a connection object. Call it only after the first Connect.
  • Match UseSSL to the server. A plain client cannot talk to a wss:// server, and a TLS client cannot talk to a plain ws:// server.

See also