full range of send/write operations
authorTero Marttila <terom@fixme.fi>
Sun, 16 Aug 2009 21:13:36 +0300
changeset 12 314d47bdd4d9
parent 11 7ae92c2b433f
child 13 a1091632a8a7
full range of send/write operations
qmsk/net/libc.pxd
qmsk/net/socket/__init__.py
qmsk/net/socket/socket.pyx
--- a/qmsk/net/libc.pxd	Sun Aug 16 20:22:06 2009 +0300
+++ b/qmsk/net/libc.pxd	Sun Aug 16 21:13:36 2009 +0300
@@ -35,6 +35,10 @@
 
     char* strerror (int errno)
 
+cdef extern from "unistd.h" :
+    ssize_t read (int fd, void *buf, size_t count)
+    ssize_t write (int fd, void *buf, size_t count)
+
 cdef extern from "alloca.h" :
     void* alloca (size_t size)
 
@@ -42,6 +46,9 @@
     struct iovec :
         void *iov_base
         size_t iov_len
+    
+    ssize_t readv (int fd, iovec *iov, int iovcnt)
+    ssize_t writev (int fd, iovec *iov, int iovcnt)
 
 ## general errno-based errors
 #cdef class Errno (py.OSError) :
--- a/qmsk/net/socket/__init__.py	Sun Aug 16 20:22:06 2009 +0300
+++ b/qmsk/net/socket/__init__.py	Sun Aug 16 21:13:36 2009 +0300
@@ -1,6 +1,6 @@
 # combine all the modules
+#from qmsk.net.socket.constants import *
+#from qmsk.net.socket.platform import *
 #from qmsk.net.socket.addr import *
 #from qmsk.net.socket.socket import *
-#from qmsk.net.socket.platform import *
-#from qmsk.net.socket.constants import *
 
--- a/qmsk/net/socket/socket.pyx	Sun Aug 16 20:22:06 2009 +0300
+++ b/qmsk/net/socket/socket.pyx	Sun Aug 16 21:13:36 2009 +0300
@@ -1,11 +1,39 @@
 from qmsk.net.socket.socket cimport *
 from qmsk.net.socket.addr cimport sockaddr, build_sockaddr
+
 cimport qmsk.net.socket.platform as platform
-
 cimport qmsk.net.libc as libc, qmsk.net.py as py
 
 from qmsk.net.py cimport raise_errno
 
+cdef parse_sockaddr (platform.sockaddr **sa_ptr, platform.socklen_t *sa_len, sockaddr addr, int optional = 0) :
+    if addr is not None :
+        addr._get_sockaddr(sa_ptr, sa_len)
+
+    elif optional :
+        sa_ptr[0] = NULL
+        sa_len[0] = 0
+
+    else :
+        raise ValueError(addr)
+
+cdef parse_buf (void **buf_ptr, size_t *buf_len, object buf, int optional = 0) :
+    cdef libc.ssize_t tmp_len
+
+    if buf is not None :
+        # XXX: test that except works right
+        py.PyObject_AsCharBuffer(buf, <char **> buf_ptr, &tmp_len)
+        
+        # XXX: ensure that this is >= 0
+        buf_len[0] = tmp_len
+    
+    elif optional :
+        buf_ptr[0] = NULL
+        buf_len[0] = 0
+
+    else :
+        raise ValueError(buf)
+
 # XXX: do some GIL-releasin'
 cdef class socket :
 
@@ -44,14 +72,14 @@
                               ephemeral port.
         """
 
-        cdef platform.sockaddr *sa_ptr
+        cdef platform.sockaddr *sa
         cdef platform.socklen_t sa_len
-
-        # get the address
-        addr._get_sockaddr(&sa_ptr, &sa_len)
+        
+        # XXX: require non-NULL addr?
+        parse_sockaddr(&sa, &sa_len, addr, 1)
 
         # bind()
-        if platform.bind(self.fd, sa_ptr, sa_len) :
+        if platform.bind(self.fd, sa, sa_len) :
             raise_errno('bind')
 
     def listen (self, int backlog) :
@@ -81,14 +109,14 @@
                 addr        - the remote address to connect to.
         """
 
-        cdef platform.sockaddr *sa_ptr
+        cdef platform.sockaddr *sa
         cdef platform.socklen_t sa_len
 
-        # get the address
-        addr._get_sockaddr(&sa_ptr, &sa_len)
+        # XXX: require non-NULL addr?
+        parse_sockaddr(&sa, &sa_len, addr, 1)
         
         # connect()
-        if platform.connect(self.fd, sa_ptr, sa_len) :
+        if platform.connect(self.fd, sa, sa_len) :
             raise_errno('connect')
 
     def accept (self) :
@@ -126,21 +154,20 @@
         """
             Transmit a message to the connected remote endpoint.
 
-                buf     - the data to send
-                flags   - MSG_* flags to send with
+                buf         - the data to send
+                flags       - (optional) MSG_* flags to send with
 
             Returns the number of bytes sent, which may be less than the length of buf.
         """
 
-        cdef char *buf_ptr
-        cdef libc.ssize_t buf_len, ret
+        cdef void *buf_ptr
+        cdef size_t buf_len
+        cdef libc.ssize_t ret
 
-        # get buffer
-        # XXX: test that except works right
-        py.PyObject_AsCharBuffer(buf, &buf_ptr, &buf_len)
-        
+        parse_buf(&buf_ptr, &buf_len, buf, 0)
+
         # send()
-        ret = platform.send(self.fd, <void *> buf_ptr, buf_len, flags)
+        ret = platform.send(self.fd, buf_ptr, buf_len, flags)
         
         if ret < 0 :
             raise_errno('send')
@@ -148,3 +175,143 @@
         else :
             return ret
 
+    def sendto (self, object buf, int flags = 0, sockaddr addr = None) :
+        """
+            Transmit a message to the given remote endpoint. If this socket is connected, the addr must not be
+            specified, and this acts like send()
+
+                buf         - the data to send
+                flags       - (optional) MSG_* flags to send with
+                addr        - (optional) target address
+
+            Returns the number of bytes sent, which may be less than the length of buf.
+        """
+
+        cdef void *buf_ptr
+        cdef size_t buf_len
+        cdef libc.ssize_t ret
+        
+        cdef platform.sockaddr *sa
+        cdef platform.socklen_t sa_len
+       
+        parse_sockaddr(&sa, &sa_len, addr, 1)
+        parse_buf(&buf_ptr, &buf_len, buf, 0)
+        
+        # send()
+        ret = platform.sendto(self.fd, buf_ptr, buf_len, flags, sa, sa_len)
+        
+        if ret < 0 :
+            raise_errno('sendto')
+
+        else :
+            return ret
+    
+    def sendmsg (self, sockaddr addr = None, iov = None, control = None, int flags = 0) :
+        """
+            Transmit an extended message to the given remote endpoint (or default for connected sockets) with the given
+            extra parameters.
+
+                addr        - (optional) destination address (struct msghdr::msg_name)
+                iov         - (optional) sequence of read-buffers to transmit
+                control     - (optional) control message to transmit
+                flags       - (optional) MSG_* flags to send with
+
+            Returns the number of bytes sent, which may be less than the total length of iov.
+        """
+        
+        cdef libc.ssize_t ret
+        cdef libc.iovec *iovec
+        cdef platform.msghdr msg
+        
+        libc.memset(&msg, 0, sizeof(msg))
+        
+        parse_sockaddr(<platform.sockaddr **> &msg.msg_name, &msg.msg_namelen, addr, 1)
+        parse_buf(&msg.msg_control, &msg.msg_controllen, control, 1)
+        
+        # iov
+        if iov :
+            iov = tuple(iov)
+
+            # numerb of bufs = number of iovecs
+            msg.msg_iovlen = len(iov)
+                
+            # alloca the required number of iovec's
+            msg.msg_iov = <libc.iovec *> libc.alloca(msg.msg_iovlen * sizeof(libc.iovec))
+            
+            # fill in the iovecs
+            for i, buf in enumerate(iov) :
+                iovec = &msg.msg_iov[i]
+
+                parse_buf(&iovec.iov_base, &iovec.iov_len, buf, 1)
+        
+        # sendmsg()
+        ret = platform.sendmsg(self.fd, &msg, flags)
+
+        if ret < 0 :
+            raise_errno('sendmsg')
+
+        else :
+            return ret
+       
+    def write (self, object buf) :
+        """
+            Write data to socket, mostly equivalent to send() with flags=0.
+
+                buf         - the data to send
+
+            Returns the number of bytes sent, which may be less than the length of buf.
+        """
+        
+        cdef void *buf_ptr
+        cdef size_t buf_len
+        cdef libc.ssize_t ret
+
+        parse_buf(&buf_ptr, &buf_len, buf, 0)
+
+        # send()
+        ret = libc.write(self.fd, buf_ptr, buf_len)
+        
+        if ret < 0 :
+            raise_errno('write')
+
+        else :
+            return ret
+    
+    def writev (self, iov) :
+        """
+            Write data to a socket from multiple read-buffers.
+
+                iov         - sequence of read-buffers to transmit
+            
+            Returns the number of bytes sent, which may be less than the total length of iov.
+        """
+
+        # iov
+        cdef libc.iovec *iov_list = NULL
+        cdef size_t iov_count = 0
+        cdef libc.iovec *iovec
+        
+        iov = tuple(iov)
+
+        # numerb of bufs = number of iovecs
+        iov_count = len(iov)
+            
+        # alloca the required number of iovec's
+        iov_list = <libc.iovec *> libc.alloca(iov_count * sizeof(libc.iovec))
+        
+        # fill in the iovecs
+        for i, buf in enumerate(iov) :
+            iovec = &iov_list[i]
+                
+                
+            parse_buf(&iovec.iov_base, &iovec.iov_len, buf, 1)
+            
+        # sendmsg()
+        ret = libc.writev(self.fd, iov_list, iov_count)
+
+        if ret < 0 :
+            raise_errno('writev')
+
+        else :
+            return ret
+