terom@185: #ifndef NETWORK_SOCKET_HH terom@185: #define NETWORK_SOCKET_HH terom@185: terom@399: /** terom@399: * @file terom@399: * terom@399: * Network sockets terom@399: */ terom@399: terom@380: // forward-declare terom@380: class NetworkSocket; terom@380: terom@186: #include "../Error.hh" terom@399: #include "Platform.hh" terom@378: #include "Address.hh" terom@380: #include "Reactor.hh" terom@185: terom@418: #include terom@418: terom@284: /** terom@399: * This is a socket class that wraps an OS socket filedescriptor and provides the more important socket operations terom@399: * as methods. The implementation aims to be address-family agnostic, and should thence work with both IPv6 and IPv4. terom@399: * terom@399: * Network addresses are abstracted into the NetworkEndpoint and derived NetworkAddress classes. The bind() and terom@399: * connect() methods accept a NetworkEndpoint as an argument, which allows them to handle hosts with multiple terom@399: * addresses (such as all dual-stack IPv6/IPv4 hosts). Other methods such as accept(), recv() and send() require a terom@399: * NetworkAddress, which encodes a single specific address. Note how these are also useable as arguments to terom@399: * connect()/bind(). terom@399: * terom@399: * The constructor accepts family/socktype/protocol arguments (as passed to the socket() syscall) which can be used to terom@399: * limit which kinds of addresses/sockets will be used. Usually, family can be specified as AF_UNSPEC and socktype as terom@399: * either SOCK_STREAM or SOCK_DGRAM - this will let bind()/connect() pick the best IPv6/IPv4 address for use. terom@399: * terom@399: * Note however that a call to bind()/connect() can result to multiple calls to the socket() syscall - this interaction terom@399: * is slightly complicated. On a succesfull bind() operation, the resulting socket will be "locked down", meaning that terom@399: * a later connect() operation will use the same local socket - this restricts the remote address families that are valid. terom@399: * terom@399: * The behaviour of send/recv differs from the behaviour of the similarly named syscalls. On failure, terom@399: * NetworkSocketErrno is thrown, and on EOF, NetworkSocketEOFError. Zero is returned if the syscall returns EAGAIN - in terom@399: * other words - if a socket is nonblocking and the operation would block. Otherwise, the return value is the same as terom@399: * for the syscalls - the number of bytes send/received. terom@399: * terom@399: * NetworkSockets also support polling for non-blocking operations using NetworkReactor. A socket is associated with a terom@399: * specific reactor (passed to the constructor, defaults to NetworkReactor::current). This is inherited by accept()'d terom@399: * sockets. For read/write, NetworkSocket provides a sig_read()/sig_write() signal which will be fired if a socket is terom@399: * registered using set_poll_read(true)/set_poll_write(true) and the NetworkReactor::poll is run. The internal logic terom@399: * does not manipulate the poll states. Usually, sig_read will always be enabled (except to throttle incoming traffic), terom@399: * and sig_write should be enabled after send() returns zero (XXX: provide an automatic mechanism for this?). terom@284: */ terom@378: class NetworkSocket { terom@378: private: terom@380: /** terom@380: * Socket family/type/protocol terom@380: */ terom@380: struct socket_type { terom@380: /** Socket domain */ terom@380: int family; terom@380: terom@380: /** Socket type */ terom@380: int socktype; terom@380: terom@380: /** Socket protocol */ terom@380: int protocol; terom@380: terom@380: /** Simple constructor */ terom@380: socket_type (int family = 0, int socktype = 0, int protocol = 0) : family(family), socktype(socktype), protocol(protocol) { } terom@380: }; terom@380: terom@380: /** These are nonzero if given via the constructor, used to filter out unwanted addrinfos */ terom@380: socket_type sock_type; terom@380: terom@378: /** The file descriptor */ terom@378: int fd; terom@378: terom@399: /** Our current type as used for fd, intialized via constructor, but updated by lazy_socket */ terom@380: socket_type type; terom@378: terom@378: /** terom@378: * Has the socket been explicitly bind()'d? If so, force ourselves to use this socket in connect(). terom@378: */ terom@380: bool bound : 1; terom@380: terom@380: /** terom@380: * Registered to reactor? terom@380: */ terom@380: bool registered : 1; terom@380: terom@380: /** terom@380: * Do we want to know about recv()s? terom@380: */ terom@380: bool want_read : 1; terom@380: terom@380: /** terom@380: * Is the write buffer full? terom@380: */ terom@380: bool want_write : 1; terom@380: terom@380: /** terom@380: * The reactor that we use, defaults to NetworkReactor::current terom@380: */ terom@380: NetworkReactor *reactor; terom@378: terom@378: /** terom@378: * Read/write signals terom@378: */ terom@378: CL_Signal_v0 _sig_read, _sig_write; terom@378: terom@378: public: terom@378: /** terom@378: * Construct a socket of the specific type. Family and protocol can be left as NULL, but type should usually terom@380: * be specified. The given reactor is used for polling, defaults to NetworkReactor::current terom@378: */ terom@380: NetworkSocket (int family, int socktype, int protocol = 0, NetworkReactor *reactor = NULL); terom@378: terom@378: /** terom@378: * Create a socket from the given pre-existing fd terom@378: */ terom@380: NetworkSocket (int fd, socket_type type, NetworkReactor *reactor = NULL); terom@378: terom@378: /** terom@378: * Force-close the socket if it's still open terom@378: */ terom@378: ~NetworkSocket (void); terom@378: terom@378: private: terom@399: // nocopy terom@399: NetworkSocket (const NetworkSocket ©); terom@399: NetworkSocket &operator= (const NetworkSocket ©); terom@378: terom@378: /** terom@380: * Reset bound+poll terom@380: */ terom@380: void reset (void); terom@380: terom@380: /** terom@378: * Create a new socket of the given type, unless we already have one terom@378: */ terom@378: void lazy_socket (int family, int type, int protocol); terom@378: terom@378: /** terom@380: * Close and reset, ignoring errors terom@378: */ terom@378: void force_close (void); terom@378: terom@378: public: terom@378: /** terom@378: * Get the socket fd... promise not to break it terom@378: */ terom@378: int get_socket (void) const { return fd; } terom@378: terom@378: /** terom@400: * Bind to a local endpoint. This can be specified in hostname form, and a suitable socket will be chosen. terom@378: */ terom@381: void bind (const NetworkEndpoint &addr); terom@378: terom@378: /** terom@400: * Put socket into listen mode for accept() terom@378: */ terom@378: void listen (int backlog); terom@378: terom@378: /** terom@378: * Get local address terom@400: * terom@400: * Note that this may block on a reverse DNS lookup. terom@378: */ terom@378: NetworkAddress get_local_address (void); terom@378: terom@378: /** terom@378: * Get remote address terom@400: * terom@400: * Note that this may block on a reverse DNS lookup. terom@378: */ terom@378: NetworkAddress get_remote_address (void); terom@378: terom@378: /** terom@400: * Make send/recv non-blocking. The current connect() implementation does not support use of non-blocking terom@400: * connects. terom@378: */ terom@378: void set_nonblocking (bool nonblocking); terom@378: terom@378: /** terom@400: * Accept an incoming connection on a listen() socket as a new socket, optionally storing the connection's terom@400: * source address. terom@400: * terom@400: * Note that this may block on a reverse DNS lookup if \a src is given. terom@378: */ terom@378: NetworkSocket* accept (NetworkAddress *src); terom@378: terom@378: /** terom@400: * Connect this socket to a remote endpoint, going through the resolved addresses until we find one that works. terom@400: * terom@400: * This is currently implemented in an entirely blocking fashion, DNS lookups and connect() included. terom@378: */ terom@381: void connect (const NetworkEndpoint &addr); terom@378: terom@378: /** terom@378: * Send, optionally using the specific destination terom@378: * terom@381: * @param buf bytes to send terom@381: * @param size how many bytes to try and send terom@381: * @param dest optional specific destination address terom@378: * @return number of bytes sent, zero if busy terom@378: * @throw NetworkSocketError on error terom@378: */ terom@378: size_t send (const char *buf, size_t size, const NetworkAddress *dest = NULL); terom@378: terom@378: /** terom@378: * Recv, optionally storing the source in src terom@378: * terom@381: * @param buf where to recv into terom@381: * @param size how many bytes to try and receive terom@381: * @param src optionally store source address terom@378: * @return number of bytes received, zero if none available terom@378: * @throw NetworkSocketEOFError if the connection was closed terom@378: * @throw NetworkSocketError on error terom@378: */ terom@378: size_t recv (char *buf, size_t size, NetworkAddress *src = NULL); terom@378: terom@378: /** terom@380: * Close and reset the socket terom@378: */ terom@378: void close (void); terom@378: terom@378: /** terom@378: * Triggered when socket becomes readable terom@378: */ terom@378: CL_Signal_v0& sig_read (void) { return _sig_read; } terom@378: terom@378: /** terom@378: * Triggered when socket becomes writeable after a send that returned zero terom@378: */ terom@378: CL_Signal_v0& sig_write (void) { return _sig_write; } terom@380: terom@380: /** terom@380: * Register to NetworkReactor unless already registered terom@380: */ terom@380: void register_poll (void); terom@380: terom@380: /** terom@400: * Trigger sig_read() once socket is ready for recv() terom@380: */ terom@380: void set_poll_read (bool want_read) { this->want_read = want_read; if (!registered) register_poll(); } terom@380: terom@380: /** terom@400: * Trigger sig_write() once socket is ready for send() terom@380: */ terom@380: void set_poll_write (bool want_write) { this->want_write = want_write; if (!registered) register_poll(); } terom@380: terom@380: /** terom@400: * What events this socket is interested in (called by NetworkReactor) terom@380: */ terom@380: NetworkPollMask get_poll (void) { terom@380: return (want_read ? POLL_READ : 0) | (want_write ? POLL_WRITE : 0); terom@380: } terom@380: terom@380: /** terom@400: * Notify of events (called by NetworkReactor) terom@380: */ terom@380: void notify (NetworkPollMask mask) { terom@380: if (mask & POLL_READ) _sig_read(); terom@380: if (mask & POLL_WRITE) _sig_write(); terom@380: } terom@378: }; terom@185: terom@284: /** terom@284: * Base class for expcetions thrown by socket methods terom@284: */ terom@185: class NetworkSocketError : public Error { terom@185: protected: terom@378: static std::string build_str (const NetworkSocket &socket, const char *op, const char *err); terom@378: terom@378: public: terom@185: NetworkSocketError (const NetworkSocket &socket, const char *op, const char *err); terom@185: }; terom@185: terom@284: /** terom@284: * Errno-enabled exception, most common type of NetworkSocketError terom@284: */ terom@380: class NetworkSocketErrno : public NetworkSocketError { terom@185: public: terom@380: NetworkSocketErrno (const NetworkSocket &socket, const char *op); terom@185: }; terom@185: terom@284: /** terom@284: * Recv returned EOF terom@284: */ terom@185: class NetworkSocketEOFError : public NetworkSocketError { terom@185: public: terom@185: NetworkSocketEOFError (const NetworkSocket &socket, const char *op) : terom@185: NetworkSocketError(socket, op, "EOF") { } terom@185: }; terom@185: terom@185: #endif /* NETWORK_SOCKET_HH */