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:
- 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
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’.
- 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.