#ifndef _SocketBase_H #define _SocketBase_H //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- #include "lib/posix/posix_sock.h" #include "ps/Pyrogenesis.h" #include #include "ps/CStr.h" //------------------------------------------------- // Error Codes //------------------------------------------------- DECLARE_ERROR( CONNECT_TIMEOUT ); DECLARE_ERROR( CONNECT_REFUSED ); DECLARE_ERROR( NO_SUCH_HOST ); DECLARE_ERROR( NO_ROUTE_TO_HOST ); DECLARE_ERROR( CONNECTION_BROKEN ); DECLARE_ERROR( WAIT_ABORTED ); DECLARE_ERROR( PORT_IN_USE ); DECLARE_ERROR( INVALID_PORT ); DECLARE_ERROR( WAIT_LOOP_FAIL ); DECLARE_ERROR( CONNECT_IN_PROGRESS ); DECLARE_ERROR( INVALID_PROTOCOL ); //------------------------------------------------- // Declarations //------------------------------------------------- class CSocketInternal; /** * An enumeration of all supported protocols, and the special value UNSPEC, * which represents an invalid address. */ // Modifiers Note: Each value in the enum should correspond to a sockaddr_* // struct and a PF_* value enum ESocketProtocol { // This should be a value that's invalid for most socket functions, so that // you don't accidentally use an UNSPEC SocketAddress // PF_UNSPEC does not work, since it is accepted by many implementations as // a "default" protocol family - whatever that may be UNSPEC=((sa_family_t)-1), IPv4=PF_INET, IPv6=PF_INET6, /* More protocols */ }; /** * A protocol-independent representation of a socket address. All protocols * in the ESocketProtocol enum should have a corresponding member in this union. */ // Modifiers Note: Each member must contain a first field, compatible with the // sin_family field of sockaddr_in. The field contains the ESocketProtocol value // for the address, and it is returned by GetProtocol() struct CSocketAddress { union { sa_family_t m_Family; sockaddr_in m_IPv4; sockaddr_in6 m_IPv6; } m_Union; inline ESocketProtocol GetProtocol() const { return (ESocketProtocol)m_Union.m_Family; } inline CSocketAddress() { memset(&m_Union, 0, sizeof(m_Union)); m_Union.m_Family=UNSPEC; } /** * Create a wildcard address for the specified protocol with a specified * port. * * @param port The port number, in local byte order * @param proto The protocol to use; default IPv4 */ explicit CSocketAddress(int port, ESocketProtocol proto=IPv4); /** * Create an address from a numerical IPv4 address and port, port in local * byte order, IPv4 address as a byte array in written order. The Protocol * of the resulting SocketAddress will be IPv4 * * @param address An IPv4 address as a byte array (in written order) * @param port A port number (0-65535) in local byte order. */ CSocketAddress(u8 address[4], int port); /** * Resolve the name using the system name resolution service (i.e. DNS) and * store the resulting address. When multiple addresses are found, the * first result is returned. * * Note that this call will block until the name resolution attempt is * either completed successfully or timed out. * * @param name The name to resolve * @param addr A reference to the variable to hold the address * * @return The result of the operation * @retval PS_OK The hostname was successfully retrieved * @retval NO_SUCH_HOST The hostname was not found */ static PS_RESULT Resolve(const char *name, int port, CSocketAddress &addr); /** * Returns the string representation of the address, i.e. the IP (v4 or v6) * address. Note that the port is not included in the string (mostly due to * the fact that the port representation differs wildly between address * families, and that Resolve does not take port as part of the hostname) */ CStr GetString() const; /** * Returns the port number part of the address */ int GetPort() const; /* Create an address pointing to the loopback, with the specified port and protocol. Use this with Bind to only listen on the loopback interface. */ static CSocketAddress Loopback(int port, ESocketProtocol proto=IPv4); }; /** * An enumeration of socket states * * @see CSocketBase::GetState() */ enum ESocketState { /** * The socket is unconnected. Use GetError() to see if it is due to a * failure, a clean close, or it was never connected. * * @see CSocketBase::GetError() */ SS_UNCONNECTED=0, /** * A connect attempt has started on a non-blocking socket. The error state * will be CONNECTION_BROKEN. * * @see CSocketBase::OnWrite() */ SS_CONNECT_STARTED, /** * The socket is connected. The error state will be set to PS_OK. */ SS_CONNECTED, /** * The connection has been closed on this end, but the other end might have * sent data that we haven't received yet. The error state will be set to * PS_OK. */ SS_CLOSED_LOCALLY }; /** * Contains the basic socket I/O abstraction and event callback methods. * A CSocketBase can only be instantiated as a subclass, none of the functions * are meant to exist as anything other than helper functions for socket * classes * * Any CSocket subclass that can be Accept:ed by a CServerSocket should * provide a constructor that takes a CSocketInternal pointer, and hands it to * the base class constructor. */ class CSocketBase { private: CSocketInternal *m_pInternal; ESocketState m_State; PS_RESULT m_Error; ESocketProtocol m_Proto; bool m_NonBlocking; /** * Loop forever, waiting for events and calling the callbacks on sockets, * according to their Op mask. This loop may be aborted by calling * AbortWaitLoop. * * The global lock must be held when calling this function, and will be held * upon return from it. */ static void RunWaitLoop(); /** * The network thread entry point. Simply locks the global lock and calls * RunWaitLoop. */ friend void *WaitLoopThreadMain(void *); #if OS_WIN /** * Used by the winsock AsyncSelect windowproc */ friend void WaitLoop_SocketUpdateProc(int fd, int error, uint eventmask); #else // These are utility functions for the unix select loop. Dox can be found in // the source file. static bool ConnectError(CSocketBase *); static void SocketWritable(CSocketBase *); static void SocketReadable(CSocketBase *); #endif /** * Abort the call to RunWaitLoop(), if one is currently running. */ static void AbortWaitLoop(); /** * Tell the running wait loop to abort. This is the platform-dependent * implementation of AbortWaitLoop() */ static void SendWaitLoopAbort(); void SendWaitLoopUpdate(); protected: /** * Initialize a CSocketBase from a CSocketInternal pointer. Use in OnAccept * callbacks to create an object of your subclass. This constructor should * be overloaded by any subclass that may be Accept:ed. */ CSocketBase(CSocketInternal *pInt); virtual ~CSocketBase(); /** * Get the op mask for the socket. */ uint GetOpMask(); /** * Set the op mask for the socket, specifying which callbacks should be * called by the WaitLoop. The initial op mask is zero, which means that * this method must be called explicitly for any callbacks to be called. * Note that before the call to BeginConnect or Bind, any call to this * method is a no-op. * * It is safe to call this function while a RunWaitLoop is running. * * The wait loop guarantees that the callbacks specified in ops will be * called when appropriate, but does not make the opposite guarantee for * unset bits; i.e. any callback may be called even with a zero op mask. */ void SetOpMask(uint ops); public: /** * These values are bitwise or-ed to produce op masks */ enum Ops { /** * Call OnRead() on a stream socket when there is data to read from the * socket, or OnAccept() on a server socket when there are incoming * connections pending */ READ=1, // Call OnWrite() when there is space available in the socket's output // buffer. Has no effect on server sockets. WRITE=2 }; /** * Constructs a CSocketBase. The OS socket object is not created by the * constructor, but by the protected Initialize method, which is called by * Connect and Bind. * * @see Connect * @see Bind */ CSocketBase(); /** * Forcibly shuts down the network wait loop. This should happen * automatically as soon as all sockets are closed. */ static void Shutdown(); /** * Returns the protocol set by Initialize. All SocketAddresses used with * the socket must have the same SocketProtocol */ inline ESocketProtocol GetProtocol() const { return m_Proto; } /** * Destroy the OS socket. If the socket is not cleanly closed before, it * will be forcefully closed by calling this method. */ void Destroy(); /** * Close the socket. No more data can be sent over the socket, but any data * pending from the remote host will still be received, and the OnRead * callback called (if the socket's op mask has the READ bit set). Note * that the socket isn't actually closed until the remote end calls * Close on the corresponding remote socket, upon which the OnClose * callback is called with a status code of PS_OK. */ void Close(); /** * Create the OS socket for the specified protocol type. */ PS_RESULT Initialize(ESocketProtocol proto=IPv4); /** * Connect the socket to the specified address. The socket must be * initialized for the protocol of the address. * * @param addr The address to connect to * @see SocketAddress::Resolve */ PS_RESULT Connect(const CSocketAddress &addr); /** @name Functions for Server Sockets */ //@{ /** * Bind the socket to the specified address and start listening for * incoming connections. You must initialize the socket for the correct * SocketProtocol before calling Bind. * * @param addr The address to bind to * @see SocketAddress::SocketAddress(int,SocketProtocol) */ PS_RESULT Bind(const CSocketAddress &addr); /** * Store the address of the next incoming connection in the SocketAddress * pointed to by addr. You must then choose whether to accept or reject the * connection by calling Accept or Reject * * @param addr A pointer to a SocketAddress * @return PS_OK or an error code * * @see Accept(SocketAddress&) * @see Reject() */ PS_RESULT PreAccept(CSocketAddress &addr); /** * Accept the next incoming connection. You must construct a suitable * CSocketBase subclass using the passed CSocketInternal. * May only be called after a successful PreAccept call */ CSocketInternal *Accept(); /** * Reject the next incoming connection. * * May only be called after a successful PreAccept call */ void Reject(); //@} /** @name Status and Options */ //@{ /** * Set or reset non-blocking operation. When non-blocking, all socket * operations will return immediately, having done none or parts of * the operation. The default state for a socket is non-blocking * * @see CSocketBase::Read * @see CSocketBase::Write * @see CSocketBase::Connect */ void SetNonBlocking(bool nonBlocking=true); /** * Return the current non-blocking state of the socket. * * @see SetNonBlocking(bool) */ inline bool IsNonBlocking() const { return m_NonBlocking; } /** * Return the error state of the socket. This will be the same value that * was returned by the IO function that failed. * * @see GetState() */ inline PS_RESULT GetErrorState() const { return m_Error; } /** * Return the connection state of the socket. If the connection status is * "unconnected", use GetError() to see if it was disconnected due to an * error, or cleanly closed. * * @see SocketState * @see GetError() */ inline ESocketState GetState() const { return m_State; } /** * Disable Nagle's algorithm (enable no-delay working mode) */ void SetTcpNoDelay(bool tcpNoDelay=true); /** * Get the address of the remote end to which the socket is connected. * * @return A reference to the socket address */ const CSocketAddress &GetRemoteAddress(); //@} /** @name Stream I/O */ //@{ /** * Attempt to read data from the socket. Any data available without blocking * will be returned. Note that a successful return does not mean that the * whole buffer was filled. * * @param buf A pointer to the buffer where the data should be written * @param len The amount of data that should be read. * @param bytesRead The number of bytes read will be stored in the variable * pointed to by bytesRead * * @retval PS_OK Some or all data was successfully read. * @retval CONNECTION_BROKEN The socket is not connected or a server socket */ PS_RESULT Read(void *buf, uint len, uint *bytesRead); /** * Attempt to write data to the socket. All data that can be sent without * blocking will be buffered. * * @param buf A pointer to the data that should be written * @param len The length of the buffer. * @param bytesWritten The number of bytes written will be stored in the * variable pointed to by bytesWritten * * @retval PS_OK Some or all data was successfully read. * @retval CONNECTION_BROKEN The socket is not connected or a server socket */ PS_RESULT Write(void *buf, uint len, uint *bytesWritten); //@} /** @name Callbacks */ //@{ /** * Called by the Network Thread when data is available for reading. Use * SetOpMask with the READ bit set to enable calling of this function. * * For server sockets, "data is available for reading" means "incoming * connections are pending". */ virtual void OnRead()=0; /** * Called by the Network Thread when data can be written to the socket. * Will only be called when the WRITE bit is set in the Op Mask of the * socket. */ virtual void OnWrite()=0; /** * The socket has been closed. It is not certain that the error code * provides meaningful diagnostics. CONNECTION_BROKEN is the generic catch- * all for erroneous closures, PS_OK for clean closures. * * @param errorCode A result code describing the reason why the socket was * closed */ virtual void OnClose(PS_RESULT errorCode)=0; }; #endif