Libwebsockets Introduction

In this article, I will share the basic concept of Libwebsockets, as well as how to write a simple program with the APIs.

What is Libwebsockets

Libwebsockets is a lightweight pure C library; built to use minimal CPU and memory resources as well as providing fast throughput in both directions.

  • Provides server and client APIs for v13 websocket protocol, along with http[s].
  • Can be configured to use OpenSSL or CyaSSL to provide fully encrypted client and server links including client certificate support.
  • It’s a fully autotools, and optionally CMake, based project that has been used in a variety of OS contexts; including Linux (uclibc and glibc), ARM-based embedded boards, MIPS / OpenWRT, Windows, Android, Apple iOS and even Tivo.
  • It includes a stub webserver that is enough to deliver your scripts to the browser that open WebSocket connections back to the same server, so it can solve the entire server side, ws://, wss://, http:// and https:// in one step. Apache, Java or any other server-side support is not needed.
  • Chrome 26 and Firefox 18 are supported, including the WebKit websocket compression extension.
  • Architectural features like zero-copy for payload data and FSM-based protocol parsers make it ideal for realtime operation on resource-constrained devices.
  • WebSocket + HTTP serving for ARM: code + data + bss combined is under 15K, plus 12K at init to support up to 1024 fds, and 112 bytes per connection… minimal single client case in < 35KBytes total including library footprint
  • Valgrind-clean, reliable and robust.
  • It’s licensed under LGPL2 + static link exception and comes with test servers that demonstrate client and server communication between the test apps and a test browser applet.

Getting Started with Libwebsockets

libwebsockets provides a simple and understandable interface to help us implement our own features or even a fully functional lightweight websocket server. So let’s start:

  1. First, to use the libwebsockets, include its header file:
#include "../lib/libwebsockets.h"

Its dependents for the project will construct on your own machine. A tip is, this header file should be included before to avoid the redefine error.

2. Lbwebsockets allows you to define any protocols at your pleasure. So just define your protocol structure as follows:

//////////////////////////////////////////////////////////////////////////
/* http-only */
struct per_session_data__http {
	HANDLE hFile;
};

static int callback_http(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len);

//////////////////////////////////////////////////////////////////////////
/* dumb-increment-protocol */
struct per_session_data__dumb_increment {
	int number;
};

static int callback_dumb_increment(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len);

//////////////////////////////////////////////////////////////////////////
/* lws-command-protocol */
struct per_session_data__lws_command {
	bool isSyn;
};

static int callback_lws_command(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len);
//////////////////////////////////////////////////////////////////////////
char pro_http[] = "http-only";
char pro_dumb_increment[] = "dumb-increment-protocol";
char pro_lws_command[] = "lws-command-protocol";
//////////////////////////////////////////////////////////////////////////
struct libwebsocket_protocols protocols[] = {
	{
		pro_http,			/* protocol name, char */
		callback_http,		/* callback function */
		sizeof (struct per_session_data__http),	/* per_session_data_size */
		0,	/* max buffer */
	},
	{
		pro_dumb_increment,
		callback_dumb_increment,
		sizeof(struct per_session_data__dumb_increment),
		0,
	},
	{
		pro_lws_command,
		callback_lws_command,
		sizeof(struct per_session_data__lws_command),
		0,
	},
	{ NULL, NULL, 0, 0 } /* terminator */
};

libwebsocket_protocols is a protocol structure defined in libwebsockets. Field instructions:

  •  [pro_http] is the name of the protocol. You can define any protocol names you like, but the first protocol must always be the HTTP handler.
  •  [callback_http] is the callback function for you to implement your own business logic. We will introduce it below.
  •  [sizeof (struct per_session_data__http)] is the user data structure size you want to transfer.
  •  [0] means the maximum frame size.

So a group of protocols like this can be defined here and processed in libwebsockets. The only job left is to define your own callback function. A typical HTTP callback function may be like this:

static int callback_http(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len)
{
	int n, m;
	unsigned char *p;
	static unsigned char buffer[4096];

	TCHAR cache_file[MAX_PATH];
	//struct stat stat_buf;
	unsigned int fileSize = 0;
	struct per_session_data__http *pss =
			(struct per_session_data__http *)user;

	DWORD readLen = 0;

	char client_name[128];
	char client_ip[128];

	switch (reason) {
	case LWS_CALLBACK_HTTP:

		lwsl_notice("http.\n");
		/* check for the "send a big file by hand" example case */
		sprintf(cache_file, "%s\\cache.png", g_cache_path);
		p = buffer;

		pss->hFile = CreateFile(cache_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);

		if (pss->hFile == NULL || pss->hFile == INVALID_HANDLE_VALUE)
			return -1;
		/*
		 * we will send a big jpeg file, but it could be
		 * anything.  Set the Content-Type: appropriately
		 * so the browser knows what to do with it.
		 */

		p += sprintf((char *)p,
			"HTTP/1.0 200 OK\x0d\x0a"
			"Server: libwebsockets\x0d\x0a"
			"Content-Type: image/png\x0d\x0a"
				"Content-Length: %u\x0d\x0a\x0d\x0a",
				GetFileSize(pss->hFile, 0));

		/*
		 * send the http headers...
		 * this won't block since it's the first payload sent
		 * on the connection since it was established
		 * (too small for partial)
		 */
		n = libwebsocket_write(wsi, buffer,
			   p - buffer, LWS_WRITE_HTTP);

		if (n < 0) {
			CloseHandle(pss->hFile);
			return -1;
		}
		/*
		 * book us a LWS_CALLBACK_HTTP_WRITEABLE callback
		 */
		libwebsocket_callback_on_writable(context, wsi);
		break;

	case LWS_CALLBACK_HTTP_FILE_COMPLETION:
//		lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n");
		/* kill the connection after we sent one file */
		return -1;

	case LWS_CALLBACK_HTTP_WRITEABLE:
		/*
		 * we can send more of whatever it is we were sending
		 */
		do {
			if (ReadFile(pss->hFile, buffer, sizeof buffer, &readLen, NULL) == INVALID_HANDLE_VALUE)
			{
				CloseHandle(pss->hFile);
				return -1;
}
			/* problem reading, close conn */
			if (readLen <= 0)
			{
				CloseHandle(pss->hFile);
				return -1;
			}
			/*
			 * because it's HTTP and not websocket, don't need to take
			 * care about pre and postamble
			 */
			m = libwebsocket_write(wsi, buffer, readLen, LWS_WRITE_HTTP);
			if (m < 0)
			{
				CloseHandle(pss->hFile);
				return -1;
			}
			if (m != readLen)
				/* partial write, adjust */
				SetFilePointer(pss->hFile, m - readLen, 0, FILE_CURRENT);

		} while (!lws_send_pipe_choked(wsi));
		libwebsocket_callback_on_writable(context, wsi);
		break;

	/*
	 * callback for confirming to continue with client IP appear in
	 * protocol 0 callback since no websocket protocol has been agreed
	 * yet.  You can just ignore this if you won't filter on client IP
	 * since the default uhandled callback return is 0 meaning let the
	 * connection continue.
	 */

	case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
		libwebsockets_get_peer_addresses(context, wsi, (int)(long)in, client_name, sizeof(client_name) ,client_ip, sizeof(client_ip));

		fprintf(stderr, "Received network connect from %s (%s)\n",
							client_name, client_ip);
		/* if we returned non-zero from here, we kill the connection */
		break;

	default:
		break;
	}
	return 0;
}

Actually, libwebsockets will call this function after receiving any HTTP request. By judging the call ‘reason’ in a switch(reason), we can implement our business logic in the right ‘case’.

  1. After defining all of this necessary structure, we can create a libwebsockets context to call all these functions:
int main(int argc, char **argv)
{
	char cert_path[1024];
	char key_path[1024];
	int n = 0;
	int use_ssl = 0;
	struct libwebsocket_context *context;
	int opts = 0;
	char interface_name[128] = "";
	const char *iface = NULL;
#ifndef WIN32
	int syslog_options = LOG_PID | LOG_PERROR;
#endif
	unsigned int oldus = 0;
	struct lws_context_creation_info info;

	int debug_level = 7;

	memset(&info, 0, sizeof info);
	info.port = 7681;	// port is important

	signal(SIGINT, sighandler);

	/* tell the library what debug level to emit and to send it to syslog */
	lws_set_log_level(debug_level, lwsl_emit_syslog);

	lwsl_notice("libwebsockets test server - "
			"(C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - "
						    "licensed under LGPL2.1\n");

	info.iface = iface;
	info.protocols = protocols;	// protocols is the libwebsocket_protocols array we defined before
	info.extensions = libwebsocket_get_internal_extensions();

	if (!use_ssl) {
		info.ssl_cert_filepath = NULL;
		info.ssl_private_key_filepath = NULL;
	} else {
		if (strlen(resource_path) > sizeof(cert_path) - 32) {
			lwsl_err("resource path too long\n");
			return -1;
		}
		sprintf(cert_path, "%s/libwebsockets-test-server.pem",
								resource_path);
		if (strlen(resource_path) > sizeof(key_path) - 32) {
			lwsl_err("resource path too long\n");
			return -1;
		}
		sprintf(key_path, "%s/libwebsockets-test-server.key.pem",
								resource_path);

		info.ssl_cert_filepath = cert_path;
		info.ssl_private_key_filepath = key_path;
	}
	info.gid = -1;
	info.uid = -1;
	info.options = opts;

	context = libwebsocket_create_context(&info);
	if (context == NULL) {
		lwsl_err("libwebsocket init failed\n");
		return -1;
	}

	n = 0;
	while (n >= 0 && !force_exit) {
		struct timeval tv;

		gettimeofday(&tv, NULL);

		if (((unsigned int)tv.tv_usec - oldus) > 50000) {
			libwebsocket_callback_on_writable_all_protocol(&protocols[PROTOCOL_DUMB_INCREMENT]);
			oldus = tv.tv_usec;
		}

		/*
		 * If libwebsockets sockets are all we care about,
		 * you can use this api which takes care of the poll()
		 * and looping through finding who needed service.
		 *
		 * If no socket needs service, it'll return anyway after
		 * the number of ms in the second argument.
		 */

		n = libwebsocket_service(context, 50);
	}

	libwebsocket_context_destroy(context);

	lwsl_notice("libwebsockets-test-server exited cleanly\n");

	return 0;
}

The important field is the port number and use_ssl. If the port number is already being used by another process, libwebsocket_create_context(&info); will fail. And, if you need to use an ssl connection in your application, right cert path and the key path are also necessary.

This is a simple introduction for libewebsockets use, Any suggestions or corrections will be appreciated.