1 #include <time.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5
6 #include "civetweb.h"
7
8 /* Get an OS independent definition for sleep() */
9 #ifdef _WIN32
10 #include <windows.h>
11 #define sleep(x) Sleep((x)*1000)
12 #else
13 #include <unistd.h>
14 #endif
15
16
17 /* User defined client data structure */
18 struct tclient_data {
19
20 time_t started;
21 time_t closed;
22 struct tmsg_list_elem *msgs;
23 };
24
25 struct tmsg_list_elem {
26 time_t timestamp;
27 void *data;
28 size_t len;
29 struct tmsg_list_elem *next;
30 };
31
32
33 /* Helper function to get a printable name for websocket opcodes */
34 static const char *
msgtypename(int flags)35 msgtypename(int flags)
36 {
37 unsigned f = (unsigned)flags & 0xFu;
38 switch (f) {
39 case MG_WEBSOCKET_OPCODE_CONTINUATION:
40 return "continuation";
41 case MG_WEBSOCKET_OPCODE_TEXT:
42 return "text";
43 case MG_WEBSOCKET_OPCODE_BINARY:
44 return "binary";
45 case MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE:
46 return "connection close";
47 case MG_WEBSOCKET_OPCODE_PING:
48 return "PING";
49 case MG_WEBSOCKET_OPCODE_PONG:
50 return "PONG";
51 }
52 return "unknown";
53 }
54
55
56 /* Callback for handling data received from the server */
57 static int
websocket_client_data_handler(struct mg_connection * conn,int flags,char * data,size_t data_len,void * user_data)58 websocket_client_data_handler(struct mg_connection *conn,
59 int flags,
60 char *data,
61 size_t data_len,
62 void *user_data)
63 {
64 struct tclient_data *pclient_data = (struct tclient_data *)user_data;
65 time_t now = time(NULL);
66
67 /* We may get some different message types (websocket opcodes).
68 * We will handle these messages differently. */
69 int is_text = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_TEXT);
70 int is_bin = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_BINARY);
71 int is_ping = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_PING);
72 int is_pong = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_PONG);
73 int is_close = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE);
74
75 /* Log output: We got some data */
76 printf("%10.0f - Client received %lu bytes of %s data from server%s",
77 difftime(now, pclient_data->started),
78 (long unsigned)data_len,
79 msgtypename(flags),
80 (is_text ? ": " : ".\n"));
81
82 /* Check if we got a websocket PING request */
83 if (is_ping) {
84 /* PING requests are to check if the connection is broken.
85 * They should be replied with a PONG with the same data.
86 */
87 mg_websocket_client_write(conn,
88 MG_WEBSOCKET_OPCODE_PONG,
89 data,
90 data_len);
91 return 1;
92 }
93
94 /* Check if we got a websocket PONG message */
95 if (is_pong) {
96 /* A PONG message may be a response to our PING, but
97 * it is also allowed to send unsolicited PONG messages
98 * send by the server to check some lower level TCP
99 * connections. Just ignore all kinds of PONGs. */
100 return 1;
101 }
102
103 /* It we got a websocket TEXT message, handle it ... */
104 if (is_text) {
105 struct tmsg_list_elem *p;
106 struct tmsg_list_elem **where = &(pclient_data->msgs);
107
108 /* ... by printing it to the log ... */
109 fwrite(data, 1, data_len, stdout);
110 printf("\n");
111
112 /* ... and storing it (OOM ignored for simplicity). */
113 p = (struct tmsg_list_elem *)malloc(sizeof(struct tmsg_list_elem));
114 p->timestamp = now;
115 p->data = malloc(data_len);
116 memcpy(p->data, data, data_len);
117 p->len = data_len;
118 p->next = NULL;
119 while (*where != NULL) {
120 where = &((*where)->next);
121 }
122 *where = p;
123 }
124
125 /* Another option would be BINARY data. */
126 if (is_bin) {
127 /* In this example, we just ignore binary data.
128 * According to some blogs, discriminating TEXT and
129 * BINARY may be some remains from earlier drafts
130 * of the WebSocket protocol.
131 * Anyway, a real application will usually use
132 * either TEXT or BINARY. */
133 }
134
135 /* It could be a CLOSE message as well. */
136 if (is_close) {
137 printf("%10.0f - Goodbye\n", difftime(now, pclient_data->started));
138 return 0;
139 }
140
141 /* Return 1 to keep the connection open */
142 return 1;
143 }
144
145
146 /* Callback for handling a close message received from the server */
147 static void
websocket_client_close_handler(const struct mg_connection * conn,void * user_data)148 websocket_client_close_handler(const struct mg_connection *conn,
149 void *user_data)
150 {
151 struct tclient_data *pclient_data = (struct tclient_data *)user_data;
152
153 pclient_data->closed = time(NULL);
154 printf("%10.0f - Client: Close handler\n",
155 difftime(pclient_data->closed, pclient_data->started));
156 }
157
158
159 /* Websocket client test function */
160 void
run_websocket_client(const char * host,int port,int secure,const char * path,const char * greetings)161 run_websocket_client(const char *host,
162 int port,
163 int secure,
164 const char *path,
165 const char *greetings)
166 {
167 char err_buf[100] = {0};
168 struct mg_connection *client_conn;
169 struct tclient_data *pclient_data;
170 int i;
171
172 /* Allocate some memory for callback specific data.
173 * For simplicity, we ignore OOM handling in this example. */
174 pclient_data = (struct tclient_data *)malloc(sizeof(struct tclient_data));
175
176 /* Store start time in the private structure */
177 pclient_data->started = time(NULL);
178 pclient_data->closed = 0;
179 pclient_data->msgs = NULL;
180
181 /* Log first action (time = 0.0) */
182 printf("%10.0f - Connecting to %s:%i\n", 0.0, host, port);
183
184 /* Connect to the given WS or WSS (WS secure) server */
185 client_conn = mg_connect_websocket_client(host,
186 port,
187 secure,
188 err_buf,
189 sizeof(err_buf),
190 path,
191 NULL,
192 websocket_client_data_handler,
193 websocket_client_close_handler,
194 pclient_data);
195
196 /* Check if connection is possible */
197 if (client_conn == NULL) {
198 printf("mg_connect_websocket_client error: %s\n", err_buf);
199 return;
200 }
201
202 /* Connection established */
203 printf("%10.0f - Connected\n", difftime(time(NULL), pclient_data->started));
204
205 /* If there are greetings to send, do it now */
206 if (greetings) {
207 printf("%10.0f - Sending greetings\n",
208 difftime(time(NULL), pclient_data->started));
209
210 mg_websocket_client_write(client_conn,
211 MG_WEBSOCKET_OPCODE_TEXT,
212 greetings,
213 strlen(greetings));
214 }
215
216 /* Wait for some seconds */
217 sleep(5);
218
219 /* Does the server play "ping pong" ? */
220 for (i = 0; i < 5; i++) {
221 /* Send a PING message every 5 seconds. */
222 printf("%10.0f - Sending PING\n",
223 difftime(time(NULL), pclient_data->started));
224 mg_websocket_client_write(client_conn,
225 MG_WEBSOCKET_OPCODE_PING,
226 (const char *)&i,
227 sizeof(int));
228 sleep(5);
229 }
230
231 /* Wait a while */
232 /* If we do not use "ping pong", the server will probably
233 * close the connection with a timeout earlier. */
234 sleep(150);
235
236 /* Send greetings again */
237 if (greetings) {
238 printf("%10.0f - Sending greetings again\n",
239 difftime(time(NULL), pclient_data->started));
240
241 mg_websocket_client_write(client_conn,
242 MG_WEBSOCKET_OPCODE_TEXT,
243 greetings,
244 strlen(greetings));
245 }
246
247 /* Wait for some seconds */
248 sleep(5);
249
250 /* Send some "song text": http://www.99-bottles-of-beer.net/ */
251 {
252 char txt[128];
253 int b = 99; /* start with 99 bottles */
254
255 while (b > 0) {
256 /* Send "b bottles" text line. */
257 sprintf(txt,
258 "%i bottle%s of beer on the wall, "
259 "%i bottle%s of beer.",
260 b,
261 ((b != 1) ? "s" : ""),
262 b,
263 ((b != 1) ? "s" : ""));
264 mg_websocket_client_write(client_conn,
265 MG_WEBSOCKET_OPCODE_TEXT,
266 txt,
267 strlen(txt));
268
269 /* Take a breath. */
270 sleep(1);
271
272 /* Drink a bottle */
273 b--;
274
275 /* Send "remaining bottles" text line. */
276 if (b) {
277 sprintf(txt,
278 "Take one down and pass it around, "
279 "%i bottle%s of beer on the wall.",
280 b,
281 ((b != 1) ? "s" : ""));
282 } else {
283 strcpy(txt,
284 "Take one down and pass it around, "
285 "no more bottles of beer on the wall.");
286 }
287 mg_websocket_client_write(client_conn,
288 MG_WEBSOCKET_OPCODE_TEXT,
289 txt,
290 strlen(txt));
291
292 /* Take a breath. */
293 sleep(2);
294 }
295
296 /* Send "no more bottles" text line. */
297 strcpy(txt,
298 "No more bottles of beer on the wall, "
299 "no more bottles of beer.");
300 mg_websocket_client_write(client_conn,
301 MG_WEBSOCKET_OPCODE_TEXT,
302 txt,
303 strlen(txt));
304
305 /* Take a breath. */
306 sleep(1);
307
308 /* Buy new bottles. */
309 b = 99;
310
311 /* Send "buy some more" text line. */
312 sprintf(txt,
313 "Go to the store and buy some more, "
314 "%i bottle%s of beer on the wall.",
315 b,
316 ((b != 1) ? "s" : ""));
317 mg_websocket_client_write(client_conn,
318 MG_WEBSOCKET_OPCODE_TEXT,
319 txt,
320 strlen(txt));
321 }
322
323 /* Wait for some seconds */
324 sleep(5);
325
326 /* Somewhat boring conversation, isn't it?
327 * Tell the server we have to leave. */
328 printf("%10.0f - Sending close message\n",
329 difftime(time(NULL), pclient_data->started));
330 mg_websocket_client_write(client_conn,
331 MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE,
332 NULL,
333 0);
334
335 /* We don't need to wait, this is just to have the log timestamp
336 * a second later, and to not log from the handlers and from
337 * here the same time (printf to stdout is not thread-safe, but
338 * adding flock or mutex or an explicit logging function makes
339 * this example unnecessarily complex). */
340 sleep(5);
341
342 /* Connection should be closed by now. */
343 printf("%10.0f - Connection state: %s\n",
344 difftime(time(NULL), pclient_data->started),
345 ((pclient_data->closed == 0) ? "open" : "closed"));
346
347 /* Close client connection */
348 mg_close_connection(client_conn);
349 printf("%10.0f - End of test\n",
350 difftime(time(NULL), pclient_data->started));
351
352
353 /* Print collected data */
354 printf("\n\nPrint all text messages from server again:\n");
355 {
356 struct tmsg_list_elem **where = &(pclient_data->msgs);
357 while (*where != NULL) {
358 printf("%10.0f - [%5lu] ",
359 difftime((*where)->timestamp, pclient_data->started),
360 (unsigned long)(*where)->len);
361 fwrite((const char *)(*where)->data, 1, (*where)->len, stdout);
362 printf("\n");
363
364 where = &((*where)->next);
365 }
366 }
367
368 /* Free collected data */
369 {
370 struct tmsg_list_elem **where = &(pclient_data->msgs);
371 void *p1 = 0;
372 void *p2 = 0;
373 while (*where != NULL) {
374 free((*where)->data);
375 free(p2);
376 p2 = p1;
377 p1 = *where;
378
379 where = &((*where)->next);
380 }
381 free(p2);
382 free(p1);
383 }
384 free(pclient_data);
385 }
386
387
388 /* main will initialize the CivetWeb library
389 * and start the WebSocket client test function */
390 int
main(int argc,char * argv[])391 main(int argc, char *argv[])
392 {
393 const char *greetings = "Hello World!";
394
395 const char *host = "echo.websocket.org";
396 const char *path = "/";
397
398 #if defined(NO_SSL)
399 const int port = 80;
400 const int secure = 0;
401 mg_init_library(0);
402 #else
403 const int port = 443;
404 const int secure = 1;
405 mg_init_library(MG_FEATURES_SSL);
406 #endif
407
408 run_websocket_client(host, port, secure, path, greetings);
409 }
410