--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/qmsk/net/transport/socket.py Fri Aug 21 00:30:06 2009 +0300
@@ -0,0 +1,359 @@
+"""
+ Socket implementation helpers
+"""
+
+from qmsk.net.socket import socket
+from qmsk.net.socket.constants import *
+
+class SocketError (Exception) :
+ """
+ Base class of errors raised by the Socket classes.
+ """
+
+class SocketBindAddrinfoError (SocketError) :
+ """
+ The socket was unable to socket()+bind() to the given addrinfo.
+ """
+
+ def __init__ (self, addrinfo, error) :
+ """
+ addrinfo - the addrinfo we tried to use
+ error - the resulting error
+ """
+
+ self.addrinfo = addrinfo
+ self.error = error
+
+ def __str__ (self) :
+ return "Unable to bind() to %s: %s" % (self.addrinfo, self.error)
+
+class SocketBindEndpointError (SocketError) :
+ """
+ The socket was unable to bind() to the any of the given endpoint's addresses.
+ """
+
+ def __init__ (self, endpoint, errors) :
+ """
+ endpoint - the endpoint we tried to bind to
+ errors - a sequence of ServiceBindAddrinfoErrors describing the failed bind attempts.
+ """
+
+ self.endpoint = endpoint
+ self.errors = errors
+
+ def __str__ (self) :
+ # XXX: too verbose?
+ return "Unable to bind() to any addresses on endpoint %s:\n%s" % (self.endpoint, "\n".join(
+ "\t%s" % (error, ) for error in self.errors
+ ))
+
+
+class Base (object) :
+ """
+ Base class for all other socket-related classes, contains the underlying socket object.
+ """
+
+ # the underlying socket object
+ sock = None
+
+ def _init_sock (self, sock) :
+ """
+ Initialize with the given pre-existing socket.
+ """
+
+ self.sock = sock
+
+
+class Common (Base) :
+ """
+ Common operations for Client/Service
+ """
+
+ @classmethod
+ def _socket (self, family, socktype, protocol = 0) :
+ """
+ Construct and return a new socket object using the given parameters.
+ """
+
+ return socket.socket(family, socktype, protocol)
+
+ @classmethod
+ def _bind_addrinfo (cls, ai) :
+ """
+ This will attempt to create a new socket and bind it, based on the given addrinfo, returning the new socket.
+
+ Raises a ServiceBindAddrinfoError if this fails
+ """
+
+ try :
+ # socket()
+ sock = self._socket(ai.family, ai.socktype, ai.protocol)
+
+ # bind()
+ sock.bind(ai.addr)
+
+ # XXX: except socket.error as e :
+ except OSError, error :
+ raise SocketBindAddrinfoError(ai, error)
+
+ else :
+ return sock
+
+ @classmethod
+ def _bind_endpoint (cls, endpoint, family, socktype, protocol=0) :
+ """
+ This will resolve the given endpoint, and attempt to create and bind a suitable socket and return it.
+
+ endpoint - local Endpoint to bind() to.
+ family - socket address family to use.
+ socktype - socket type to use
+ protocol - (optional) specific protocol
+
+ Raises a ServiceBindError if this is unable to create a bound socket.
+
+ XXX: bind to all of the given endpoint's addresses instead of just one...?
+ """
+
+ errors = []
+
+ # resolve the endpoint and try socket+bind
+ for ai in endpoint.getaddrinfo(family, socktype, protocol, AI_PASSIVE) :
+ try :
+ # try to socket+bind this addrinfo
+ sock = cls._bind_addrinfo(ai)
+
+ except SocketBindAddrinfoError, error :
+ # collect
+ errors.append(error)
+
+ # keep trying
+ continue
+
+ else :
+ # got a working socket :)
+ return sock
+
+ else :
+ # no suitable address found :(
+ raise SocketBindEndpointError(endpoint, errors)
+
+ def _init_endpoint (self, endpoint, family, socktype, protocol = 0) :
+ """
+ Initialize this socket by constructing a new socket with the given parameters, bound to the given endpoint,
+ if given. If no endpoint is given, this simply creates a socket with the given settings and does not bind
+ it anywhere.
+ """
+
+ # create local socket
+ if endpoint :
+ # create a suitable socket bound to a the given endpoint
+ self.sock = self._bind_endpoint(endpoint, family, socktype, protocol)
+
+ else :
+ # create a suitable socket not bound to anything
+ self.sock = self._socket(family, socktype, protocol)
+
+
+class Service (Common) :
+ """
+ Listener socket
+ """
+
+ def _listen (self, backlog) :
+ """
+ Puts this socket into listen() mode with the given backlog.
+ """
+
+ self.sock.listen(backlog)
+
+class SocketConnectAddrinfoError (SocketError) :
+ """
+ The socket was unable to socket()+connect() to the given addrinfo.
+ """
+
+ def __init__ (self, addrinfo, error) :
+ """
+ addrinfo - the addrinfo we tried to use
+ error - the resulting error
+ """
+
+ self.addrinfo = addrinfo
+ self.error = error
+
+ def __str__ (self) :
+ return "Unable to connect() to %s: %s" % (self.addrinfo, self.error)
+
+class SocketConnectEndpointError (SocketError) :
+ """
+ The socket was unable to connect() to the any of the given endpoint's addresses.
+ """
+
+ def __init__ (self, endpoint, errors) :
+ """
+ endpoint - the endpoint we tried to connect to
+ errors - a sequence of ServiceBindAddrinfoErrors describing the failed connect attempts.
+ """
+
+ self.endpoint = endpoint
+ self.errors = errors
+
+ def __str__ (self) :
+ # XXX: too verbose?
+ return "Unable to connect() to any addresses on endpoint %s:\n%s" % (self.endpoint, "\n".join(
+ "\t%s" % (error, ) for error in self.errors
+ ))
+
+
+class Client (Common) :
+ """
+ Connecting socket
+ """
+
+ @classmethod
+ def _connect_sock_addr (cls, sock, addr) :
+ """
+ Attempt to connect the given socket to the given address.
+ """
+
+ sock.connect(addr)
+
+ @classmethod
+ def _connect_sock_addrinfo (cls, sock, ai) :
+ """
+ Attempt to connect the given socket to the given addrinfo's address.
+ """
+
+ try :
+ cls._connect_sock_addr(sock, ai.addr)
+
+ # XXX: except socket.error as e :
+ except OSError, error :
+ raise SocketConnectAddrinfoError(ai, error)
+
+ @classmethod
+ def _connect_addrinfo (cls, ai) :
+ """
+ Attempt to create a socket and connect it based on the given addrinfo, returning the new socket is succesfull.
+ """
+
+ try :
+ # socket()
+ sock = cls._socket(ai.family, ai.socktype, ai.protocol)
+
+ # XXX: except socket.error as e :
+ except OSError, error :
+ raise SocketConnectAddrinfoError(ai, error)
+
+
+ # try and connect() it
+ cls._connect_sock_addrinfo(sock, ai)
+
+ # return once succesfully
+ return sock
+
+ @classmethod
+ def _connect_sock_endpoint (cls, sock, endpoint, family, socktype, protocol = 0) :
+ """
+ Connect this socket to the given remote endpoint, using the given parameters to resolve the endpoint.
+ """
+
+ errors = []
+
+ # resolve the endpoint and try socket+bind
+ for ai in endpoint.getaddrinfo(family, socktype, protocol) :
+ try :
+ # try to connect the socket to this addrinfo
+ cls._connect_sock_addrinfo(sock, ai)
+
+ except SocketConnectAddrinfoError, error :
+ # collect
+ errors.append(error)
+
+ # keep trying
+ continue
+
+ else :
+ # got a working socket :)
+ return
+
+ else :
+ # no suitable address found :(
+ raise SocketConnectEndpointError(endpoint, errors)
+
+ @classmethod
+ def _connect_endpoint (self, endpoint, family, socktype, protocol = 0) :
+ """
+ Create a new socket and connect it to the given remote endpoint, using the given parameters to resolve the
+ endpoint.
+ """
+
+ errors = []
+
+ # resolve the endpoint and try socket+bind
+ for ai in endpoint.getaddrinfo(family, socktype, protocol) :
+ try :
+ # try to socket+connect this addrinfo
+ sock = cls._connect_addrinfo(ai)
+
+ except SocketConnectAddrinfoError, error :
+ # collect
+ errors.append(error)
+
+ # keep trying
+ continue
+
+ else :
+ # got a working socket :)
+ return sock
+
+ else :
+ # no suitable address found :(
+ raise SocketConnectEndpointError(endpoint, errors)
+
+ def _init_connect_endpoint (self, endpoint, family, socktype, protocol = 0):
+ """
+ If we already have an existing socket, connect it to the given endpoint, otherwise try and connect to the
+ given endpoint with a new socket.
+
+ There is a subtle difference here, because if we have e.g. an IPv4 socket and try and connect it to an
+ endpoint with both IPv6 and IPv4 addresses, we will try to connect to an IPv6 address using the IPv4 socket,
+ and then to the IPv4 address using the IPv6 socket.
+
+ If we do not yet have a socket, then we will attempt to connect to the IPv6 address using an IPv6 socket,
+ and to the IPv4 address using an IPv4 socket.
+ """
+
+ if self.socket :
+ # connect with existing socket
+ self._connect_sock_endpoint(self.socket, endpoint, family, socktype, protocol)
+
+ else :
+ # connect with new socket
+ self._connect_endpoint(endpoint, family, socktype, protocol)
+
+class Stream (Base) :
+ """
+ Unbuffered byte-stream interface.
+ """
+
+ def read (self, iov) :
+ return self.sock.read(iov)
+
+ def readv (self, iovecs) :
+ return self.sock.readv(iovecs)
+
+ def write (self, buf) :
+ return self.sock.write(buf)
+
+ def writev (self, iovecs) :
+ return self.sock.writev(iovecs)
+
+ def close (self) :
+ self.sock.close()
+
+ def abort (self) :
+ # XXX: SO_LINGER magic
+
+ raise NotImplementedError()
+
+