Skip to content

Commit 487034c

Browse files
committed
WSocket MultiEndpoint example
1 parent 81d5c17 commit 487034c

File tree

4 files changed

+1897
-0
lines changed

4 files changed

+1897
-0
lines changed
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
3+
4+
/*
5+
this example shows how to handle multiple websocket endpoints within same server instance
6+
7+
*/
8+
9+
#include <AsyncWSocket.h>
10+
#include <WiFi.h>
11+
#include "endpoints.h"
12+
13+
#define WIFI_SSID "your_ssid"
14+
#define WIFI_PASSWD "your_pass"
15+
16+
// WebSocket endpoints to serve
17+
constexpr const char WSENDPOINT_DEFAULT[] = "/ws"; // default endpoint - used for logging messages
18+
constexpr const char WSENDPOINT_ECHO[] = "/wsecho"; // echo server - it replies back messages it receives
19+
constexpr const char WSENDPOINT_SPEED[] = "/wsspeed"; // upstream speed test - sending large data chunks from server to browser
20+
21+
// *** Event handlers ***
22+
// WS Events dispatcher
23+
void wsEventDispatcher(WSocketClient *client, WSocketClient::event_t event);
24+
// speed tester
25+
void wsSpeedService(WSocketClient *client, WSocketClient::event_t event);
26+
// echo service
27+
void wsEchoService(WSocketClient *client, WSocketClient::event_t event);
28+
// default service
29+
void wsDefaultService(WSocketClient *client, WSocketClient::event_t event);
30+
31+
// Web Sever
32+
AsyncWebServer server(80);
33+
// WebSocket server instance
34+
WSocketServer ws(WSENDPOINT_DEFAULT, wsEventDispatcher);
35+
36+
// this function is attached as main callback function for websocket events
37+
void wsEventDispatcher(WSocketClient *client, WSocketClient::event_t event){
38+
if (event == WSocketClient::event_t::connect || event == WSocketClient::event_t::disconnect){
39+
// report all new connections to default endpoint
40+
char buff[100];
41+
snprintf(buff, 100, "Client %s, id:%lu, IP:%s:%u\n",
42+
event == WSocketClient::event_t::connect ? "connected" : "disconnected" ,
43+
client->id,
44+
IPAddress (client->client()->getRemoteAddress4().addr).toString().c_str(),
45+
client->client()->getRemotePort()
46+
);
47+
// send message to clients connected to default /ws endpoint
48+
ws.textToEndpoint(WSENDPOINT_DEFAULT, buff);
49+
Serial.print(buff);
50+
}
51+
52+
// here we identify on which endpoint we received and event and dispatch to the corresponding handler
53+
switch (client->getURLHash())
54+
{
55+
case asyncsrv::hash_djb2a(WSENDPOINT_ECHO) :
56+
wsEchoService(client, event);
57+
break;
58+
59+
case asyncsrv::hash_djb2a(WSENDPOINT_SPEED) :
60+
wsSpeedService(client, event);
61+
break;
62+
63+
default:
64+
wsDefaultService(client, event);
65+
break;
66+
}
67+
68+
}
69+
70+
71+
// default service - we will use it for event logging and information reports
72+
void wsDefaultService(WSocketClient *client, WSocketClient::event_t event){
73+
switch (event){
74+
case WSocketClient::event_t::msgRecv : {
75+
// we do nothing but discard messages here (if any), no use for now
76+
client->dequeueMessage();
77+
break;
78+
}
79+
80+
default:;
81+
}
82+
}
83+
84+
// default service - we will use it for event logging and information reports
85+
void wsEchoService(WSocketClient *client, WSocketClient::event_t event){
86+
switch (event){
87+
case WSocketClient::event_t::connect : {
88+
ws.text(client->id, "Hello Client, this is an echo endpoint, message me something and I will reply it back");
89+
break;
90+
}
91+
92+
// incoming message
93+
case WSocketClient::event_t::msgRecv : {
94+
auto m = client->dequeueMessage();
95+
if (m->type == WSFrameType_t::text){
96+
// got a text message, reformat it and reply
97+
std::string msg("Your message was: ");
98+
msg.append(m->getData());
99+
// avoid copy and move string to message queue
100+
ws.text(client->id, std::move(msg));
101+
}
102+
103+
break;
104+
}
105+
default:;
106+
}
107+
}
108+
109+
110+
// for speed test load
111+
uint8_t *buff = nullptr;
112+
size_t buff_size = 32 * 1024; // will send 32k message buffer
113+
//size_t cnt{0};
114+
// a unique stream id - it is used to avoid sending dublicate frames when multiple clients connected to speedtest endpoint
115+
uint32_t client_id{0};
116+
117+
void bulkSend(uint32_t token){
118+
if (!buff) return;
119+
if (client_id == 0){
120+
// first client connected grabs the stream
121+
client_id = token;
122+
} else if (token != client_id) {
123+
// we will send next frame only upon delivery the previous one to client owning the stream, for others we ignore
124+
// this is to avoid stacking new frames in the Q when multiple clients are connected to the server
125+
return;
126+
}
127+
128+
// generate metadata info frame
129+
// this text frame will carry our resources stat
130+
char msg[120];
131+
snprintf(msg, 120, "FrameSize:%u, Mem:%lu, psram:%lu, token:%lu", buff_size, ESP.getFreeHeap(), ESP.getFreePsram(), client_id);
132+
133+
ws.textToEndpoint(WSENDPOINT_SPEED, msg);
134+
135+
// here we MUST ensure that client owning the stream is able to send data, otherwise recursion would crash controller
136+
if (ws.clientState(client_id) == WSocketClient::err_t::ok){
137+
// for bulk load sending we will use WSMessageStaticBlob object, it will directly send
138+
// payload to websocket peers without intermediate buffer copies and
139+
// it is the most efficient way to send large objects from memory/ROM
140+
auto m = std::make_shared<WSMessageStaticBlob>(
141+
WSFrameType_t::binary, // bynary message
142+
true, // final message
143+
reinterpret_cast<const char*>(buff), buff_size, // buffer to transfer
144+
// the key here to understand when frame buffer completes delivery - for this we set
145+
// the callback back to ourself, so that when when
146+
// this frame would complete delivery, this function is called again to obtain a new frame buffer from camera
147+
[](WSMessageStatus_t s, uint32_t t){ bulkSend(t); },
148+
client_id // message token
149+
);
150+
// send message to all peers of this endpoint
151+
ws.messageToEndpoint(WSENDPOINT_SPEED, m);
152+
//++cnt;
153+
} else {
154+
client_id = 0;
155+
}
156+
}
157+
158+
// speed tester - this endpoint will send bulk dummy payload to anyone who connects here
159+
void wsSpeedService(WSocketClient *client, WSocketClient::event_t event){
160+
switch (event){
161+
case WSocketClient::event_t::connect : {
162+
// prepare a buffer with some junk data
163+
if (!buff)
164+
buff = (uint8_t*)malloc(buff_size);
165+
// start an endless bulk transfer
166+
bulkSend(client->id);
167+
break;
168+
}
169+
170+
// incoming message
171+
case WSocketClient::event_t::msgRecv : {
172+
// silently discard here everything comes in
173+
client->dequeueMessage();
174+
}
175+
break;
176+
177+
case WSocketClient::event_t::disconnect :
178+
// if no more clients are connected, release memory
179+
if ( ws.activeEndpointClientsCount(WSENDPOINT_SPEED) == 0){
180+
delete buff;
181+
buff = nullptr;
182+
}
183+
break;
184+
185+
default:;
186+
}
187+
188+
}
189+
190+
191+
192+
// setup our server
193+
void setup() {
194+
Serial.begin(115200);
195+
196+
#ifndef CONFIG_IDF_TARGET_ESP32H2
197+
WiFi.mode(WIFI_STA);
198+
WiFi.begin(WIFI_SSID, WIFI_PASSWD);
199+
//WiFi.softAP("esp-captive");
200+
#endif
201+
// Wait for connection
202+
while (WiFi.status() != WL_CONNECTED) {
203+
delay(250);
204+
Serial.print(".");
205+
}
206+
Serial.println("");
207+
Serial.print("Connected to ");
208+
//Serial.println(ssid);
209+
Serial.print("IP address: ");
210+
Serial.println(WiFi.localIP());
211+
Serial.printf("Open the browser and connect to http://%s/\n", WiFi.localIP());
212+
213+
// HTTP endpoint
214+
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
215+
// need to cast to uint8_t*
216+
// if you do not, the const char* will be copied in a temporary String buffer
217+
request->send(200, "text/html", (uint8_t *)htmlPage, std::string_view(htmlPage).length());
218+
});
219+
220+
// add endpoint for bulk speed testing
221+
ws.addURLendpoint("/wsspeed");
222+
223+
// add endpoint for message echo testing
224+
ws.addURLendpoint("/wsecho");
225+
226+
// attach WebSocket server to web server
227+
server.addHandler(&ws);
228+
229+
// start server
230+
server.begin();
231+
232+
//log_e("e setup end");
233+
//log_w("w server started");
234+
//log_d("d server debug");
235+
}
236+
237+
238+
void loop() {
239+
// nothing to do here
240+
vTaskDelete(NULL);
241+
}

0 commit comments

Comments
 (0)