src/Network/Socket.hh
author Tero Marttila <terom@fixme.fi>
Thu, 22 Jan 2009 02:38:33 +0200
branchnew_graphics
changeset 418 194bc810a570
parent 400 d64bf28c4340
permissions -rw-r--r--
add --log-level option, improve Config/Logger documentation, fix NETWORK_EANBLED typos in Engine
#ifndef NETWORK_SOCKET_HH
#define NETWORK_SOCKET_HH

/**
 * @file
 *
 * Network sockets
 */

// forward-declare
class NetworkSocket;

#include "../Error.hh"
#include "Platform.hh"
#include "Address.hh"
#include "Reactor.hh"

#include <ClanLib/signals.h>

/**
 * This is a socket class that wraps an OS socket filedescriptor and provides the more important socket operations
 * as methods. The implementation aims to be address-family agnostic, and should thence work with both IPv6 and IPv4.
 *
 * Network addresses are abstracted into the NetworkEndpoint and derived NetworkAddress classes. The bind() and
 * connect() methods accept a NetworkEndpoint as an argument, which allows them to handle hosts with multiple
 * addresses (such as all dual-stack IPv6/IPv4 hosts). Other methods such as accept(), recv() and send() require a
 * NetworkAddress, which encodes a single specific address. Note how these are also useable as arguments to
 * connect()/bind().
 *
 * The constructor accepts family/socktype/protocol arguments (as passed to the socket() syscall) which can be used to
 * limit which kinds of addresses/sockets will be used. Usually, family can be specified as AF_UNSPEC and socktype as
 * either SOCK_STREAM or SOCK_DGRAM - this will let bind()/connect() pick the best IPv6/IPv4 address for use.
 *
 * Note however that a call to bind()/connect() can result to multiple calls to the socket() syscall - this interaction
 * is slightly complicated. On a succesfull bind() operation, the resulting socket will be "locked down", meaning that
 * a later connect() operation will use the same local socket - this restricts the remote address families that are valid.
 *
 * The behaviour of send/recv differs from the behaviour of the similarly named syscalls. On failure,
 * NetworkSocketErrno is thrown, and on EOF, NetworkSocketEOFError. Zero is returned if the syscall returns EAGAIN - in
 * other words - if a socket is nonblocking and the operation would block. Otherwise, the return value is the same as
 * for the syscalls - the number of bytes send/received.
 *
 * NetworkSockets also support polling for non-blocking operations using NetworkReactor. A socket is associated with a
 * specific reactor (passed to the constructor, defaults to NetworkReactor::current). This is inherited by accept()'d
 * sockets. For read/write, NetworkSocket provides a sig_read()/sig_write() signal which will be fired if a socket is
 * registered using set_poll_read(true)/set_poll_write(true) and the NetworkReactor::poll is run. The internal logic
 * does not manipulate the poll states. Usually, sig_read will always be enabled (except to throttle incoming traffic),
 * and sig_write should be enabled after send() returns zero (XXX: provide an automatic mechanism for this?).
 */
class NetworkSocket {
    private:
        /**
         * Socket family/type/protocol
         */
        struct socket_type {
            /** Socket domain */
            int family;

            /** Socket type */
            int socktype;

            /** Socket protocol */
            int protocol;
            
            /** Simple constructor */
            socket_type (int family = 0, int socktype = 0, int protocol = 0) : family(family), socktype(socktype), protocol(protocol) { }
        };

        /** These are nonzero if given via the constructor, used to filter out unwanted addrinfos */
        socket_type sock_type;

        /** The file descriptor */
        int fd;

        /** Our current type as used for fd, intialized via constructor, but updated by lazy_socket */
        socket_type type;

        /** 
         * Has the socket been explicitly bind()'d? If so, force ourselves to use this socket in connect().
         */
        bool bound : 1;

        /**
         * Registered to reactor?
         */
        bool registered : 1;

        /**
         * Do we want to know about recv()s?
         */
        bool want_read : 1;

        /**
         * Is the write buffer full?
         */
        bool want_write : 1;

        /**
         * The reactor that we use, defaults to NetworkReactor::current
         */
        NetworkReactor *reactor;

        /**
         * Read/write signals
         */
        CL_Signal_v0 _sig_read, _sig_write;

    public:
        /**
         * Construct a socket of the specific type. Family and protocol can be left as NULL, but type should usually
         * be specified. The given reactor is used for polling, defaults to NetworkReactor::current
         */
        NetworkSocket (int family, int socktype, int protocol = 0, NetworkReactor *reactor = NULL);
        
        /**
         * Create a socket from the given pre-existing fd
         */
        NetworkSocket (int fd, socket_type type, NetworkReactor *reactor = NULL);

        /**
         * Force-close the socket if it's still open
         */
        ~NetworkSocket (void);

    private:
        // nocopy
        NetworkSocket (const NetworkSocket &copy);
        NetworkSocket &operator= (const NetworkSocket &copy);
        
        /**
         * Reset bound+poll
         */ 
        void reset (void);

        /**
         * Create a new socket of the given type, unless we already have one
         */
        void lazy_socket (int family, int type, int protocol);
        
        /**
         * Close and reset, ignoring errors
         */
        void force_close (void);

    public:
        /**
         * Get the socket fd... promise not to break it
         */
        int get_socket (void) const { return fd; }

        /**
         * Bind to a local endpoint. This can be specified in hostname form, and a suitable socket will be chosen.
         */ 
        void bind (const NetworkEndpoint &addr);

        /**
         * Put socket into listen mode for accept()
         */
        void listen (int backlog);

        /**
         * Get local address
         *
         * Note that this may block on a reverse DNS lookup.
         */
        NetworkAddress get_local_address (void); 

        /**
         * Get remote address
         *
         * Note that this may block on a reverse DNS lookup.
         */
        NetworkAddress get_remote_address (void); 

        /**
         * Make send/recv non-blocking. The current connect() implementation does not support use of non-blocking
         * connects.
         */
        void set_nonblocking (bool nonblocking);

        /**
         * Accept an incoming connection on a listen() socket as a new socket, optionally storing the connection's
         * source address.
         *
         * Note that this may block on a reverse DNS lookup if \a src is given.
         */
        NetworkSocket* accept (NetworkAddress *src);

        /**
         * Connect this socket to a remote endpoint, going through the resolved addresses until we find one that works.
         *
         * This is currently implemented in an entirely blocking fashion, DNS lookups and connect() included.
         */
        void connect (const NetworkEndpoint &addr);

        /**
         * Send, optionally using the specific destination
         *
         * @param buf bytes to send
         * @param size how many bytes to try and send
         * @param dest optional specific destination address
         * @return number of bytes sent, zero if busy
         * @throw NetworkSocketError on error
         */
        size_t send (const char *buf, size_t size, const NetworkAddress *dest = NULL);

        /**
         * Recv, optionally storing the source in src
         *
         * @param buf where to recv into
         * @param size how many bytes to try and receive
         * @param src optionally store source address
         * @return number of bytes received, zero if none available
         * @throw NetworkSocketEOFError if the connection was closed
         * @throw NetworkSocketError on error
         */
        size_t recv (char *buf, size_t size, NetworkAddress *src = NULL);

        /**
         * Close and reset the socket
         */
        void close (void);

        /**
         * Triggered when socket becomes readable
         */
        CL_Signal_v0& sig_read (void) { return _sig_read; }

        /**
         * Triggered when socket becomes writeable after a send that returned zero
         */
        CL_Signal_v0& sig_write (void) { return _sig_write; }

        /**
         * Register to NetworkReactor unless already registered
         */
        void register_poll (void);

        /**
         * Trigger sig_read() once socket is ready for recv()
         */
        void set_poll_read (bool want_read) { this->want_read = want_read; if (!registered) register_poll(); }

        /**
         * Trigger sig_write() once socket is ready for send()
         */
        void set_poll_write (bool want_write) { this->want_write = want_write; if (!registered) register_poll(); }

        /**
         * What events this socket is interested in (called by NetworkReactor)
         */
        NetworkPollMask get_poll (void) { 
            return (want_read ? POLL_READ : 0) | (want_write ? POLL_WRITE : 0); 
        }

        /**
         * Notify of events (called by NetworkReactor)
         */
        void notify (NetworkPollMask mask) { 
            if (mask & POLL_READ) _sig_read();
            if (mask & POLL_WRITE) _sig_write();
        }
};

/**
 * Base class for expcetions thrown by socket methods
 */
class NetworkSocketError : public Error {
    protected:
        static std::string build_str (const NetworkSocket &socket, const char *op, const char *err);
    
    public:
        NetworkSocketError (const NetworkSocket &socket, const char *op, const char *err);
};

/**
 * Errno-enabled exception, most common type of NetworkSocketError
 */
class NetworkSocketErrno : public NetworkSocketError {
    public:
        NetworkSocketErrno (const NetworkSocket &socket, const char *op);
};

/**
 * Recv returned EOF
 */
class NetworkSocketEOFError : public NetworkSocketError {
    public:
        NetworkSocketEOFError (const NetworkSocket &socket, const char *op) :
            NetworkSocketError(socket, op, "EOF") { }
};

#endif /* NETWORK_SOCKET_HH */