diff --git a/README.coding b/README.coding index d26c21dbb3..a34211ec40 100644 --- a/README.coding +++ b/README.coding @@ -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. + diff --git a/changelog b/changelog index 21ecb0560e..31cc570c7c 100644 --- a/changelog +++ b/changelog @@ -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 ---------------- diff --git a/lib/client-handshake.c b/lib/client-handshake.c index 2dd810c561..a501565690 100644 --- a/lib/client-handshake.c +++ b/lib/client-handshake.c @@ -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; @@ -66,20 +83,30 @@ 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 */ @@ -87,9 +114,8 @@ struct libwebsocket *__libwebsocket_client_connect_2( 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, @@ -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; @@ -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; } @@ -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 */ diff --git a/lib/client.c b/lib/client.c index 96ae2bd3cf..4f806275e4 100644 --- a/lib/client.c +++ b/lib/client.c @@ -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 */ diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index e2254e05f2..ea8a8ff3b7 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -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); @@ -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 */ @@ -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; diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index b3cfa154f9..2d62107f60 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -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, diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 11f9204316..7c39183a34 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -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, diff --git a/test-server/test-client.c b/test-server/test-client.c index 7b32cb1f67..949d398815 100644 --- a/test-server/test-client.c +++ b/test-server/test-client.c @@ -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 @@ -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; @@ -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 @@ -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; @@ -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); @@ -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; @@ -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: