| // 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; |
| } |