1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
// Copyright (c) 2004-2012 Sergey Lyubka
// This file is a part of civetweb project, http://github.com/bel2125/civetweb
//
// v 0.1 Contributed by William Greathouse 9-Sep-2013
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "civetweb.h"
// simple structure for keeping track of websocket connection
struct ws_connection {
struct mg_connection *conn;
int update;
int closing;
};
// time base and structure periodic updates to client for demo
#define BASETIME 100000 /* 0.1 seconds */
struct progress {
int limit;
int increment;
int period;
int value;
};
// up to 16 independent client connections
#define CONNECTIONS 16
static struct ws_connection ws_conn[CONNECTIONS];
// ws_server_thread()
// Simple demo server thread. Sends periodic updates to connected clients
static void *ws_server_thread(void *parm)
{
int wsd = (long)parm;
struct mg_connection *conn = ws_conn[wsd].conn;
int timer = 0;
char tstr[32];
int i;
struct progress meter[] = {
/* first meter 0 to 1000, by 5 every 0.1 second */
{ 1000, 5, 1, 0 },
/* second meter 0 to 500, by 10 every 0.5 second */
{ 500, 10, 5, 0 },
/* third meter 0 to 100, by 10 every 1.0 second */
{ 100, 10, 10, 0},
/* end of list */
{ 0, 0, 0, 0}
};
fprintf(stderr, "ws_server_thread %d\n", wsd);
/* Send initial meter updates */
for (i=0; meter[i].period != 0; i++) {
if (meter[i].value >= meter[i].limit)
meter[i].value = 0;
if (meter[i].value >= meter[i].limit)
meter[i].value = meter[i].limit;
sprintf(tstr, "meter%d:%d,%d", i+1,
meter[i].value, meter[i].limit);
mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, tstr, strlen(tstr));
}
/* While the connection is open, send periodic updates */
while(!ws_conn[wsd].closing) {
usleep(100000); /* 0.1 second */
timer++;
/* Send meter updates */
if (ws_conn[wsd].update) {
for (i=0; meter[i].period != 0; i++) {
if (timer%meter[i].period == 0) {
if (meter[i].value >= meter[i].limit)
meter[i].value = 0;
else
meter[i].value += meter[i].increment;
if (meter[i].value >= meter[i].limit)
meter[i].value = meter[i].limit;
// if we are closing, server should not send new data
if (!ws_conn[wsd].closing) {
sprintf(tstr, "meter%d:%d,%d", i+1,
meter[i].value, meter[i].limit);
mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, tstr, strlen(tstr));
}
}
}
}
/* Send periodic PING to assure websocket remains connected, except if we are closing */
if (timer%100 == 0 && !ws_conn[wsd].closing)
mg_websocket_write(conn, WEBSOCKET_OPCODE_PING, NULL, 0);
}
fprintf(stderr, "ws_server_thread %d exiting\n", wsd);
// reset connection information to allow reuse by new client
ws_conn[wsd].conn = NULL;
ws_conn[wsd].update = 0;
ws_conn[wsd].closing = 2;
return NULL;
}
// websocket_connect_handler()
// On new client connection, find next available server connection and store
// new connection information. If no more server connections are available
// tell civetweb to not accept the client request.
static int websocket_connect_handler(const struct mg_connection *conn)
{
int i;
fprintf(stderr, "connect handler\n");
for(i=0; i < CONNECTIONS; ++i) {
if (ws_conn[i].conn == NULL) {
fprintf(stderr, "...prep for server %d\n", i);
ws_conn[i].conn = (struct mg_connection *)conn;
ws_conn[i].closing = 0;
ws_conn[i].update = 0;
break;
}
}
if (i >= CONNECTIONS) {
fprintf(stderr, "Refused connection: Max connections exceeded\n");
return 1;
}
return 0;
}
// websocket_ready_handler()
// Once websocket negotiation is complete, start a server for the connection
static void websocket_ready_handler(struct mg_connection *conn)
{
int i;
fprintf(stderr, "ready handler\n");
for(i=0; i < CONNECTIONS; ++i) {
if (ws_conn[i].conn == conn) {
fprintf(stderr, "...start server %d\n", i);
mg_start_thread(ws_server_thread, (void *)(long)i);
break;
}
}
}
// websocket_close_handler()
// When websocket is closed, tell the associated server to shut down
static void websocket_close_handler(struct mg_connection *conn)
{
int i;
//fprintf(stderr, "close handler\n"); /* called for every close, not just websockets */
for(i=0; i < CONNECTIONS; ++i) {
if (ws_conn[i].conn == conn) {
fprintf(stderr, "...close server %d\n", i);
ws_conn[i].closing = 1;
}
}
}
// Arguments:
// flags: first byte of websocket frame, see websocket RFC,
// http://tools.ietf.org/html/rfc6455, section 5.2
// data, data_len: payload data. Mask, if any, is already applied.
static int websocket_data_handler(struct mg_connection *conn, int flags,
char *data, size_t data_len)
{
int i;
int wsd;
for(i=0; i < CONNECTIONS; ++i) {
if (ws_conn[i].conn == conn) {
wsd = i;
break;
}
}
if (i >= CONNECTIONS) {
fprintf(stderr, "Received websocket data from unknown connection\n");
return 1;
}
if (flags & 0x80) {
flags &= 0x7f;
switch (flags) {
case WEBSOCKET_OPCODE_CONTINUATION:
fprintf(stderr, "CONTINUATION...\n");
break;
case WEBSOCKET_OPCODE_TEXT:
fprintf(stderr, "TEXT: %-.*s\n", (int)data_len, data);
/*** interpret data as commands here ***/
if (strncmp("update on", data, data_len)== 0) {
/* turn on updates */
ws_conn[wsd].update = 1;
/* echo back */
mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, data, data_len);
} else if (strncmp("update off", data, data_len)== 0) {
/* turn off updates */
ws_conn[wsd].update = 0;
/* echo back */
mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, data, data_len);
}
break;
case WEBSOCKET_OPCODE_BINARY:
fprintf(stderr, "BINARY...\n");
break;
case WEBSOCKET_OPCODE_CONNECTION_CLOSE:
fprintf(stderr, "CLOSE...\n");
/* If client initiated close, respond with close message in acknowlegement */
if (!ws_conn[wsd].closing) {
mg_websocket_write(conn, WEBSOCKET_OPCODE_CONNECTION_CLOSE, data, data_len);
ws_conn[wsd].closing = 1; /* we should not send addional messages when close requested/acknowledged */
}
return 0; /* time to close the connection */
break;
case WEBSOCKET_OPCODE_PING:
/* client sent PING, respond with PONG */
mg_websocket_write(conn, WEBSOCKET_OPCODE_PONG, data, data_len);
break;
case WEBSOCKET_OPCODE_PONG:
/* received PONG to our PING, no action */
break;
default:
fprintf(stderr, "Unknown flags: %02x\n", flags);
break;
}
}
return 1; /* keep connection open */
}
int main(void)
{
char server_name[40];
struct mg_context *ctx;
struct mg_callbacks callbacks;
const char *options[] = {
"listening_ports", "8080",
"document_root", "docroot",
NULL
};
/* get simple greeting for the web server */
snprintf(server_name, sizeof(server_name),
"Civetweb websocket server v. %s",
mg_version());
memset(&callbacks, 0, sizeof(callbacks));
callbacks.websocket_connect = websocket_connect_handler;
callbacks.websocket_ready = websocket_ready_handler;
callbacks.websocket_data = websocket_data_handler;
callbacks.connection_close = websocket_close_handler;
ctx = mg_start(&callbacks, NULL, options);
/* show the greeting and some basic information */
printf("%s started on port(s) %s with web root [%s]\n",
server_name, mg_get_option(ctx, "listening_ports"),
mg_get_option(ctx, "document_root"));
getchar(); // Wait until user hits "enter"
mg_stop(ctx);
return 0;
}
|