qmsk/net/transport/tcp.py
author Tero Marttila <terom@fixme.fi>
Sun, 23 Aug 2009 22:59:40 +0300
changeset 37 14db3fe42b6c
parent 28 020c89baaa33
child 45 bb49bf8222ed
permissions -rw-r--r--
move address-family from tcp/socket interface to endpoint interface. The address family of a socket is strictly a property of the address passed to it
"""
    TCP service/client implementation.
"""

from qmsk.net.transport import service, client, stream, socket

# default backlog for listen()
# XXX: number pulled out of a hat
LISTEN_BACKLOG = 5

class Connection (socket.Stream, stream.Stream) :
    """
        Stream interface for a TCP connection
    """

    def __init__ (self, sock) :
        """
            Initialize with the given already-existing, connected, socket.
        """

        self._init_sock(sock)
    
    def shutdown (self, how) :
        """
            Selectively shut-down parts of all of the full-duplex TCP connection.

                how             - one of socket.SHUT_* to shutdown read, write or both.
        """

        self.sock.shutdown(how)

class Service (socket.Service, service.Service) :
    """
        An implementation of Service for TCP sockets.
    """
    
    _SOCKTYPE = socket.SOCK_STREAM

    def __init__ (self, endpoint, listen_backlog=LISTEN_BACKLOG, family=None) :
        """
            Construct a service, bound to the given local endpoint and listening for incoming connections using the
            given backlog.

                endpoint        - local Endpoint to bind() to. Usually, it is enough to just specify the port.
                listen_backlog  - backlog length argument to use for socket.listen()
                family          - (optional) address family to use if no endpoint is given
            
            Note that as a special case, it is possible to construct a service without an Endpoint (i.e. None).
            In this case, there will be no socket.bind() call, instead, a socket is created with the given address
            family (which *MUST* be given), and .listen() causes the OS to pick a local address to use.

            This will raise an error if the bind() or listen() operations fail.
        """
        
        # construct a suitable socket bound to the given endpoint
        self._init_endpoint(endpoint, family=family)

        # make us listen
        self._listen(listen_backlog)

        # ok, great

    def accept (self, cls=Connection) :
        """
            Perform an accept() operation on our socket, returning a tcp.Connection.
        """
        
        # XXX: trap and raise a ServiceAcceptError?
        # XXX: what to do with addr?
        sock, addr = self.sock.accept()
        
        # construct the new Stream
        return cls(sock)

    def close (self) :
        """
            Close the underlying socket object, invalidating this Service for future use.

            This will raise if the underlying socket.close() operation does.
        """

        self.sock.close()


class Client (socket.Client, client.Client) :
    """
        An implementation of Client for TCP sockets.
    """

    _SOCKTYPE = socket.SOCK_STREAM

    def __init__ (self, connect_endpoint, bind_endpoint=None) :
        """
            Construct a client, connecting to the given remote endpoint.

                connect_endpoint    - remote Endpoint to connect() to.
                bind_endpoint       - (optional) local Endpoint to bind() to before connecting.

        """

        # store
        self.connect_endpoint = connect_endpoint
        self.bind_endpoint = bind_endpoint

    def connect (self, cls=Connection) :
        """
            Perform the connect() operation, returning a tcp.Connection.
        """

        if self.bind_endpoint :
            # construct a suitable local socket, bound to a specific endpoint
            sock = self._bind_endpoint(self.bind_endpoint)

            # connect it to the remote endpoint
            self._connect_sock_endpoint(sock, self.connect_endpoint)

        else :
            # let _init_connect_endpoint pick a socket to use
            sock = self._connect_endpoint(self.connect_endpoint)
        
        # construct
        return cls(sock)