Broadcasting messages
A WebSocket server earns its keep by pushing data: a price tick to every
dashboard, a chat line to everyone in a room, a notification to one user.
TTMSFNCWebSocketServer offers two families of send methods — broadcasts that
reach every connected client, and selector-based sends that reach exactly the
clients a function picks — each in a text and a binary variant. It also
receives the clients' own messages through events and can close selected
clients. This chapter covers broadcasting, targeted sending, relaying messages
between clients, splitting large messages into frames, and removing clients.
Reach for the selector-based methods as soon as not every client should see
every message.
Broadcasting to all clients
BroadcastMessage
sends a text message and
BroadcastData a binary
message to every client whose handshake is complete. Clients that are still
connecting are skipped.
procedure TForm1.BroadcastBtnClick(Sender: TObject);
begin
// Text message to every connected client.
TMSFNCWebSocketServer1.BroadcastMessage('Hello client!');
end;
procedure TForm1.PublishSnapshot(const AImage: TBytes);
begin
// Binary message to every connected client.
TMSFNCWebSocketServer1.BroadcastData(AImage);
end;
void __fastcall TForm1::BroadcastBtnClick(TObject *Sender)
{
// Text message to every connected client.
TMSFNCWebSocketServer1->BroadcastMessage("Hello client!");
}
void __fastcall TForm1::PublishSnapshot(const TBytes AImage)
{
// Binary message to every connected client.
TMSFNCWebSocketServer1->BroadcastData(AImage);
}
Receiving client messages
Handle the event that matches the message type.
OnMessageReceived
fires for text messages and
OnBinaryDataReceived
for binary messages. AConnection identifies the client that sent the
message; on the server it is always a
TTMSFNCWebSocketServerConnection.
As on the client, twsoFrameByFrame in
Options delivers fragmented
messages one frame at a time, with AFrameType marking the first and last
frame.
Relaying messages between clients
SendMessageTo (text) and
SendDataTo (binary) take a
selector of type
TTMSFNCWebSocketSendToCallBack:
an anonymous function that receives each connected client and returns True
for the clients that should get the message. Comparing against the sender
produces the classic chat relay — every client except the one the message came
from.
procedure TForm1.TMSFNCWebSocketServer1MessageReceived(Sender: TObject;
AConnection: TTMSFNCWebSocketConnection; const AMessage: string);
begin
// The selector runs once per connected client; only clients for which it
// returns True receive the message. Here: everyone except the sender.
TMSFNCWebSocketServer1.SendMessageTo(AMessage,
function(AClientConnection: TTMSFNCWebSocketServerConnection): Boolean
begin
Result := AClientConnection <> AConnection;
end);
end;
// The anonymous method of the Delphi API is an interface in C++Builder.
class TExceptSenderSelector : public TCppInterfacedObject<TTMSFNCWebSocketSendToCallBack>
{
public:
TExceptSenderSelector(TTMSFNCWebSocketConnection *ASender) : FSender(ASender) {}
bool __fastcall Invoke(TTMSFNCWebSocketServerConnection *AClientConnection)
{
return static_cast<TTMSFNCWebSocketConnection*>(AClientConnection) != FSender;
}
private:
TTMSFNCWebSocketConnection *FSender;
};
void __fastcall TForm1::TMSFNCWebSocketServer1MessageReceived(TObject *Sender,
TTMSFNCWebSocketConnection *AConnection, const System::UnicodeString AMessage)
{
// The selector runs once per connected client; only clients for which it
// returns True receive the message. Here: everyone except the sender.
TMSFNCWebSocketServer1->SendMessageTo(AMessage,
_di_TTMSFNCWebSocketSendToCallBack(new TExceptSenderSelector(AConnection)));
}
Targeting specific clients
Because the selector sees the whole connection object, it can route on
anything the connection carries: the requested path or query string in
HandshakeRequest, the peer address, or — most usefully — your own data in
UserData. Store a
user name, room, or subscription list on each connection (see
Custom connection data) and the selector becomes a
one-line filter.
Sending in multiple frames
By default each message goes out in one frame. Set
SplitMessage to True to
cut messages into frames of at most
FrameSize bytes, headers
included. The setting applies to the broadcast and the selector-based methods
alike.
Removing clients
DisconnectClient uses
the same kind of selector and sends a close frame to every client it picks,
starting the closing handshake. Use it to kick a user, to close idle clients,
or to empty a room.
Combining targeted sends, frames, and client removal
The example keeps a user name and room on every connection and uses them to send split-frame updates to one room and to disconnect one user.
type
TChatUser = class
public
UserName: string;
Room: string;
end;
procedure TForm1.SendToRoom(const ARoom, AMessage: string);
begin
// Large room updates go out in frames of at most 4 KB.
TMSFNCWebSocketServer1.SplitMessage := True;
TMSFNCWebSocketServer1.FrameSize := 4096;
TMSFNCWebSocketServer1.SendMessageTo(AMessage,
function(AConnection: TTMSFNCWebSocketServerConnection): Boolean
begin
Result := Assigned(AConnection.UserData) and
(TChatUser(AConnection.UserData).Room = ARoom);
end);
end;
procedure TForm1.KickUser(const AUserName: string);
begin
// Sends a close frame to every client the selector picks.
TMSFNCWebSocketServer1.DisconnectClient(
function(AConnection: TTMSFNCWebSocketServerConnection): Boolean
begin
Result := Assigned(AConnection.UserData) and
SameText(TChatUser(AConnection.UserData).UserName, AUserName);
end);
end;
class TChatUser : public TObject
{
public:
System::UnicodeString UserName;
System::UnicodeString Room;
};
// The anonymous methods of the Delphi API are interfaces in C++Builder.
class TRoomSelector : public TCppInterfacedObject<TTMSFNCWebSocketSendToCallBack>
{
public:
TRoomSelector(const System::UnicodeString ARoom) : FRoom(ARoom) {}
bool __fastcall Invoke(TTMSFNCWebSocketServerConnection *AConnection)
{
return (AConnection->UserData != nullptr) &&
(static_cast<TChatUser*>(AConnection->UserData)->Room == FRoom);
}
private:
System::UnicodeString FRoom;
};
class TUserNameSelector : public TCppInterfacedObject<TTMSFNCWebSocketSendToCallBack>
{
public:
TUserNameSelector(const System::UnicodeString AUserName) : FUserName(AUserName) {}
bool __fastcall Invoke(TTMSFNCWebSocketServerConnection *AConnection)
{
return (AConnection->UserData != nullptr) &&
SameText(static_cast<TChatUser*>(AConnection->UserData)->UserName, FUserName);
}
private:
System::UnicodeString FUserName;
};
void __fastcall TForm1::SendToRoom(const System::UnicodeString ARoom, const System::UnicodeString AMessage)
{
// Large room updates go out in frames of at most 4 KB.
TMSFNCWebSocketServer1->SplitMessage = true;
TMSFNCWebSocketServer1->FrameSize = 4096;
TMSFNCWebSocketServer1->SendMessageTo(AMessage,
_di_TTMSFNCWebSocketSendToCallBack(new TRoomSelector(ARoom)));
}
void __fastcall TForm1::KickUser(const System::UnicodeString AUserName)
{
// Sends a close frame to every client the selector picks.
TMSFNCWebSocketServer1->DisconnectClient(
_di_TTMSFNCWebSocketSendToCallBack(new TUserNameSelector(AUserName)));
}
Pitfalls
- The selector runs for every client on every send. Keep it cheap; look up data you stored on the connection rather than doing I/O.
- Sends skip clients that are still connecting. A client is only reachable
after
OnHandshakeResponseSent. - Do not store connection references. A connection object is freed after its client disconnects; route with selectors over the live connections instead.
- Set
FrameSizewhen splitting. Its default is0; give it a real size wheneverSplitMessageisTrue. - Use the binary methods for binary payloads. Encoding files as text wastes
bandwidth;
BroadcastDataandSendDataTosend them as they are.
Related API
BroadcastMessage,BroadcastData,SendMessageTo,SendDataTo,DisconnectClientTTMSFNCWebSocketSendToCallBack,SplitMessage,FrameSizeOnMessageReceived,OnBinaryDataReceived,TTMSFNCWebSocketServerConnection
See also
- Running a server — start the server and wire its events.
- Custom connection data — the data selectors route on.
- Sending and receiving — the client side of messaging.