The InspIRCd Project
Home | Developers | Wiki | Forums | Bug Tracker | SVN | Download | Blog | Stats
Personal tools

The InspSocket API

From the makers of InspIRCd.

Jump to: navigation, search

Contents

InspSocket

The InspSocket API is the preferred method of handling asynchronous TCP sockets within InspIRCd modules.

As with most of the InspIRCd module API and core, InspSocket is utilized via inheritance and subclassing, and to make use of InspSocket, you must implement your own child class from InspSocket which overrides one or more of the virtual methods shown below.

Please read the examples at the bottom of this page for some demonstrations of how to use this class in a module.

Constructors

InspSocket(InspIRCd* SI)

The default constructor does nothing and should not be used.

  • SI: A pointer to a valid InspIRCd class

InspSocket(InspIRCd* SI, int newfd, const char* ip)

This constructor is used to associate an existing connecting with an InspSocket class. The given file descriptor must be valid, and when initialized, the InspSocket will be set with the given IP address and placed in CONNECTED state.

  • SI: A pointer to a valid InspIRCd class
  • newfd: The file descriptor to associate with this class
  • ip: The IP address associated with this class

InspSocket(InspIRCd* SI, const std::string &ipaddr, int port, bool listening, unsigned long maxtime)

This constructor is used to create a new socket, either listening for connections, or an outbound connection to another host. Note that if you specify a hostname in the 'ipaddr' parameter, this class will not connect. You must resolve your hostnames before passing them to InspSocket. To do so, you should use the nonblocking class 'Resolver'.

  • SI: A pointer to a valid InspIRCd class
  • ipaddr: The IP to connect to, or bind to
  • port: The port number to connect to, or bind to
  • listening: true to listen on the given host:port pair, or false to connect to them
  • maxtime: Number of seconds to wait, if connecting, before the connection times out and an OnTimeout() event is generated

Virtual Methods

virtual bool OnConnected()

This method is called when an outbound connection on your socket is completed.

  • return false to abort the connection, true to continue

virtual void OnError(InspSocketError e)

This method is called when an error occurs. A closed socket in itself is not an error, however errors also generate close events.

  • e: The error type which occurred

virtual int OnDisconnect()

When an established connection is terminated, the OnDisconnect method is triggered.

  • The return value is currently unused.

virtual bool OnDataReady()

When there is data waiting to be read on a socket, the OnDataReady() method is called. Within this method, you *MUST* call InspSocket::Read() or otherwise read any pending data. At its lowest level, this event is signalled by the core via the socket engine. If you return false from this function, the core removes your socket from its list and erases it from the socket engine, then calls InspSocket::Close() and deletes it.

  • Return false to close the socket

virtual bool OnWriteReady()

This function is called when your socket is writeable, after you have called InspSocket::WantWrite().

  • Return true to continue the connection, false to abort it.

virtual void OnTimeout()

When an outbound connection fails, and the attempt times out, you will receive this event. The method will trigger once maxtime seconds are reached (as given in the constructor) just before the socket's descriptor is closed.

virtual void OnClose()

Whenever close() is called, OnClose() will be called first. Please note that this means OnClose will be called alongside OnError(), OnTimeout(), and Close(), and also when cancelling a listening socket by calling the destructor indirectly.

virtual char* Read()

Reads all pending bytes from the socket into a char* array which can be up to 16 kilobytes in length.

  • Returns the data, or an empty NON-NULL string if there is an EOF condition. If there is a non-EOF error condition such as 'Connection reset by peer', NULL is returned and errno should be queried for the actual error.


virtual int Write(const std::string &data)

Writes a std::string to the socket. No carriage returns or linefeeds are appended to the string.

  • data: The data to send

virtual int OnIncomingConnection(int newfd, char* ip)

If your socket is a listening socket, when a new connection comes in on the socket this method will be called. Given the new file descriptor in the * parameters, and the IP, it is recommended you copy them to a new instance of your socket class, e.g.:

MySocket* newsocket = new MySocket(newfd,ip);

Once you have done this, you can then associate the new socket with the core using SocketEngine::AddFd().

virtual void Close()

This method causes the socket to close, and may also be triggered by other methods such as OnTimeout and OnError.

virtual ~InspSocket()

The destructor may implicitly call OnClose(), and will close() and shutdown() the file descriptor used for this socket.

virtual bool DoConnect()

This method attempts to connect to an IP address. This only occurs on a non-listening socket, and this method is asynchronous. You should not usually need to override this method at all.

Non-virtual Methods

bool FlushWriteBuffer()

Flushes the write buffer of the socket, sending any pending data to the peer.

void SetQueues(int nfd)

This method sets the operating system queue sizes for this socket to 32768 so that it can queue more information without application-level queueing which was required in older software. This is called for you and usually you will not have to call it directly.

  • nfd: The file descriptor to set the queue sizes on

bool BindAddr()

This method is usually called by the constructor, if your socket is an outbound connection. It selects the first IP address defined for binding of clients in the configuration file, and uses it for binding the outbound socket, to simplify IP address security. For example, in m_spanningtree, calls to this method ensure that the connection is likely to come from the same IP the user has defined at the other end, when the connecting system is multi-homed.

std::string GetIP()

Returns the IP address associated with this connection, or an empty string if no IP address exists.

void SetState(InspSocketState s)

Changes the socket's state. The core uses this to change socket states, and you should not call it directly.

void WantWrite()

Call this to receive a write event to the OnWriteReady() method next time around the event loop, rather than a read event to OnDataReady(). Note that in the current implementation, you cannot receive both a read and a write event at the same time. When the write event has been received, the socket switches back to readable.

InspSocketState GetState()

Returns the current socket state. This is one of:

  • I_DISCONNECTED: The socket is not currently connected, and hasn't ever been connected.
  • I_CONNECTING: The socket is an outbound socket which is currently connecting to the peer
  • I_CONNECTED: The socket is connected to a peer
  • I_LISTENING: The socket is a listening socket (server) which is not connected to any peer, and is accepting connections.
  • I_ERROR: The socket experienced an error and is not connected or listening.

bool Poll()

Only the core should call this function. When called, it is assumed the socket is ready to read or write data, and the method call routes the event to the various methods of InspSocket for you to handle. This can also cause the socket's state to change.

int GetFd()

This method returns the socket's file descriptor as assigned by the operating system, or -1 if no descriptor has been assigned or an error previously occurred.

void HandleEvent(EventType et)

This is re-implemented here as InspSocket inherits from EventHandler, and all child classes of EventHandler must implement a HandleEvent method. When the file descriptor becomes readable or writeable, this event is triggered which in turn calls Poll() and can remove the socket if required.

bool Readable()

This is re-implemented here as InspSocket inherits from EventHandler, and all child classes of EventHandler must implement one of Readable() or Writeable(). It always returns true, except for when the socket is in the I_CONNECTING state or wanting a write event, when it then returns false.

Examples

Connecting to a server

// first, declare our class

class MyOutboundSocket : public InspSocket
{
    MyOutboundSocket(InspIRCd* SI, std::string ip, int port) : InspSocket(SI, ip, port, false, 3000)
    {
        Instance->Log(DEFAULT,"New outbound socket created to %s", ip.c_str());
    }

    bool OnConnected()
    {
        Instance->Log(DEFAULT,"Socket just connected!");
        // In a real world program, we'd send some data here:
        // Note that this is nonblocking, and might not be sent
        // until you actually return from the method.
        //
        // this->Write("GET / HTTP/1.0\r\n\r\n");
        //
        return true;
    }
 
    void OnError(InspSocketError e)
    {
        Instance->Log(DEFAULT,"Ut oh, an error of type %d occured!", e);
    }
 
    int OnDisconnect()
    {
        Instance->Log(DEFAULT,"Our socket went byebye.");
        return 0;
    }

    bool OnDataReady()
    {
        Instance->Log(DEFAULT,"Ooooh, new shiny data!");
        // Note: We MUST read! IF we don't we'll just get
        // another read event later, until we do.
        char* data = this->Read();
        if (data && *data)
        {
            // We only read once per OnDataReady().
            // InspSocket::Read() and the socket engine will handle
            // multiple read calls for us and the EAGAIN error type,
            // without us having to worry about 'draining' the socket.
            // If you do try to 'drain' the socket, you could confuse
            // the error handlers. :)
            Instance->Log(DEFAULT,"Lovely tasty data: %s", data);
            return true;
        }
        // EOF happened, close the connection!
        return false;
    }

    void OnTimeout()
    {
        Instance->Log(DEFAULT,"The socket timed out, knock knock?");
    }

    void OnClose()
    {
        Instance->Log(DEFAULT,"The socket closed.");
    }
};

// now later on, use our class. Instantiating it causes it to connect.
MyOutboundSocket* MOS = new MyOutboundSocket(ServerInstance, "66.233.161.99", 80);

// Now if its state is I_ERROR and/or the fd is -1, it errored, dont use it.
if ((MOS->GetFd() < 0) || (MOS->GetState() == I_ERROR))
{
    ServerInstance->Log(DEFAULT,"Your socket fails it.");
    return;
}

// else, all was good, insert it into the socket engine.
ServerInstance->SE->AddFd(MOS);

// Now, we can just leave it, and the events in the class
// above will trigger when neccessary. Note that we don't
// have to free it, the socket engine will do this for us
// when the socket has outlived its usefulness, but if we
// want to remove it from the socket engine at a later date
// ourselves, we should track the pointer 'MOS' someplace.

Listening for incoming connections

// first, declare our class

class MyInboundSocket : public InspSocket
{
    MyInboundSocket(InspIRCd* SI, std::string ip, int port) : InspSocket(SI, ip, port, true, 3000)
    {
        Instance->Log(DEFAULT,"New inbound socket created listening on %s:%d", ip.c_str(), port);
    }
    
    MyInboundSocket(InspIRCd* SI, int newfd, const char* ip) : InspSocket(SI, newfd, ip)
    {
        Instance->Log(DEFAULT,"New connection set up and ready on fd %d", newfd);
    }
 
    virtual int OnIncomingConnection(int newfd, char* ip)
    {
        MyInboundSocket* newsocket = new MyInboundSocket(newfd, ip);
        Instance->Log(DEFAULT,"New inbound connection from %s on %d!", ip, newfd);
    }
    
    void OnError(InspSocketError e)
    {
        Instance->Log(DEFAULT,"Ut oh, an error of type %d occured!", e);
    }
 
    int OnDisconnect()
    {
        Instance->Log(DEFAULT,"Our socket went byebye.");
        return 0;
    }
 
    bool OnDataReady()
    {
        Instance->Log(DEFAULT,"Ooooh, new shiny data!");
        // Note: We MUST read! IF we don't we'll just get
        // another read event later, until we do.
        char* data = this->Read();
        if (data && *data)
        {
            // We only read once per OnDataReady().
            // InspSocket::Read() and the socket engine will handle
            // multiple read calls for us and the EAGAIN error type,
            // without us having to worry about 'draining' the socket.
            // If you do try to 'drain' the socket, you could confuse
            // the error handlers. :)
            Instance->Log(DEFAULT,"Lovely tasty data: %s", data);
            return true;
        }
        // EOF happened, close the connection!
        return false;
    }

    void OnTimeout()
    {
        Instance->Log(DEFAULT,"The socket timed out, knock knock?");
    }

    void OnClose()
    {
        Instance->Log(DEFAULT,"The socket closed.");
    }
};

// now later on, use our class. Instantiating it causes it to listen.
MyInboundSocket* MIS = new MyInboundSocket(ServerInstance, "127.0.0.1", 80);

// Now if its state is I_ERROR and/or the fd is -1, it errored, dont use it.
if ((MIS->GetFd() < 0) || (MIS->GetState() == I_ERROR))
{
    ServerInstance->Log(DEFAULT,"Your socket fails it.");
    return;
}

// else, all was good, insert it into the socket engine.
ServerInstance->SE->AddFd(MIS);

// Now, we can just leave it, and the events in the class
// above will trigger when neccessary. Note that we don't
// have to free it, the socket engine will do this for us
// when the socket has outlived its usefulness, but if we 
// want to remove it from the socket engine at a later date
// ourselves, we should track the pointer 'MIS' someplace.