Skip to content

Commit

Permalink
trac 40 client connection properly nonblocking
Browse files Browse the repository at this point in the history
As spotted by JM on Trac#40

http://libwebsockets.org/trac/libwebsockets/ticket/40

client connect didn't do anything about being truly nonblocking.  This patch
should hopefully solve that.

Signed-off-by: Andy Green <[email protected]>
  • Loading branch information
Andy Green committed Sep 20, 2013
1 parent e929476 commit 5dc62ea
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 44 deletions.
20 changes: 20 additions & 0 deletions README.coding
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,23 @@ is too computationally expensive. To use it, point it to a string like

if left NULL, then the "DEFAULT" set of ciphers are all possible to select.


Async nature of client connections
----------------------------------

When you call libwebsocket_client_connect(..) and get a wsi back, it does not
mean your connection is active. It just mean it started trying to connect.

Your client connection is actually active only when you receive
LWS_CALLBACK_CLIENT_ESTABLISHED for it.

There's a 5 second timeout for the connection, and it may give up or die for
other reasons, if any of that happens you'll get a
LWS_CALLBACK_CLIENT_CONNECTION_ERROR callback on protocol 0 instead for the
wsi.

After attempting the connection and getting back a non-NULL wsi you should
loop calling libwebsocket_service() until one of the above callbacks occurs.

As usual, see test-client.c for example code.

4 changes: 4 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ User api additions
move the protocol name to the "in" parameter. The docs for this
callback are also updated to reflect how to check headers in there.

- libwebsocket_client_connect() is now properly nonblocking and async. See
README.coding and test-client.c for information on the callbacks you
can rely on controlling the async connection period with.


User api changes
----------------
Expand Down
65 changes: 48 additions & 17 deletions lib/client-handshake.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,28 @@ struct libwebsocket *__libwebsocket_client_connect_2(
goto oom4;
}

wsi->sock = socket(AF_INET, SOCK_STREAM, 0);

if (wsi->sock < 0) {
lwsl_warn("Unable to open socket\n");
goto oom4;

wsi->sock = socket(AF_INET, SOCK_STREAM, 0);

if (wsi->sock < 0) {
lwsl_warn("Unable to open socket\n");
goto oom4;
}

if (lws_set_socket_options(context, wsi->sock)) {
lwsl_err("Failed to set wsi socket options\n");
compatible_close(wsi->sock);
goto oom4;
}

wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_CONNECT;

insert_wsi_socket_into_fds(context, wsi);

libwebsocket_set_timeout(wsi,
PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE,
AWAITING_TIMEOUT);
}

server_addr.sin_family = AF_INET;
Expand All @@ -66,30 +83,39 @@ struct libwebsocket *__libwebsocket_client_connect_2(

if (connect(wsi->sock, (struct sockaddr *)&server_addr,
sizeof(struct sockaddr)) == -1) {
lwsl_debug("Connect failed\n");
compatible_close(wsi->sock);
goto oom4;
}

lwsl_client("connected\n");
if (errno == EALREADY || errno == EINPROGRESS) {
lwsl_client("nonblocking connect retry\n");

if (lws_set_socket_options(context, wsi->sock)) {
lwsl_err("Failed to set wsi socket options\n");
compatible_close(wsi->sock);
goto oom4;
/*
* must do specifically a POLLOUT poll to hear
* about the connect completion
*/

context->fds[wsi->position_in_fds_table].events |= POLLOUT;

/* external POLL support via protocol 0 */
context->protocols[0].callback(context, wsi,
LWS_CALLBACK_SET_MODE_POLL_FD,
wsi->user_space, (void *)(long)wsi->sock, POLLOUT);

return wsi;
}

lwsl_debug("Connect failed errno=%d\n", errno);
goto failed;
}

insert_wsi_socket_into_fds(context, wsi);
lwsl_client("connected\n");

/* we are connected to server, or proxy */

if (context->http_proxy_port) {

n = send(wsi->sock, context->service_buffer, plen, MSG_NOSIGNAL);
if (n < 0) {
compatible_close(wsi->sock);
lwsl_debug("ERROR writing to proxy socket\n");
goto oom4;
goto failed;
}

libwebsocket_set_timeout(wsi,
Expand Down Expand Up @@ -121,7 +147,7 @@ struct libwebsocket *__libwebsocket_client_connect_2(
n = libwebsocket_service_fd(context, &pfd);

if (n < 0)
goto oom4;
goto failed;

if (n) /* returns 1 on failure after closing wsi */
return NULL;
Expand All @@ -131,7 +157,11 @@ struct libwebsocket *__libwebsocket_client_connect_2(
oom4:
free(wsi->u.hdr.ah);
free(wsi);
return NULL;

failed:
libwebsocket_close_and_free_session(context, wsi,
LWS_CLOSE_STATUS_NOSTATUS);
return NULL;
}

Expand Down Expand Up @@ -185,6 +215,7 @@ libwebsocket_client_connect(struct libwebsocket_context *context,
goto bail;

memset(wsi, 0, sizeof(*wsi));
wsi->sock = -1;

/* -1 means just use latest supported */

Expand Down
16 changes: 16 additions & 0 deletions lib/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ int lws_client_socket_service(struct libwebsocket_context *context,

switch (wsi->mode) {

case LWS_CONNMODE_WS_CLIENT_WAITING_CONNECT:

/*
* we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE
* timeout protection set in client-handshake.c
*/

if (__libwebsocket_client_connect_2(context, wsi) == NULL) {
/* closed */
lwsl_client("closed\n");
return -1;
}

/* either still pending connection, or changed mode */
return 0;

case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY:

/* handle proxy hung up on us */
Expand Down
17 changes: 14 additions & 3 deletions lib/libwebsockets.c
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,17 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context,

wsi->u.ws.close_reason = reason;

if (wsi->mode == LWS_CONNMODE_WS_CLIENT_WAITING_CONNECT ||
wsi->mode == LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE) {

context->protocols[0].callback(context, wsi,
LWS_CALLBACK_CLIENT_CONNECTION_ERROR, NULL, NULL, 0);

free(wsi->u.hdr.ah);
goto just_kill_connection;
}


if (wsi->mode == LWS_CONNMODE_HTTP_SERVING_ACCEPTED && wsi->u.http.fd) {
lwsl_debug("closing http fd %d\n", wsi->u.http.fd);
close(wsi->u.http.fd);
Expand Down Expand Up @@ -904,12 +915,10 @@ libwebsocket_service_fd(struct libwebsocket_context *context,
return 0;

/* just here for timeout management? */

if (pollfd == NULL)
return 0;

/* no, here to service a socket descriptor */

wsi = context->lws_lookup[pollfd->fd];
if (wsi == NULL)
/* not lws connection ... leave revents alone and return */
Expand Down Expand Up @@ -1286,8 +1295,10 @@ libwebsocket_service(struct libwebsocket_context *context, int timeout_ms)
/* wait for something to need service */

n = poll(context->fds, context->fds_count, timeout_ms);
if (n == 0) /* poll timeout */
if (n == 0) /* poll timeout */ {
libwebsocket_service_fd(context, NULL);
return 0;
}

if (n < 0)
return -1;
Expand Down
1 change: 1 addition & 0 deletions lib/libwebsockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,7 @@ libwebsocket_context_user(struct libwebsocket_context *context);
enum pending_timeout {
NO_PENDING_TIMEOUT = 0,
PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE,
PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE,
PENDING_TIMEOUT_ESTABLISH_WITH_SERVER,
PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
PENDING_TIMEOUT_AWAITING_PING,
Expand Down
1 change: 1 addition & 0 deletions lib/private-libwebsockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ enum connection_mode {
LWS_CONNMODE_SSL_ACK_PENDING,

/* transient modes */
LWS_CONNMODE_WS_CLIENT_WAITING_CONNECT,
LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY,
LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE,
LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY,
Expand Down
61 changes: 37 additions & 24 deletions test-server/test-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ static int deny_mux;
static struct libwebsocket *wsi_mirror;
static int mirror_lifetime = 0;
static int force_exit = 0;
static int longlived = 0;

/*
* This demo shows how to connect multiple websockets simultaneously to a
Expand Down Expand Up @@ -73,6 +74,15 @@ callback_dumb_increment(struct libwebsocket_context *this,
{
switch (reason) {

case LWS_CALLBACK_CLIENT_ESTABLISHED:
fprintf(stderr, "callback_dumb_increment: LWS_CALLBACK_CLIENT_ESTABLISHED\n");
break;

case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
fprintf(stderr, "LWS_CALLBACK_CLIENT_CONNECTION_ERROR\n");
was_closed = 1;
break;

case LWS_CALLBACK_CLOSED:
fprintf(stderr, "LWS_CALLBACK_CLOSED\n");
was_closed = 1;
Expand Down Expand Up @@ -125,13 +135,25 @@ callback_lws_mirror(struct libwebsocket_context *context,

switch (reason) {

case LWS_CALLBACK_CLOSED:
fprintf(stderr, "mirror: LWS_CALLBACK_CLOSED mirror_lifetime=%d\n", mirror_lifetime);
wsi_mirror = NULL;
break;

case LWS_CALLBACK_CLIENT_ESTABLISHED:

fprintf(stderr, "callback_lws_mirror: LWS_CALLBACK_CLIENT_ESTABLISHED\n");

mirror_lifetime = 10 + (random() & 1023);
/* useful to test single connection stability */
if (longlived)
mirror_lifetime += 50000;

fprintf(stderr, "opened mirror connection with "
"%d lifetime\n", mirror_lifetime);

/*
* mirror_lifetime is decremented each send, when it reaches
* zero the connection is closed in the send callback.
* When the close callback comes, wsi_mirror is set to NULL
* so a new connection will be opened
*/

/*
* start the ball rolling,
* LWS_CALLBACK_CLIENT_WRITEABLE will come next service
Expand All @@ -140,6 +162,11 @@ callback_lws_mirror(struct libwebsocket_context *context,
libwebsocket_callback_on_writable(context, wsi);
break;

case LWS_CALLBACK_CLOSED:
fprintf(stderr, "mirror: LWS_CALLBACK_CLOSED mirror_lifetime=%d\n", mirror_lifetime);
wsi_mirror = NULL;
break;

case LWS_CALLBACK_CLIENT_RECEIVE:
/* fprintf(stderr, "rx %d '%s'\n", (int)len, (char *)in); */
break;
Expand Down Expand Up @@ -227,7 +254,6 @@ int main(int argc, char **argv)
const char *address;
struct libwebsocket *wsi_dumb;
int ietf_version = -1; /* latest */
int longlived = 0;
struct lws_context_creation_info info;

memset(&info, 0, sizeof info);
Expand Down Expand Up @@ -306,16 +332,18 @@ int main(int argc, char **argv)
protocols[PROTOCOL_DUMB_INCREMENT].name, ietf_version);

if (wsi_dumb == NULL) {
fprintf(stderr, "libwebsocket dumb connect failed\n");
fprintf(stderr, "libwebsocket connect failed\n");
ret = 1;
goto bail;
}

fprintf(stderr, "Websocket connections opened\n");
fprintf(stderr, "Waiting for connect...\n");

/*
* sit there servicing the websocket context to handle incoming
* packets, and drawing random circles on the mirror protocol websocket
* nothing happens until the client websocket connection is
* asynchronously established
*/

n = 0;
Expand All @@ -337,25 +365,10 @@ int main(int argc, char **argv)

if (wsi_mirror == NULL) {
fprintf(stderr, "libwebsocket "
"dumb connect failed\n");
"mirror connect failed\n");
ret = 1;
goto bail;
}

mirror_lifetime = 10 + (random() & 1023);
/* useful to test single connection stability */
if (longlived)
mirror_lifetime += 50000;

fprintf(stderr, "opened mirror connection with "
"%d lifetime\n", mirror_lifetime);

/*
* mirror_lifetime is decremented each send, when it reaches
* zero the connection is closed in the send callback.
* When the close callback comes, wsi_mirror is set to NULL
* so a new connection will be opened
*/
}

bail:
Expand Down

0 comments on commit 5dc62ea

Please sign in to comment.