[ccan] ccan: the psuedo "networking" module.

Rusty Russell rusty at rustcorp.com.au
Tue Dec 4 13:14:30 EST 2012


Allan Ference <f.fallen45 at gmail.com> writes:
> Hey Rusty,
>
> Good day, I've done the changes needed and pushed to branch named `net'
> which you can view on my fork here:
> https://github.com/allanference/ccan/tree/net

Hi Allan,

        Please CC the list in future.

I looked at the net changes, and initially I thought it was a bad idea
to rename net_client_lookup to net_lookup.  But I thought harder, and I
now agree (we should probably add a net_wildcard() function if we want a
convenient way of getting a v4/v6 address to bind to).  Similarly with
exporting set_nonblock, though it seems that your version handling
windows would be better?

Your interface to socket is fairly incoherent, however.  As I'm sure you
know by now writing a socket library is *hard*.  If I were you, I'd
think what interface you'd want to use, and then implement that.

For example, if you're trying to write a server, I've always wanted an
interface like:

        /* Listeners create connections. */
        struct listener;

        /* One connection per client. */
        struct conn;

        /* Create a new listener; fn gets called when it gets a connection */
        bool new_listener(const char *service,
                          bool (*fn)(struct conn *, void *arg), void *arg);

        /* To create a connection. */
        bool new_conn(const char *node, const char *service,
                      bool (*fn)(struct conn *, void *arg), void *arg);

        /* In case you want to create a connection manually. */
        struct conn *new_conn_fd(int fd,
                            bool (*fn)(struct conn *, void *arg),
                            void *arg);

        /* Queue some data to be written.  Fails on OOM or closed fd. */
        bool conn_write(struct conn *conn, const void *data, size_t len);

        /* Queue a request to read into a buffer.  Fails on OOM or closed fd. */
        bool conn_read(struct conn *conn, void *data, size_t len);

        /* Queue a partial request to read into a buffer. */
        bool conn_read_partial(struct conn *conn, void *data, size_t *len);

        /* Queue a partial write request. */
        bool conn_write_partial(struct conn *conn, const void *data, size_t *len);

        /* Your function must return this value. */
        bool conn_next(struct conn *conn,
                       bool (*next)(struct conn *, void *arg), void *arg);

        /* Useful next functions. */
        /* Close the connection, we're done. */
        void next_close(struct conn *, void *arg));

        /* Exit the loop, returning this (non-NULL) arg. */
        void next_break(struct conn *, void *arg);

        /* This is the main loop. */
        void *conn_loop(void);

Now to use this would be really nice, eg:
        #define BUFFER_SIZE 1024

        struct buf {
                char bytes[BUFFER_SIZE];
                size_t used;
        };

        static bool echo_write(struct conn *conn, struct buf *buf)
        {
                if (!conn_write(conn, buf->bytes, buf->used)) {
                        free(buf);
                        return false;
                }
                return conn_next(conn, echo_read, buf);
        }

        static bool echo_read(struct conn *conn, struct buf *buf)
        {
                buf->used = BUFFER_SIZE;
                if (!conn_read_partial(conn, buf->bytes, &buf->used)) {
                        free(buf);
                        return false;
                }
                return conn_next(conn, echo_write, buf);
        }

        static bool echo_start(struct conn *conn, void *unused)
        {
                struct buf *buf = malloc(sizeof(buf));
                if (!buf)
                        return false;

                return conn_next(conn, buf, echo_read, buf);
        }
        
        int main(int argc, char *argv[])
        {
                if (!new_listener(argv[1], echo_start, NULL))
                        err(1, "Creating listener");

                conn_loop();
        }

The implementation can be done completely without threads, yet be fully
async.  Or another variant could use a thread per connection, so the
only user-visible synchronization would be for anything shared between
threads.

The best thing is that you could force the implementation into a
debugging sychronous mode, where conn_read_* and conn_write_* worked
synchonously, and conn_next immediately called the next function.  This
gives you a nice call-chain to see exactly what occurred.

Of course, the names might need work, and the callbacks should be made
typesafe using ccan/typesafe_cb, but I'd love to use such a module!

Cheers,
Rusty.


More information about the ccan mailing list