1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
.. highlight:: c
Tutorial
========
WebSocket Echo Server
---------------------
This section describes briefly how to make WebSocket echo server with
Wslay library in C.
The complete source code is located at :file:`examples/fork-echoserv.c`.
This WebSocket echo server listens on port given in the command-line.
When the incoming connection from the client is accepted,
it forks another process.
The parent process goes back to the event loop and can accept another client.
The child process communicates with
the client (WebSocket HTTP handshake and then WebSocket data transfer).
For the purpose of this tutorial, we focus on the use of Wslay library.
The primary function to communicate with WebSocket client is
:c:func:`communicate` function.
.. c:function:: int communicate(int fd)
This function performs HTTP handshake
and WebSocket data transfer until close handshake is done or an
error occurs. *fd* is the file descriptor of the connection to the
client. This function returns 0 if it succeeds, or returns 0.
Let's look into this function. First we perform HTTP handshake.
It will be done with :c:func:`http_handshake` function.
When it succeeds, we make the file descriptor of the connection non-block.
You may set other socket options like ``TCP_NODELAY``.
At this point, we can start WebSocket data transfer.
Now establish callbacks for wslay event-based API.
We use 3 callbacks in this example::
struct wslay_event_callbacks callbacks = {
recv_callback,
send_callback,
NULL,
NULL,
NULL,
NULL,
on_msg_recv_callback
};
``recv_callback`` is invoked by :c:func:`wslay_event_recv`
when it wants to read more data from the client.
It looks like this::
ssize_t recv_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len,
void *user_data)
{
struct Session *session = (struct Session*)user_data;
ssize_t r;
while((r = recv(session->fd, buf, len, 0)) == -1 && errno == EINTR);
if(r == -1) {
if(errno == EAGAIN || errno == EWOULDBLOCK) {
wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK);
} else {
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
}
} else if(r == 0) {
/* Unexpected EOF is also treated as an error */
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
r = -1;
}
return r;
}
If :c:func:`recv` failed with ``EAGAIN`` or ``EWOULDBLOCK``
(notice that we made socket
non-block), we set ``WSLAY_ERR_WOULDBLOCK`` using
:c:func:`wslay_event_set_error`
to tell the wslay library to stop reading from the socket.
If it failed with other reasons, we set ``WSLAY_ERR_CALLBACK_FAILED``,
and it will make :c:func:`wslay_event_recv` fail.
Notice that reading EOF here is unexpected: so it is also treated as an error.
``send_callback`` is invoked by :c:func:`wslay_event_send`
when it wants to send data to the client.
It looks like this::
ssize_t send_callback(wslay_event_context_ptr ctx,
const uint8_t *data, size_t len, void *user_data)
{
struct Session *session = (struct Session*)user_data;
ssize_t r;
int sflags = 0;
#ifdef MSG_MORE
if(flags & WSLAY_MSG_MORE) {
sflags |= MSG_MORE;
}
#endif // MSG_MORE
while((r = send(session->fd, data, len, sflags)) == -1 && errno == EINTR);
if(r == -1) {
if(errno == EAGAIN || errno == EWOULDBLOCK) {
wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK);
} else {
wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE);
}
}
return r;
}
Similar to ``recv_callback``, we set error code using
:c:func:`wslay_event_set_error` depending on the ``errno`` value.
``on_msg_recv_callback`` is invoked by :c:func:`wslay_event_recv`
when it have received a message completely.
It looks like this::
void on_msg_recv_callback(wslay_event_context_ptr ctx,
const struct wslay_event_on_msg_recv_arg *arg,
void *user_data)
{
/* Echo back non-control message */
if(!wslay_is_ctrl_frame(arg->opcode)) {
struct wslay_event_msg msgarg = {
arg->opcode, arg->msg, arg->msg_length
};
wslay_event_queue_msg(ctx, &msgarg);
}
}
Here, since we are building echo server, we just echo back non-control
frames to the client. ``arg->opcode`` is a opcode of the received
message. ``arg->msg`` contains received message data with length
``arg->msg_length``.
:c:func:`wslay_event_queue_msg` queues message to the client.
Then initialize wslay event-based API context::
wslay_event_context_server_init(&ctx, &callbacks, &session);
At this point, we finished initialization of Wslay library and all we have to
do is run event-loop and communicate with the client.
For event-loop we need event notification mechanism, here we use
standard :c:func:`poll`. Since we don't have any message to send client,
first we query read event only.
The event loop looks like this::
/*
* Event loop: basically loop until both wslay_event_want_read(ctx)
* and wslay_event_want_write(ctx) return 0.
*/
while(wslay_event_want_read(ctx) || wslay_event_want_write(ctx)) {
int r;
while((r = poll(&event, 1, -1)) == -1 && errno == EINTR);
if(r == -1) {
perror("poll");
res = -1;
break;
}
if(((event.revents & POLLIN) && wslay_event_recv(ctx) != 0) ||
((event.revents & POLLOUT) && wslay_event_send(ctx) != 0) ||
(event.revents & (POLLERR | POLLHUP | POLLNVAL))) {
/*
* If either wslay_event_recv() or wslay_event_send() return
* non-zero value, it means serious error which prevents wslay
* library from processing further data, so WebSocket connection
* must be closed.
*/
res = -1;
break;
}
event.events = 0;
if(wslay_event_want_read(ctx)) {
event.events |= POLLIN;
}
if(wslay_event_want_write(ctx)) {
event.events |= POLLOUT;
}
}
return res;
Basically, we just loop until both :c:func:`wslay_event_want_read` and
:c:func:`wslay_event_want_write` return 0.
Also if either :c:func:`wslay_event_recv` or :c:func:`wslay_event_send`
return non-zero value, we exit the loop.
If there is data to read, call :c:func:`wslay_event_recv`.
If there is data to write and writing will not block, call
:c:func:`wslay_event_send`.
After exiting the event loop, we just close the connection,
most likely, using ``shutdown(fd, SHUT_WR)`` and ``close(fd)``.
|