forked from 0ad/0ad
484 lines
14 KiB
C++
Executable File
484 lines
14 KiB
C++
Executable File
#ifndef _SocketBase_H
|
|
#define _SocketBase_H
|
|
|
|
//--------------------------------------------------------
|
|
// Includes / Compiler directives
|
|
//--------------------------------------------------------
|
|
|
|
#include "posix.h"
|
|
#include "types.h"
|
|
#include "Prometheus.h"
|
|
#include <string.h>
|
|
#include "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;
|
|
|
|
/**
|
|
* Initialize any data needed to communicate to the RunWaitLoop(). After
|
|
* the call to InitWaitLoop, it should be safe to call any IPC function
|
|
* that expects to talk to the wait loop.
|
|
*/
|
|
static void InitWaitLoop();
|
|
|
|
/**
|
|
* Loop forever, waiting for events and calling the callbacks on sockets,
|
|
* according to their Op mask.
|
|
*/
|
|
static void RunWaitLoop();
|
|
|
|
/**
|
|
* The network thread entry point. Simply calls RunWaitLoop()
|
|
*/
|
|
friend void *WaitLoopThreadMain(void *);
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* Used by the winsock AsyncSelect windowproc
|
|
*/
|
|
friend void WaitLoop_SocketUpdateProc(int fd, int error, uint eventmask);
|
|
|
|
#else
|
|
|
|
/**
|
|
* An internal utility function used by the UNIX select loop
|
|
*/
|
|
friend bool ConnectError(CSocketBase *, CSocketInternal *);
|
|
#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();
|
|
|
|
/**
|
|
* 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 PS_FAIL
|
|
*
|
|
* @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
|