implement tcp_server, along with new error codes
authorTero Marttila <terom@fixme.fi>
Thu, 07 May 2009 02:17:29 +0300
changeset 178 3d357d055d67
parent 177 a74b55104fb9
child 179 0eaa5c1b926d
implement tcp_server, along with new error codes
src/error.c
src/error.h
src/tcp_server.c
--- a/src/error.c	Thu May 07 02:17:20 2009 +0300
+++ b/src/error.c	Thu May 07 02:17:29 2009 +0300
@@ -33,6 +33,9 @@
     {   ERR_CLOSE,                          "close",                                    ERR_EXTRA_ERRNO     },
     {   ERR_GETSOCKOPT,                     "getsockopt",                               ERR_EXTRA_ERRNO     },
     {   ERR_OPEN,                           "open",                                     ERR_EXTRA_ERRNO     },
+    {   ERR_ACCEPT,                         "accept",                                   ERR_EXTRA_ERRNO     },
+    {   ERR_BIND,                           "bind",                                     ERR_EXTRA_ERRNO     },
+    {   ERR_LISTEN,                         "listen",                                   ERR_EXTRA_ERRNO     },
     {   _ERR_INVALID,                       NULL,                                       0                   }
 
 }, _sock_gnutls_error_desc[] = {
--- a/src/error.h	Thu May 07 02:17:20 2009 +0300
+++ b/src/error.h	Thu May 07 02:17:29 2009 +0300
@@ -63,6 +63,9 @@
     ERR_CLOSE,          ///< close(2) failed, some written data was probably not sent
     ERR_GETSOCKOPT,     ///< getsockopt(2) failed
     ERR_OPEN,           ///< open(2) failed
+    ERR_ACCEPT,         ///< accept(2) failed
+    ERR_BIND,           ///< bind(2) failed
+    ERR_LISTEN,         ///< listen(2) failed
 
     /** @see sock_gnutls_error_code */
     _ERR_GNUTLS     = 0x000400,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tcp_server.c	Thu May 07 02:17:29 2009 +0300
@@ -0,0 +1,252 @@
+#include "tcp_internal.h"
+#include "sock_internal.h"
+#include "log.h"
+
+#include <unistd.h>
+
+/*
+ * Service methods
+ */
+void tcp_server__deinit (service_t *service)
+{
+    struct tcp_server *serv = service_check(service, &tcp_server_type);
+
+    tcp_server_deinit(serv);
+}
+
+/*
+ * Service type
+ */
+const struct service_type tcp_server_type = {
+    .base_type = {
+        .parent = NULL,
+    },
+    .methods = {
+        .deinit = tcp_server__deinit,
+    },
+};
+
+/*
+ * We got a new client, build a transport for it and give it to the user
+ */
+static err_t tcp_server_client (struct tcp_server *serv, evutil_socket_t sock, error_t *err)
+{
+    struct tcp_transport *trans;
+    
+    // create a new transport for it, this also makes it nonblocking
+    if (tcp_transport_create(&trans, &serv->base_service.info.trans_info, sock, err))
+        goto error;
+
+    // make it connected
+    // this will call transport_callbacks::on_connect, which is all the user needs
+    if (tcp_transport_connected(trans, err))
+        goto error;
+
+    // ok
+    return SUCCESS;
+
+error:
+    // cleanup
+    if (trans)
+        tcp_transport_destroy(trans);
+
+    return ERROR_CODE(err);    
+}
+
+/*
+ * Libevent callback
+ */
+static void tcp_server_on_accept (evutil_socket_t sock, short what, void *arg)
+{
+    struct tcp_server *serv = arg;
+    evutil_socket_t client_sock;
+    error_t err;
+
+    (void) what;
+
+    // accept as a new client connection
+    if ((client_sock = accept(sock, NULL, NULL)) < 0 && errno != EAGAIN)
+        JUMP_SET_ERROR_ERRNO(&err, ERR_ACCEPT);
+
+    // spurious read event?
+    if (client_sock < 0)
+        return;
+
+    // handle it
+    if (tcp_server_client(serv, client_sock, &err))
+        goto error;
+
+    // ok
+    return;
+
+error:
+    if (client_sock >= 0)
+        EVUTIL_CLOSESOCKET(client_sock);
+
+    // faaail
+    service_error(&serv->base_service, &err);
+}
+
+/*
+ * Attempts to construct a listen()'d socket with the given addr, and return it
+ *
+ * @param addr the addrinfo to try and create a socket for
+ * @param err returned error info
+ * @return listening socket, or -err_t on error
+ */
+static int tcp_server_sock_addr (struct addrinfo *addr, error_t *err)
+{
+    evutil_socket_t sock;
+
+    // create the sock
+    if ((sock = tcp_sock_create(addr, err)))
+        goto error;
+
+    // bind it
+    if (bind(sock, addr->ai_addr, addr->ai_addrlen) < 0)
+        JUMP_SET_ERROR_ERRNO(err, ERR_BIND);
+
+    // listen
+    if (listen(sock, TCP_SERVER_BACKLOG) < 0)
+        JUMP_SET_ERROR_ERRNO(err, ERR_LISTEN);
+    
+    // ok, valid socket
+    return sock;
+
+error:
+    if (sock >= 0)
+        // cleanup
+        EVUTIL_CLOSESOCKET(sock);
+    
+    return -ERROR_CODE(err);
+}
+
+/*
+ * Construct a listen()'d socket with the given resolver result, and return it.
+ *
+ * @param rr the resolver lookup result to create a socket for
+ * @param err returned error info
+ * @return listening socket, or -err_t on error
+ */
+static int tcp_server_sock (struct resolve_result *rr, error_t *err)
+{
+    struct addrinfo *addr;
+    evutil_socket_t sock;
+
+    // try each addrinfo
+    while ((addr = resolve_result_next(rr))) {
+        // attempt to construct given socket
+        if ((sock = tcp_server_sock_addr(addr, err)) < 0)
+            // log an informative error warning
+            log_warn_error(err, "%s", resolve_addr_text(addr)); 
+
+        else
+            // got a valid socket 
+            break;
+    }
+    
+    if (sock >= 0)
+        // valid socket
+        return sock;
+
+    else
+        // some error occured
+        return -ERROR_CODE(err);
+}
+
+err_t tcp_server_listen (struct tcp_server *serv, const char *interface, const char *service, error_t *err)
+{
+    struct resolve_result rr;
+    evutil_socket_t sock;
+
+    // get the global event_base
+    struct event_base *ev_base = _sock_stream_ctx.ev_base;
+
+    // init the resolver
+    resolve_result_init(&rr);
+
+    // resolve the interface/service
+    if (resolve_addr(&rr, interface, service, SOCK_STREAM, AI_PASSIVE, err))
+        return ERROR_CODE(err);
+    
+    // create the socket
+    if ((sock = tcp_server_sock(&rr, err)) < 0)
+        goto error;
+
+    // make it nonblocking
+    if (evutil_make_socket_nonblocking(sock))
+        JUMP_SET_ERROR_STR(err, ERR_MISC, "evutil_make_socket_nonblocking");
+    
+    // construct event for the sock
+    if ((serv->ev = event_new(ev_base, sock, EV_READ | EV_PERSIST, tcp_server_on_accept, serv)) == NULL)
+        JUMP_SET_ERROR(err, ERR_EVENT_NEW);
+
+    // add it
+    if (event_add(serv->ev, NULL))
+        JUMP_SET_ERROR(err, ERR_EVENT_ADD);
+
+    // ok
+    return SUCCESS;
+
+error:
+    if (sock >= 0 && !serv->ev)
+        // need to close socket ourselves, because we couldn't register our event for it
+        EVUTIL_CLOSESOCKET(sock);
+    
+    // general cleanup
+    tcp_server_deinit(serv);
+    
+    return ERROR_CODE(err);
+}
+
+void tcp_server_deinit (struct tcp_server *serv)
+{
+    if (serv->ev) {
+        // ignore errors
+        event_del(serv->ev);
+
+        // ignore errors
+        close(event_get_fd(serv->ev));
+        
+        // release event
+        event_free(serv->ev);
+        
+        // invalidate
+        serv->ev = NULL;
+    }
+}
+
+static void tcp_server_destroy (struct tcp_server *serv)
+{
+    tcp_server_deinit(serv);
+
+    free(serv);
+}
+
+/*
+ * Public interface
+ */
+err_t tcp_listen (const struct service_info *info, service_t **service_ptr, 
+        const char *interface, const char *service, error_t *err)
+{
+    struct tcp_server *serv;
+
+    // alloc
+    if ((serv = calloc(1, sizeof(*serv))) == NULL)
+        return SET_ERROR(err, ERR_MEM);
+
+    // init service
+    service_init(&serv->base_service, &tcp_server_type, info);
+
+    // init ourselves
+    if (tcp_server_listen(serv, interface, service, err))
+        goto error;
+
+    // ok
+    *service_ptr = &serv->base_service;
+
+error:
+    tcp_server_destroy(serv);
+
+    return ERROR_CODE(err);
+}