Broadcasting
The ability to transmit real-time data from servers to clients is a requirement for many modern web and mobile applications. When some data is updated on the server, a message is typically sent over a WebSocket connection for processing by the client. WebSockets provide a more efficient alternative to constantly polling your application server for data changes that need to be reflected in your user interface.
The basic concept of broadcasting is simple: clients connect to named pipes on the frontend, while your application broadcasts events to those pipes on the backend. These events can contain any additional data that you want to make available on the front end.
Channel
A channel is a group of connections/clients for receiving/sending messages.
The channel structure contains:
- channel name
- connection list
Each connection is assigned:
- identification structure
- response handler
An identity structure is needed to send a message to specific clients in a channel.
The response handler generates the message body before sending it.
Route configuration
"servers": {
"s1": {
...
"websockets": {
"default": ["/var/www/server/build/exec/handlers/libwsindexpage.so", "default_"],
"routes": {
"/channel-join": {
"GET": ["/var/www/server/build/exec/handlers/libwsindexpage.so", "channel_join"]
},
"/channel-leave": {
"GET": ["/var/www/server/build/exec/handlers/libwsindexpage.so", "channel_leave"]
},
"/channel-send-message": {
"POST": ["/var/www/server/build/exec/handlers/libwsindexpage.so", "channel_send"]
}
}
},
...
}
}
Server
Channel
The broadcast_add
method creates a channel and adds a connection pointer, connection identification structure data, and a response handler function to it.
If messages in the channel will be sent to all clients, then there is no need to create a connection identification structure.
void channel_join(websocketsrequest_t* request, websocketsresponse_t* response) {
broadcast_id_t* id = NULL;
broadcast_add("my_broadcast_name", request->connection, id, mybroadcast_send_data);
response->data(response, "done");
}
The broadcast_remove
method removes the client from the channel.
void channel_leave(websocketsrequest_t* request, websocketsresponse_t* response) {
broadcast_remove("my_broadcast_name", request->connection);
response->data(response, "done");
}
Response handler
Handler for generating the response structure websocketsresponse_t
.
void mybroadcast_send_data(response_t* response, const char* data, size_t size) {
websocketsresponse_t* wsresponse = (websocketsresponse_t*)response;
wsresponse->textn(wsresponse, data, size);
}
Identification structure
Basic connection identification structure:
typedef struct broadcast_id {
void(*free)(struct broadcast_id*);
} broadcast_id_t;
The structure must be allocated memory from the heap so that the scheduler can correctly identify connections.
Memory is released automatically when the connection is closed or the client leaves the channel.
To free memory, the structure contains a free
callback. For each structure, it is necessary to implement its own callback to correctly free memory.
Based on the basic structure, you need to form your own structure.
// broadcasting/mybroadcast.h
typedef struct mybroadcast_id {
broadcast_id_t base;
int user_id;
int project_id;
} mybroadcast_id_t;
mybroadcast_id_t* mybroadcast_id_create();
void mybroadcast_id_free(void*);
// broadcasting/mybroadcast.c
mybroadcast_id_t* mybroadcast_id_create() {
mybroadcast_id_t* st = malloc(sizeof * st);
if (st == NULL) return NULL;
st->base.free = mybroadcast_id_free;
st->user_id = 1;
st->project_id = 2;
return st;
}
void mybroadcast_id_free(void* id) {
mybroadcast_id_t* my_id = id;
if (id == NULL) return;
my_id->user_id = 0;
my_id->project_id = 0;
free(my_id);
}
Let's create a channel and attach an identification structure to the connection.
void channel_join(websocketsrequest_t* request, websocketsresponse_t* response) {
broadcast_id_t* id = mybroadcast_id_create();
if (id == NULL) {
response->data(response, "out of memory");
return;
}
broadcast_add("my_broadcast_name", request->connection, id, mybroadcast_send_data);
response->data(response, "done");
}
Sending messages
To send data, the broadcast_send_all
and broadcast_send
methods are used. The broadcast_send_all
method broadcasts data to all recipients.
void channel_send(websocketsrequest_t* request, websocketsresponse_t* response) {
const char* data = "text data";
size_t length = strlen(data);
broadcast_send_all("my_broadcast_name", request->connection, data, length);
response->data(response, "done");
}
The broadcast_send
method broadcasts data to recipients by comparing identity structures.
mybroadcast_id_t* mybroadcast_compare_id_create() {
mybroadcast_id_t* st = malloc(sizeof * st);
if (st == NULL) return NULL;
st->base.free = mybroadcast_id_free;
st->user_id = 1;
st->project_id = 0;
return st;
}
int mybroadcast_compare(void* sourceStruct, void* targetStruct) {
mybroadcast_id_t* sd = sourceStruct;
mybroadcast_id_t* td = targetStruct;
return sd->user_id == td->user_id;
}
void channel_send(websocketsrequest_t* request, websocketsresponse_t* response) {
const char* data = "text data";
size_t length = strlen(data);
mybroadcast_id_t* id = mybroadcast_compare_id_create();
if (id == NULL) {
response->data(response, "out of memory");
return;
}
broadcast_send("my_broadcast_name", request->connection, data, length, id, mybroadcast_compare);
response->data(response, "done");
}
Client
Establishing a connection
Connect to the server and send a request to the channel-join
resource to join the channel.
let socket = new WebSocket("wss://cpdy.io/wss", "resource");
socket.onopen = (event) => {
socket.send("GET /channel-join")
}
socket.onmessage = (event) => {
console.log(event.data)
}
You will now receive messages coming into the channel.
Sending a message
To send a message, you must send a request to the resourcechannel-send-message
with payload.
socket.send("POST /channel-send-message new message")
The client that sends the message will not receive the message from the channel.