Repository: docker/docker-py

Issue:
windwos raise OSError: [Errno 9] Bad file descriptor
the file ./utils/socket.py line 31 hava code `os.read(socket.fileno(), n)` but windows not support

my call container.exec_run("xxx", socket=False) raise:

Traceback (most recent call last):
  File "E:/projects/python/judgeClient/judge.py", line 34, in <module>
    ret, s = container.exec_run("javac Main.java", workdir="/judge", socket=False)
  File "D:\Python35\lib\site-packages\docker\models\containers.py", line 185, in exec_run
    resp['Id'], detach=detach, tty=tty, stream=stream, socket=socket
  File "D:\Python35\lib\site-packages\docker\utils\decorators.py", line 19, in wrapped
    return f(self, resource_id, *args, **kwargs)
  File "D:\Python35\lib\site-packages\docker\api\exec_api.py", line 165, in exec_start
    return self._read_from_socket(res, stream, tty)
  File "D:\Python35\lib\site-packages\docker\api\client.py", line 373, in _read_from_socket
    return six.binary_type().join(gen)
  File "D:\Python35\lib\site-packages\docker\utils\socket.py", line 75, in frames_iter
    n = next_frame_size(socket)
  File "D:\Python35\lib\site-packages\docker\utils\socket.py", line 62, in next_frame_size
    data = read_exactly(socket, 8)
  File "D:\Python35\lib\site-packages\docker\utils\socket.py", line 47, in read_exactly
    next_data = read(socket, n - len(data))
  File "D:\Python35\lib\site-packages\docker\utils\socket.py", line 32, in read
    return os.read(socket.fileno(), n)
OSError: [Errno 9] Bad file descriptor



Discussion:
I met same error, is there a workaround ?
@zhangyanwei 
rewrite read function in utils\socket.py 
```
def read(socket, n=4096):
    """
    Reads at most n bytes from socket
    """

    recoverable_errors = (errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK)

    if six.PY3 and not isinstance(socket, NpipeSocket):
        select.select([socket], [], [])

    try:
        if hasattr(socket, 'recv'):
            return socket.recv(n)
        # fix OSError
        try:
            return os.read(socket.fileno(), n)
        except OSError:
            return socket.read(n)
    except EnvironmentError as e:
        if e.errno not in recoverable_errors:
            raise
```

Resolving patch:
diff --git a/docker/utils/socket.py b/docker/utils/socket.py
--- a/docker/utils/socket.py
+++ b/docker/utils/socket.py
@@ -1,6 +1,7 @@
 import errno
 import os
 import select
+import socket as pysocket
 import struct
 
 import six
@@ -28,6 +29,8 @@ def read(socket, n=4096):
     try:
         if hasattr(socket, 'recv'):
             return socket.recv(n)
+        if six.PY3 and isinstance(socket, getattr(pysocket, 'SocketIO')):
+            return socket.read(n)
         return os.read(socket.fileno(), n)
     except EnvironmentError as e:
         if e.errno not in recoverable_errors:


Test patch:
diff --git a/tests/unit/api_test.py b/tests/unit/api_test.py
--- a/tests/unit/api_test.py
+++ b/tests/unit/api_test.py
@@ -365,7 +365,7 @@ def test_stream_helper_decoding(self):
         assert result == content
 
 
-class StreamTest(unittest.TestCase):
+class UnixSocketStreamTest(unittest.TestCase):
     def setUp(self):
         socket_dir = tempfile.mkdtemp()
         self.build_context = tempfile.mkdtemp()
@@ -462,7 +462,61 @@ def test_early_stream_response(self):
                         raise e
 
             assert list(stream) == [
-                str(i).encode() for i in range(50)]
+                str(i).encode() for i in range(50)
+            ]
+
+
+class TCPSocketStreamTest(unittest.TestCase):
+    text_data = b'''
+    Now, those children out there, they're jumping through the
+    flames in the hope that the god of the fire will make them fruitful.
+    Really, you can't blame them. After all, what girl would not prefer the
+    child of a god to that of some acne-scarred artisan?
+    '''
+
+    def setUp(self):
+
+        self.server = six.moves.socketserver.ThreadingTCPServer(
+            ('', 0), self.get_handler_class()
+        )
+        self.thread = threading.Thread(target=self.server.serve_forever)
+        self.thread.setDaemon(True)
+        self.thread.start()
+        self.address = 'http://{}:{}'.format(
+            socket.gethostname(), self.server.server_address[1]
+        )
+
+    def tearDown(self):
+        self.server.shutdown()
+        self.server.server_close()
+        self.thread.join()
+
+    def get_handler_class(self):
+        text_data = self.text_data
+
+        class Handler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler, object):
+            def do_POST(self):
+                self.send_response(101)
+                self.send_header(
+                    'Content-Type', 'application/vnd.docker.raw-stream'
+                )
+                self.send_header('Connection', 'Upgrade')
+                self.send_header('Upgrade', 'tcp')
+                self.end_headers()
+                self.wfile.flush()
+                time.sleep(0.2)
+                self.wfile.write(text_data)
+                self.wfile.flush()
+
+        return Handler
+
+    def test_read_from_socket(self):
+        with APIClient(base_url=self.address) as client:
+            resp = client._post(client._url('/dummy'), stream=True)
+            data = client._read_from_socket(resp, stream=True, tty=True)
+            results = b''.join(data)
+
+        assert results == self.text_data
 
 
 class UserAgentTest(unittest.TestCase):