Преглед изворни кода

Add example websocket client

bel2125 пре 7 година
родитељ
комит
5b25223222
3 измењених фајлова са 416 додато и 0 уклоњено
  1. 2 0
      examples/README.md
  2. 5 0
      examples/ws_client/build.sh
  3. 409 0
      examples/ws_client/ws_client.c

+ 2 - 0
examples/README.md

@@ -9,6 +9,8 @@ In addition, there is one example how to configure a HTTPS server, to comply wit
 
 The [multidomain](https://github.com/civetweb/civetweb/tree/master/examples/multidomain) example demonstrates how to host multiple domains with different HTTPS certificates. It uses the standalone server (civetweb.c + main.c) and existing certificates.
 
+The [ws_client](https://github.com/civetweb/civetweb/tree/master/examples/ws_client) example shows how to use the websocket client interface to communicate with an (external) websocket server. It uses the "echo demo" of [websocket.org](http://websocket.org/echo.html), and only works if this server is reachable.
+
 Some no longer maintained examples can be found in the ["obsolete"](https://github.com/civetweb/civetweb/tree/master/examples/_obsolete) folder. It is not guaranteed that they work in the current version - they are kept for reference, but might be removed in the future.
 
 All examples are subject to the MIT license (unless noted otherwise) - they come without warranty of any kind.

+ 5 - 0
examples/ws_client/build.sh

@@ -0,0 +1,5 @@
+#!/bin/sh
+rm ws_client_example
+cc ws_client.c ../../src/civetweb.c -DUSE_WEBSOCKET -I../../include -lpthread -ldl -g -O0 -DNO_SSL -o ws_client_example -Wall
+ls -la ws_client_example
+

+ 409 - 0
examples/ws_client/ws_client.c

@@ -0,0 +1,409 @@
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "civetweb.h"
+
+/* Get an OS independent definition for sleep() */
+#ifdef _WIN32
+#include <windows.h>
+#define sleep(x) Sleep((x)*1000)
+#else
+#include <unistd.h>
+#endif
+
+
+/* User defined client data structure */
+struct tclient_data {
+
+	time_t started;
+	time_t closed;
+	struct tmsg_list_elem *msgs;
+};
+
+struct tmsg_list_elem {
+	time_t timestamp;
+	void *data;
+	size_t len;
+	struct tmsg_list_elem *next;
+};
+
+
+/* Helper function to get a printable name for websocket opcodes */
+static const char *
+msgtypename(int flags)
+{
+	unsigned f = (unsigned)flags & 0xFu;
+	switch (f) {
+	case MG_WEBSOCKET_OPCODE_CONTINUATION:
+		return "continuation";
+	case MG_WEBSOCKET_OPCODE_TEXT:
+		return "text";
+	case MG_WEBSOCKET_OPCODE_BINARY:
+		return "binary";
+	case MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE:
+		return "clonnection close";
+	case MG_WEBSOCKET_OPCODE_PING:
+		return "PING";
+	case MG_WEBSOCKET_OPCODE_PONG:
+		return "PING";
+	}
+	return "unknown";
+}
+
+
+/* Callback for handling data received from the server */
+static int
+websocket_client_data_handler(struct mg_connection *conn,
+                              int flags,
+                              char *data,
+                              size_t data_len,
+                              void *user_data)
+{
+	struct tclient_data *pclient_data = (struct tclient_data *)user_data;
+	time_t now = time(NULL);
+
+	/* We may get some different message types (websocket opcodes).
+	 * We will handle these messages differently. */
+	int is_text = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_TEXT);
+	int is_bin = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_BINARY);
+	int is_ping = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_PING);
+	int is_pong = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_PONG);
+	int is_close = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE);
+
+	/* Log output: We got some data */
+	printf("%10.0f - Client received %lu bytes of %s data from server%s",
+	       difftime(now, pclient_data->started),
+	       (long unsigned)data_len,
+	       msgtypename(flags),
+	       (is_text ? ": " : ".\n"));
+
+	/* Check if we got a websocket PING request */
+	if (is_ping) {
+		/* PING requests are to check if the connection is broken.
+		 * They should be replied with a PONG with the same data.
+		 */
+		mg_websocket_client_write(conn,
+		                          MG_WEBSOCKET_OPCODE_PONG,
+		                          data,
+		                          data_len);
+		return 1;
+	}
+
+	/* Check if we got a websocket PONG message */
+	if (is_pong) {
+		/* A PONG message may be a response to our PING, but
+		 * it is also allowed to send unsolicited PONG messages
+		 * send by the server to check some lower level TCP
+		 * connections. Just ignore all kinds of PONGs. */
+		return 1;
+	}
+
+	/* It we got a websocket TEXT message, handle it ... */
+	if (is_text) {
+		struct tmsg_list_elem *p;
+		struct tmsg_list_elem **where = &(pclient_data->msgs);
+
+		/* ... by printing it to the log ... */
+		fwrite(data, 1, data_len, stdout);
+		printf("\n");
+
+		/* ... and storing it (OOM ignored for simplicity). */
+		p = (struct tmsg_list_elem *)malloc(sizeof(struct tmsg_list_elem));
+		p->timestamp = now;
+		p->data = malloc(data_len);
+		memcpy(p->data, data, data_len);
+		p->len = data_len;
+		p->next = NULL;
+		while (*where != NULL) {
+			where = &((*where)->next);
+		}
+		*where = p;
+	}
+
+	/* Another option would be BINARY data. */
+	if (is_bin) {
+		/* In this example, we just ignore binary data.
+		 * According to some blogs, discriminating TEXT and
+		 * BINARY may be some remains from earlier drafts
+		 * of the WebSocket protocol.
+		 * Anyway, a real application will usually use
+		 * either TEXT or BINARY. */
+	}
+
+	/* It could be a CLOSE message as well. */
+	if (is_close) {
+		printf("%10.0f - Goodbye\n", difftime(now, pclient_data->started));
+		return 0;
+	}
+
+	/* Return 1 to keep the connection open */
+	return 1;
+}
+
+
+/* Callback for handling a close message received from the server */
+static void
+websocket_client_close_handler(const struct mg_connection *conn,
+                               void *user_data)
+{
+	struct tclient_data *pclient_data = (struct tclient_data *)user_data;
+
+	pclient_data->closed = time(NULL);
+	printf("%10.0f - Client: Close handler\n",
+	       difftime(pclient_data->closed, pclient_data->started));
+}
+
+
+/* Websocket client test function */
+void
+run_websocket_client(const char *host,
+                     int port,
+                     int secure,
+                     const char *path,
+                     const char *greetings)
+{
+	char err_buf[100] = {0};
+	struct mg_connection *client_conn;
+	struct tclient_data *pclient_data;
+	int i;
+
+	/* Allocate some memory for callback specific data.
+	 * For simplicity, we ignore OOM handling in this example. */
+	pclient_data = (struct tclient_data *)malloc(sizeof(struct tclient_data));
+
+	/* Store start time in the private structure */
+	pclient_data->started = time(NULL);
+	pclient_data->closed = 0;
+	pclient_data->msgs = NULL;
+
+	/* Log first action (time = 0.0) */
+	printf("%10.0f - Connecting to %s:%i\n", 0.0, host, port);
+
+	/* Connect to the given WS or WSS (WS secure) server */
+	client_conn = mg_connect_websocket_client(host,
+	                                          port,
+	                                          secure,
+	                                          err_buf,
+	                                          sizeof(err_buf),
+	                                          path,
+	                                          NULL,
+	                                          websocket_client_data_handler,
+	                                          websocket_client_close_handler,
+	                                          pclient_data);
+
+	/* Check if connection is possible */
+	if (client_conn == NULL) {
+		printf("mg_connect_websocket_client error: %s\n", err_buf);
+		return;
+	}
+
+	/* Connection established */
+	printf("%10.0f - Connected\n", difftime(time(NULL), pclient_data->started));
+
+	/* If there are greetings to send, do it now */
+	if (greetings) {
+		printf("%10.0f - Sending greetings\n",
+		       difftime(time(NULL), pclient_data->started));
+
+		mg_websocket_client_write(client_conn,
+		                          MG_WEBSOCKET_OPCODE_TEXT,
+		                          greetings,
+		                          strlen(greetings));
+	}
+
+	/* Wait for some seconds */
+	sleep(5);
+
+	/* Does the server play "ping pong" ? */
+	for (i = 0; i < 5; i++) {
+		/* Send a PING message every 5 seconds. */
+		printf("%10.0f - Sending PING\n",
+		       difftime(time(NULL), pclient_data->started));
+		mg_websocket_client_write(client_conn,
+		                          MG_WEBSOCKET_OPCODE_PING,
+		                          (const char *)&i,
+		                          sizeof(int));
+		sleep(5);
+	}
+
+	/* Wait a while */
+	/* If we do not use "ping pong", the server will probably
+	 * close the connection with a timeout earlier. */
+	sleep(150);
+
+	/* Send greetings again */
+	if (greetings) {
+		printf("%10.0f - Sending greetings again\n",
+		       difftime(time(NULL), pclient_data->started));
+
+		mg_websocket_client_write(client_conn,
+		                          MG_WEBSOCKET_OPCODE_TEXT,
+		                          greetings,
+		                          strlen(greetings));
+	}
+
+	/* Wait for some seconds */
+	sleep(5);
+
+	/* Send some "song text": http://www.99-bottles-of-beer.net/ */
+	{
+		char txt[128];
+		int b = 99; /* start with 99 bottles */
+
+		while (b > 0) {
+			/* Send "b bottles" text line. */
+			sprintf(txt,
+			        "%i bottle%s of beer on the wall, "
+			        "%i bottle%s of beer.",
+			        b,
+			        ((b != 1) ? "s" : ""),
+			        b,
+			        ((b != 1) ? "s" : ""));
+			mg_websocket_client_write(client_conn,
+			                          MG_WEBSOCKET_OPCODE_TEXT,
+			                          txt,
+			                          strlen(txt));
+
+			/* Take a breath. */
+			sleep(1);
+
+			/* Drink a bottle */
+			b--;
+
+			/* Send "remaining bottles" text line. */
+			if (b) {
+				sprintf(txt,
+				        "Take one down and pass it around, "
+				        "%i bottle%s of beer on the wall.",
+				        b,
+				        ((b != 1) ? "s" : ""));
+			} else {
+				strcpy(txt,
+				       "Take one down and pass it around, "
+				       "no more bottles of beer on the wall.");
+			}
+			mg_websocket_client_write(client_conn,
+			                          MG_WEBSOCKET_OPCODE_TEXT,
+			                          txt,
+			                          strlen(txt));
+
+			/* Take a breath. */
+			sleep(2);
+		}
+
+		/* Send "no more bottles" text line. */
+		strcpy(txt,
+		       "No more bottles of beer on the wall, "
+		       "no more bottles of beer.");
+		mg_websocket_client_write(client_conn,
+		                          MG_WEBSOCKET_OPCODE_TEXT,
+		                          txt,
+		                          strlen(txt));
+
+		/* Take a breath. */
+		sleep(1);
+
+		/* Buy new bottles. */
+		b = 99;
+
+		/* Send "buy some more" text line. */
+		sprintf(txt,
+		        "Go to the store and buy some more, "
+		        "%i bottle%s of beer on the wall.",
+		        b,
+		        ((b != 1) ? "s" : ""));
+		mg_websocket_client_write(client_conn,
+		                          MG_WEBSOCKET_OPCODE_TEXT,
+		                          txt,
+		                          strlen(txt));
+	}
+
+	/* Wait for some seconds */
+	sleep(5);
+
+	/* Somewhat boring conversation, isn't it?
+	 * Tell the server we have to leave. */
+	printf("%10.0f - Sending close message\n",
+	       difftime(time(NULL), pclient_data->started));
+	mg_websocket_client_write(client_conn,
+	                          MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE,
+	                          NULL,
+	                          0);
+
+	/* We don't need to wait, this is just to have the log timestamp
+	 * a second later, and to not log from the handlers and from
+	 * here the same time (printf to stdout is not thread-safe, but
+	 * adding flock or mutex or an explicit logging function makes
+	 * this example unnecessarily complex). */
+	sleep(5);
+
+	/* Connection should be closed by now. */
+	printf("%10.0f - Connection state: %s\n",
+	       difftime(time(NULL), pclient_data->started),
+	       ((pclient_data->closed == 0) ? "open" : "closed"));
+
+	/* Close client connection */
+	mg_close_connection(client_conn);
+	printf("%10.0f - End of test\n",
+	       difftime(time(NULL), pclient_data->started));
+
+
+	/* Print collected data */
+	printf("\n\nPrint all text messages from server again:\n");
+	{
+		struct tmsg_list_elem **where = &(pclient_data->msgs);
+		while (*where != NULL) {
+			printf("%10.0f - [%5lu] ",
+			       difftime((*where)->timestamp, pclient_data->started),
+			       (unsigned long)(*where)->len);
+			fwrite((const char *)(*where)->data, 1, (*where)->len, stdout);
+			printf("\n");
+
+			where = &((*where)->next);
+		}
+	}
+
+	/* Free collected data */
+	{
+		struct tmsg_list_elem **where = &(pclient_data->msgs);
+		void *p1 = 0;
+		void *p2 = 0;
+		while (*where != NULL) {
+			free((*where)->data);
+			free(p2);
+			p2 = p1;
+			p1 = *where;
+
+			where = &((*where)->next);
+		}
+		free(p2);
+		free(p1);
+	}
+	free(pclient_data);
+}
+
+
+/* main will initialize the CivetWeb library
+ * and start the WebSocket client test function */
+int
+main(int argc, char *argv[])
+{
+	const char *greetings = "Hello World!";
+
+	const char *host = "echo.websocket.org";
+	const char *path = "/";
+
+#if defined(NO_SSL)
+	const int port = 80;
+	const int secure = 0;
+	mg_init_library(0);
+#else
+	const int port = 443;
+	const int secure = 1;
+	mg_init_library(MG_FEATURES_SSL);
+#endif
+
+	run_websocket_client(host, port, secure, path, greetings);
+}