diff -r 5834a2a972a8 Doc/library/socket.rst --- a/Doc/library/socket.rst Thu Sep 03 16:33:16 2015 +0200 +++ b/Doc/library/socket.rst Thu Sep 03 17:40:14 2015 +0200 @@ -188,6 +188,21 @@ Exceptions .. versionchanged:: 3.3 This class was made a subclass of :exc:`OSError`. +.. exception:: IncompleteReadError + + Incomplete read error, subclass of :exc:`EOFError`. The exception can be + raised by :meth:`socket.socket.recvall` for example. + + .. attribute:: expected + + Total number of expected bytes (:class:`int`). + + .. attribute:: partial + + Read bytes string before the end of stream was reached (:class:`bytes`). + + + Constants ^^^^^^^^^ @@ -962,7 +977,7 @@ to sockets. .. versionchanged:: 3.5 The *backlog* parameter is now optional. -.. method:: socket.makefile(mode='r', buffering=None, *, encoding=None, \ +.. method:: socket.makefile(mode='r', buffering=None, \*, encoding=None, \ errors=None, newline=None) .. index:: single: I/O control; buffering @@ -986,7 +1001,7 @@ to sockets. stream arguments of :meth:`subprocess.Popen`. -.. method:: socket.recv(bufsize[, flags]) +.. method:: socket.recv(bufsize, flags=0) Receive data from the socket. The return value is a bytes object representing the data received. The maximum amount of data to be received at once is specified @@ -1004,6 +1019,24 @@ to sockets. an :exc:`InterruptedError` exception (see :pep:`475` for the rationale). +.. method:: socket.recvall(bufsize, flags=0) + + Receive exactly *bufsize* bytes from the socket. The return value is a bytes object representing the + data received. See the Unix manual page :manpage:`recv(2)` for the meaning of + the optional argument *flags*; it defaults to zero. + + Raise an :exc:`IncompleteReadError` exception if the end of the stream is + reached before *n* can be read, the :attr:`IncompleteReadError.partial` + attribute of the exception contains the partial read bytes. + + The socket timeout is the maximum total duration to receive all data. + + .. seealso:: + + For blocking sockets, the :meth:`makefile` method can be used for + buffered read. It can be more efficient for small reads. + + .. method:: socket.recvfrom(bufsize[, flags]) Receive data from the socket. The return value is a pair ``(bytes, address)`` diff -r 5834a2a972a8 Lib/socket.py --- a/Lib/socket.py Thu Sep 03 16:33:16 2015 +0200 +++ b/Lib/socket.py Thu Sep 03 17:40:14 2015 +0200 @@ -120,6 +120,20 @@ if sys.platform.lower().startswith("win" class _GiveupOnSendfile(Exception): pass +class IncompleteReadError(EOFError): + """ + Incomplete read error. Attributes: + + - partial: read bytes string before the end of stream was reached + - expected: total number of expected bytes + """ + def __init__(self, partial, expected): + EOFError.__init__(self, "%s bytes read on a total of %s expected bytes" + % (len(partial), expected)) + self.partial = partial + self.expected = expected + + class socket(_socket.socket): """A subclass of _socket.socket adding the makefile() method.""" @@ -185,6 +199,28 @@ class socket(_socket.socket): sock.settimeout(self.gettimeout()) return sock + def recvall(self, size, flags=0): + """ + Receive exactly bufsize bytes from the socket. + + The return value is a bytes object representing the data received. + See the Unix manual page recv(2) for the meaning of the optional + argument *flags*; it defaults to zero. + + Raise an IncompleteReadError exception if the end of the stream is + reached before size can be read, the IncompleteReadError.partial + attribute of the exception contains the partial read bytes. + """ + buffer = bytearray(size) + view = memoryview(buffer) + pos = 0 + while pos < size: + read = self.recv_into(view[pos:], size - pos, flags) + if not read: + raise IncompleteReadError(bytes(view[:pos]), size) + pos += read + return bytes(buffer) + def accept(self): """accept() -> (socket object, address info) diff -r 5834a2a972a8 Lib/test/test_socket.py --- a/Lib/test/test_socket.py Thu Sep 03 16:33:16 2015 +0200 +++ b/Lib/test/test_socket.py Thu Sep 03 17:40:14 2015 +0200 @@ -1720,6 +1720,30 @@ class BasicTCPTest(SocketConnectedTest): def _testRecv(self): self.serv_conn.send(MSG) + def testRecvall(self): + # Testing large receive over TCP + msg = self.cli_conn.recvall(len(MSG)) + self.assertEqual(msg, MSG) + + def _testRecvall(self): + chunk_len = len(MSG) // 5 + chunks = [MSG[pos:pos+chunk_len] + for pos in range(0, len(MSG), chunk_len)] + for chunk in chunks: + # send small packets every 50 ms + self.serv_conn.send(chunk) + time.sleep(0.050) + + def testRecvallIncomplete(self): + # Testing large receive over TCP + with self.assertRaises(socket.IncompleteReadError) as cm: + self.cli_conn.recvall(len(MSG) * 2) + self.assertEqual(cm.exception.partial, MSG) + self.assertEqual(cm.exception.expected, len(MSG) * 2) + + def _testRecvallIncomplete(self): + self._testRecvall() + def testOverFlowRecv(self): # Testing receive in chunks over TCP seg1 = self.cli_conn.recv(len(MSG) - 3)