Manual Page
gwsocket is a standalone, simple, yet powerful rfc6455 compliant WebSocket Server.
gwsocket -p [--addr][--origin][gwsocket options ...]
gwsocket is a simple, standalone, language-agnostic, RFC6455 compliant
WebSocket Server, written in C. It sits between your application and the
client's browser, giving fast bidirectional communication between these two
with ease and flexibility.
You can run gwsocket without passing any options to the command line. By
default, it will listen on port 7890
on all the interfaces, e.g.,
127.0.0.1
, ::1
, etc. For instance:
# gwsocket --access-log=/tmp/access.log
Note: See a basic client-side example
at the bottom of the page.
The following options can be supplied to the command line.
-
-p --port
-
Specifies the port to bind.
-
-h --help
-
Command line help.
-
-V --version
-
Display version information and exit.
-
--access-log=<path/file>
-
Specifies the path/file for the access log.
-
--addr=<addr>
-
Specifies the address to bind.
-
--echo-mode
-
Set the server to echo all received messages.
-
--max-frame-size=<bytes>
-
Maximum size of a websocket frame. This includes received frames from the
client and messages through the named pipe.
-
--origin=<origin>
-
Ensure clients send the specified origin header upon the WebSocket handshake.
-
--pipein=<path/file>
-
Creates a named pipe (FIFO) that reads from on the given path/file.
-
--pipeout=<path/file>
-
Creates a named pipe (FIFO) that writes to the given path/file.
-
--std
-
Enable --stdin and --stdout. By default named pipes are used for I/O. This adds the option to use stdin / stdout as well. For example, this allows:
# gwsocket --std > log.txt
# tail -F /var/log/syslog | gwsocket --std
--stdin
and --stdout
are added for fine grained control.
-
--stdin
-
Send stdin to the websocket.
-
--stdout
-
Send received websocket data to stdout.
-
--strict
-
Parse messages using strict mode. See man page for more details.
-
--ssl-cert=<cert.crt>
-
Path to TLS/SSL certificate.
-
--ssl-key=<priv.key>
-
Path to TLS/SSL private key.
-
--unix-socket=<addr>
-
Specify UNIX-domain socket address to bind server to.
-
--enable-debug
-
Compile with debugging symbols and turn off compiler optimizations.
-
--with-openssl
-
Compile gwsocket with OpenSSL support for its WebSocket server.
In order to establish a channel between your application and the client's
browser, gwsocket provides two methods that allow the user to send data in and
out. The first one is through the use of the standard input (stdin),
and the standard output (stdout). The second method is through a
fixed-size header followed by the payload. See options below for more details.
1. stdin/stdout
The standard input/output is the simplest way of sending/receiving data to/from
a client. However, it's limited to broadcasting messages to all clients. To
send messages to or receive from a specific client, use the strict mode in
section 2.
1.1 Sending Data to all Clients — stdout
If you need to broadcast data from your application to all clients connected to
gwsocket, then, the simplest way of doing it is by piping your
application output into a
named pipe
(also known as FIFO) that gwsocket makes use of. Once gwsocket receives the
payload, then it will automatically broadcast the message to all connected
clients.
1.1. Examples
Sending data can be as simple as doing a # tail -f /var/log/nginx/access.log >
/tmp/wspipein.fifo
or you can do it in the language of your choice.
See examples below.
#include
#include
#include
#include
int main() {
int fd;
char *myfifo = "/tmp/wspipein.fifo";
const char *msg = "Message to broadcast";
fd = open(myfifo, O_WRONLY);
write(fd, msg, strlen(msg));
close(fd);
return 0;
}
#!/usr/bin/perl
use warnings;
use strict;
open(my $fh, '>>', "/tmp/wspipein.fifo") or die "Could not open pipe!";
say $fh "Message to broadcast";
close $fh;
import os
fifo = open("/tmp/wspipein.fifo", "w")
fifo.write("Message to broadcast!\n")
fifo.close()
/* Send me a snippet of another language and I'll post it here... */
Note: You can send as many bytes PIPE_BUF
can hold. If a message is greater than PIPE_BUF
, it would send the
rest on a second message or third, and so on. See strict mode
below for more control over messages.
1.2 Receiving Data from Clients — stdin
When a client sends a message to the server, it is possible to capture that
message in your application. To do this, your application simply needs to read
from a named pipe. By default, gwsocket creates a FIFO under
/tmp/wspipeout.fifo
.
1.2. Examples
Receiving data can be as simple as doing a # cat
/tmp/wspipeout.fifo
or you can do it in the language of your choice.
See examples below.
#include
#include
#include
#include
static void read_message (int fd, fd_set set) {
int bytes = 0;
char buf[PIPE_BUF] = { 0 };
FD_ZERO (&set);
FD_SET (fd, &set);
if ((select (fd + 1, &set, NULL, NULL, NULL)) < 1)
exit (1);
if (!FD_ISSET (fd, &set))
return;
if (read (fd, buf, PIPE_BUF) > 0)
printf ("%s\n", buf);
}
int main (void) {
fd_set set;
char *fifo = "/tmp/wspipeout.fifo";
int fd = 0;
if ((fd = open (fifo, O_RDWR | O_NONBLOCK)) < 0)
exit (1);
while (1)
read_message(fd, set);
return 0;
}
#!/usr/bin/perl
use strict;
use warnings;
use IO::Select;
use POSIX;
my $s = IO::Select->new();
sysopen my $fifo, "/tmp/wspipeout.fifo", O_RDWR|O_NONBLOCK;
$s->add($fifo);
while (1) {
if (!$s->can_read(1)) {
next;
}
my $data;
if (sysread($fifo, $data, 1024) == 0) {
last;
}
print "$data\n";
}
import select
import os
poller = select.poll()
fifo = os.open("/tmp/wspipeout.fifo", os.O_RDONLY | os.O_NONBLOCK)
poller.register(fifo, select.POLLIN)
while True:
p = poller.poll()
string = os.read(p[0][0], 4096)
if len(string):
print string
0) {
$bytes = fread($r[0], 1024);
print $bytes . "\n";
}
}
/* Send me a snippet of another language and I'll post it here... */
Note: Make sure the reader in your
application is set as non-blocking to get a constant feed.
Tip: If you need to know which client
sent the message, for example, in a chat application, please see the strict
mode below.
2. Strict Mode
gwsocket implements its own tiny protocol for sending/receiving data. In
contrast to the stdin/stdout mode, the strict mode allows you to send/receive
data to/from specific connected clients as well as to keep track of who
opened/closed a WebSocket connection. It also gives you the ability to pack
and send as much data as you would like on a single message.
The message header is a fixed-size header. The first 12 bytes
(uint32_t)
are packed in network byte order and contain the
"meta-data" of the message we are sending/receiving. The rest of it is the
actual message.
0 1 2 3
+---------------------------------------------+
| Client Socket Id (listener) |
+---------------------------------------------+
| Message Type (binary: 0x2 / text: 0x1) |
+---------------------------------------------+
| Payload length |
+---------------------------------------------+
| Payload Data |
+---------------------------------------------+
2.1 Sending Data — Strict Mode
If you need to send a message to a specific client, then you can do so by
specifying the client id in the message header. If set to 0
, the
message will be broadcasted to all clients. The first 4 bytes
are
reserved for the client id or listener. The following 4 bytes
are
reserved for the message type. 0x01
for a text message, and
0x02
for a binary message. And the last 4 bytes
are
reserved for the payload's length.
Once the header has been written to the pipe, you may now write the message.
2.1 Examples
First, start the server in strict-mode.
# gwsocket --strict
#include
#include
#include
#include
#include
size_t pack_uint32(void* buf, uint32_t val) {
uint32_t v32 = htonl(val);
memcpy(buf, &v32, sizeof(uint32_t));
return sizeof(uint32_t);
}
int main() {
char *p = calloc (sizeof(uint32_t) * 3, sizeof(char)), *ptr;
const char *msg = "Message to broadcast";
const char *fifo = "/tmp/wspipein.fifo";
int fd;
ptr = p;
ptr += pack_uint32(ptr, 0);
ptr += pack_uint32(ptr, 0x01);
ptr += pack_uint32(ptr, strlen(msg));
fd = open(fifo, O_WRONLY);
write(fd, p, sizeof(uint32_t) * 3);
write(fd, msg, strlen(msg));
close(fd);
free (p);
return 0;
}
#!/usr/bin/perl
use warnings;
use strict;
my $message = "Message to broadcast";
open(my $fh, '>>', "/tmp/wspipein.fifo") or die "Could not open pipe!";
# Send the header packed as an unsigned long (32-bit) in network (big-endian) order.
# 0 -> Broadcast to all clients. You may specify the client id.
# 1 -> Message type. 2 -> binary, 1 -> text
# Message length
print $fh pack('NNN', 0, 1, length $message);
# Followed by a second write containing the payload
print $fh $message;
close $fh;
import os
import struct
msg = "Message to broadcast!"
fifo = open("/tmp/wspipein.fifo", "w")
# Send the header packed as an unsigned long (32-bit) in network (big-endian) order.
# 0 -> Broadcast to all clients. You may specify the client id.
# 1 -> Message type. 2 -> binary, 1 -> text
# Message length
fifo.write(struct.pack(">LLL", 0, 1, len(msg)))
# Followed by a second write containing the payload
fifo.write(msg)
fifo.close()
Broadcast to all clients. You may specify the client id.
// 1 -> Message type. 2 -> binary, 1 -> text
// Message length
fwrite($pipe, pack('NNN', 0, 1, strlen($message)));
// Followed by a second write containing the payload
fwrite($pipe, $message);
fclose($pipe);
/* Send me a snippet of another language and I'll post it here... */
2.2 Receiving Data from Clients — Strict Mode
Now, to get a message from a specific client and route it to another client,
you just need to do the opposite of sending data. First you unpack the header
from network byte order to host byte order and then read the payload.
2.2. Examples
First, start the server in strict-mode.
# gwsocket --strict
#include
#include
#include
#include
#include
static size_t unpack_uint32 (const void *b, uint32_t * val) {
uint32_t v32 = 0;
memcpy (&v32, b, sizeof (uint32_t));
*val = ntohl (v32);
return sizeof (uint32_t);
}
static void read_message (int fd, fd_set set) {
int bytes = 0;
uint32_t size = 0, listener = 0, type = 0;
char hdr[PIPE_BUF] = { 0 }, buf[PIPE_BUF] = {0};
char *ptr = NULL;
FD_ZERO (&set);
FD_SET (fd, &set);
if ((select (fd + 1, &set, NULL, NULL, NULL)) < 1)
exit (1);
if (!FD_ISSET (fd, &set))
return;
if (hdr[0] == '\0') {
if (read (fd, hdr, sizeof (uint32_t) * 3) < 1)
return;
}
ptr = hdr;
ptr += unpack_uint32(ptr, &listener);
ptr += unpack_uint32(ptr, &type);
ptr += unpack_uint32(ptr, &size);
if (read (fd, buf, size) < 1)
return;
printf ("client: %d, msg: %s\n", listener, buf);
}
int main (void) {
fd_set set;
char *fifo = "/tmp/wspipeout.fifo";
int fd = 0;
if ((fd = open (fifo, O_RDWR | O_NONBLOCK)) < 0)
exit (1);
while (1)
read_message(fd, set);
return 0;
}
#!/usr/bin/perl
use strict;
use warnings;
use IO::Select;
use POSIX;
my $s = IO::Select->new();
sysopen my $fifo, "/tmp/wspipeout.fifo", O_RDWR|O_NONBLOCK;
$s->add($fifo);
while (1) {
if (!$s->can_read(1)) {
next;
}
my $hdr;
if (sysread($fifo, $hdr, 12) == 0) {
last;
}
my ($size, $type, $listener) = reverse unpack("N4N4N4", $hdr) ;
my $buf;
if (sysread($fifo, $buf, $size) == 0) {
last;
}
print "client: $listener, msg: $buf\n";
}
import select
import os
import struct
poller = select.poll()
fifo = os.open("/tmp/wspipeout.fifo", os.O_RDONLY | os.O_NONBLOCK)
poller.register(fifo, select.POLLIN)
while True:
p = poller.poll()
hdr = os.read(p[0][0], 12)
if len(hdr):
listener, mtype, size, = struct.unpack('>LLL', hdr)
print "client: %d" % listener
buf = os.read(p[0][0], size)
if len(buf):
print "msg: %s" % buf
0) {
$hdr = fread($r[0], 12);
list ($listener, $type, $size) = array_values(unpack("N*", $hdr));
$buf = fread($r[0], $size);
printf("client: %d, msg: %s\n", $listener, $buf);
}
}
/* Send me a snippet of another language and I'll post it here... */
Note: If you read/write to a stream,
be aware that they do not necessarily read/write the full amount of data you
have requested. Your application will need to handle the case where only a
single byte is read or written. Examples above do not handle this.
Here's the basic example, client and server side. First start the server and set it in echo mode.
# gwsocket --echo-mode
Now, let's create the client side.
If you think you have found a bug, please send me an email to hello [@at] goaccess.io.
Gerardo Orellana. For more details about it, or new releases, please visit http://gwsocket.io or check my twitter.