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 <windows.h> 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’.

3. 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.

One thought on “Libwebsockets Introduction

Comments are closed.