socket: new read implementation with select (#62)
diff --git a/serial/urlhandler/protocol_socket.py b/serial/urlhandler/protocol_socket.py
index 22f6904..7932ee0 100644
--- a/serial/urlhandler/protocol_socket.py
+++ b/serial/urlhandler/protocol_socket.py
@@ -133,6 +133,9 @@
         lr, lw, lx = select.select([self._socket], [], [], 0)
         return len(lr)
 
+    # select based implementation, similar to posix, but only using socket API
+    # to be portable, additionally handle socket timeout which is used to
+    # emulate write timeouts
     def read(self, size=1):
         """\
         Read size bytes from the serial port. If a timeout is set it may
@@ -141,31 +144,46 @@
         """
         if not self.is_open:
             raise portNotOpenError
-        data = bytearray()
-        if self._timeout is not None:
-            timeout = time.time() + self._timeout
-        else:
-            timeout = None
-        while len(data) < size:
+        read = bytearray()
+        timeout = self._timeout
+        while len(read) < size:
             try:
-                # an implementation with internal buffer would be better
-                # performing...
-                block = self._socket.recv(size - len(data))
-                if block:
-                    data.extend(block)
-                else:
-                    # no data -> EOF (connection probably closed)
-                    break
+                start_time = time.time()
+                ready, _, _ = select.select([self._socket], [], [], timeout)
+                # If select was used with a timeout, and the timeout occurs, it
+                # returns with empty lists -> thus abort read operation.
+                # For timeout == 0 (non-blocking operation) also abort when
+                # there is nothing to read.
+                if not ready:
+                    break   # timeout
+                buf = self._socket.recv(size - len(read))
+                # read should always return some data as select reported it was
+                # ready to read when we get to this point, unless it is EOF
+                if not buf:
+                    raise SerialException('socket disconnected')
+                read.extend(buf)
+                if timeout is not None:
+                    timeout -= time.time() - start_time
+                    if timeout <= 0:
+                        break
             except socket.timeout:
-                # just need to get out of recv from time to time to check if
-                # still alive and timeout did not expire
+                # timeout is used for write support, just go reading again
                 pass
             except socket.error as e:
                 # connection fails -> terminate loop
                 raise SerialException('connection failed (%s)' % e)
-            if timeout is not None and time.time() > timeout:
-                break
-        return bytes(data)
+            except OSError as e:
+                # this is for Python 3.x where select.error is a subclass of
+                # OSError ignore EAGAIN errors. all other errors are shown
+                if e.errno != errno.EAGAIN:
+                    raise SerialException('read failed: %s' % (e,))
+            except select.error as e:
+                # this is for Python 2.x
+                # ignore EAGAIN errors. all other errors are shown
+                # see also http://www.python.org/dev/peps/pep-3151/#select
+                if e[0] != errno.EAGAIN:
+                    raise SerialException('read failed: %s' % (e,))
+        return bytes(read)
 
     def write(self, data):
         """\