Skip to content

Commit 052ed36

Browse files
authored
Merge pull request #310 from ccatterina/patch-socket-recv
Use select to check if there is data available in the socket
2 parents 7caad55 + f391262 commit 052ed36

File tree

2 files changed

+45
-30
lines changed

2 files changed

+45
-30
lines changed

pymodbus/client/sync.py

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import socket
2+
import select
23
import serial
34
import time
45
import sys
@@ -230,34 +231,43 @@ def _recv(self, size):
230231
"""
231232
if not self.socket:
232233
raise ConnectionException(self.__str__())
233-
# socket.recv(size) waits until it gets some data from the host but
234-
# not necessarily the entire response that can be fragmented in
235-
# many packets.
236-
# To avoid the splitted responses to be recognized as invalid
237-
# messages and to be discarded, loops socket.recv until full data
238-
# is received or timeout is expired.
239-
# If timeout expires returns the read data, also if its length is
240-
# less than the expected size.
234+
235+
# socket.recv(size) waits until it gets some data from the host but
236+
# not necessarily the entire response that can be fragmented in
237+
# many packets.
238+
# To avoid the splitted responses to be recognized as invalid
239+
# messages and to be discarded, loops socket.recv until full data
240+
# is received or timeout is expired.
241+
# If timeout expires returns the read data, also if its length is
242+
# less than the expected size.
241243
self.socket.setblocking(0)
242-
begin = time.time()
243244

244-
data = b''
245-
if size is not None:
246-
while len(data) < size:
247-
try:
248-
data += self.socket.recv(size - len(data))
249-
except socket.error:
250-
pass
251-
if not self.timeout or (time.time() - begin > self.timeout):
252-
break
245+
timeout = self.timeout
246+
247+
# If size isn't specified read 1 byte at a time.
248+
if size is None:
249+
recv_size = 1
253250
else:
254-
while True:
255-
try:
256-
data += self.socket.recv(1)
257-
except socket.error:
258-
pass
259-
if not self.timeout or (time.time() - begin > self.timeout):
260-
break
251+
recv_size = size
252+
253+
data = b''
254+
begin = time.time()
255+
while recv_size > 0:
256+
ready = select.select([self.socket], [], [], timeout)
257+
if ready[0]:
258+
data += self.socket.recv(recv_size)
259+
260+
# If size isn't specified continue to read until timeout expires.
261+
if size:
262+
recv_size = size - len(data)
263+
264+
# Timeout is reduced also if some data has been received in order
265+
# to avoid infinite loops when there isn't an expected response size
266+
# and the slave sends noisy data continuosly.
267+
timeout -= time.time() - begin
268+
if timeout <= 0:
269+
break
270+
261271
return data
262272

263273
def is_socket_open(self):

test/test_client_sync.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,12 @@ def testSyncTcpClientInstantiation(self):
166166
client = ModbusTcpClient()
167167
self.assertNotEqual(client, None)
168168

169-
def testBasicSyncTcpClient(self):
169+
@patch('pymodbus.client.sync.select')
170+
def testBasicSyncTcpClient(self, mock_select):
170171
''' Test the basic methods for the tcp sync client'''
171172

172173
# receive/send
174+
mock_select.select.return_value = [True]
173175
client = ModbusTcpClient()
174176
client.socket = mockSocket()
175177
self.assertEqual(0, client._send(None))
@@ -207,8 +209,11 @@ def testTcpClientSend(self):
207209
self.assertEqual(0, client._send(None))
208210
self.assertEqual(4, client._send('1234'))
209211

210-
def testTcpClientRecv(self):
212+
@patch('pymodbus.client.sync.select')
213+
def testTcpClientRecv(self, mock_select):
211214
''' Test the tcp client receive method'''
215+
216+
mock_select.select.return_value = [True]
212217
client = ModbusTcpClient()
213218
self.assertRaises(ConnectionException, lambda: client._recv(1024))
214219

@@ -223,10 +228,10 @@ def testTcpClientRecv(self):
223228
self.assertEqual(b'\x00\x01\x02', client._recv(3))
224229
mock_socket.recv.side_effect = iter([b'\x00', b'\x01', b'\x02'])
225230
self.assertEqual(b'\x00\x01', client._recv(2))
226-
mock_socket.recv.side_effect = socket.error('No data')
231+
mock_select.select.return_value = [False]
227232
self.assertEqual(b'', client._recv(2))
228233
client.socket = mockSocket()
229-
client.socket.timeout = 0.1
234+
mock_select.select.return_value = [True]
230235
self.assertIn(b'\x00', client._recv(None))
231236

232237
def testSerialClientRpr(self):
@@ -351,4 +356,4 @@ def testSerialClientRepr(self):
351356
# Main
352357
# ---------------------------------------------------------------------------#
353358
if __name__ == "__main__":
354-
unittest.main()
359+
unittest.main()

0 commit comments

Comments
 (0)