TestCenter Reference
IPC.py
Go to the documentation of this file.
1 #
2 # Copyright 2007, MeVis Medical Solutions AG
3 #
4 # The user may use this file in accordance with the license agreement provided with
5 # the Software or, alternatively, in accordance with the terms contained in a
6 # written agreement between the user and MeVis Medical Solutions AG.
7 #
8 # For further information use the contact form at https://www.mevislab.de/contact
9 #
10 
11 
13 
14 # -- system imports ----------------------------------------------------------------------------{{{-
15 import os
16 import socket
17 import select
18 import errno
19 import time
20 import sys
21 from enum import IntEnum
22 # ----------------------------------------------------------------------------------------------}}}-
23 
24 # -- error constants ---------------------------------------------------------------------------{{{-
25 
26 class Error(IntEnum):
27  OK = -1
28  ON_INIT = 1
29  ON_CONNECTING = 2
30  ON_DISCONNECTING = 3
31  NOT_CONNECTED = 10
32  ON_SENDING_TIMEOUT = 20
33  ON_RECEIVING_TIMEOUT = 21
34  ON_SENDING_1 = 30
35  SENDING_BLOCKED = 31
36  ON_SENDING_2 = 32
37  ON_RECEIVING_1 = 40
38  EMPTY_MESSAGE = 41
39  ON_RECEIVING_2 = 42
40 
41 # ----------------------------------------------------------------------------------------------}}}-
42 
43 # -- class Communicator ------------------------------------------------------------------------{{{-
44 
51 class Communicator (object):
52 
53 
54  _mMsgLength = 8192
55 
56  def __init__(self):
57  # -- member variables ------------------------------------------------------------------------{{{-
58 
59  self._mHost_mHost = 'localhost'
60 
61  self._mPort_mPort = 20000 if not 'MeVisLabTestCenterPort' in os.environ else int(os.environ['MeVisLabTestCenterPort'])
62 
63  self._mConnected_mConnected = False
64 
65 
66  self._msocket_msocket = None
67 
68  self._csocket_csocket = None
69 
70 
71  self._mErrorCode_mErrorCode = Error.OK
72 
73  self._mErrorMsg_mErrorMsg = ""
74  # --------------------------------------------------------------------------------------------}}}-
75 
76  # -- def getPort -----------------------------------------------------------------------------{{{-
77 
78  def getPort (self):
79  return self._mPort_mPort
80  # --------------------------------------------------------------------------------------------}}}-
81 
82  # -- def isConnected -------------------------------------------------------------------------{{{-
83 
84  def isConnected (self):
85  return self._mConnected_mConnected
86  # --------------------------------------------------------------------------------------------}}}-
87 
88  # -- def send --------------------------------------------------------------------------------{{{-
89 
91  def send (self, data, timeout):
92  # Sending requires to be connected.
93  if not self._mConnected_mConnected:
94  self._handleError_handleError(Error.NOT_CONNECTED, "Not connected.")
95  return False
96 
97  if data == "":
98  data == "##[empty]"
99 
100  # Send length of following data.
101  length = len(data)
102  if not self._send_send(str(length).ljust(16), 16, timeout):
103  return False
104 
105  # Send actual data.
106  return self._send_send(data, length, 1)
107  # --------------------------------------------------------------------------------------------}}}-
108 
109  # -- def _send -------------------------------------------------------------------------------{{{-
110 
114  def _send (self, data, length, timeout):
115  if not isinstance(data, bytes):
116  data = data.encode('UTF-8')
117  i = 0
118  while i < length:
119  # Wait for socket to get ready.
120  try:
121  (rlist, wlist, xlist) = select.select([], [self._csocket_csocket], [], timeout)
122  if self._csocket_csocket not in wlist:
123  self._handleError_handleError(Error.ON_SENDING_TIMEOUT, "Connection error (sending).")
124  return False
125  except select.error as msg:
126  self._handleError_handleError(Error.ON_SENDING_1, "Error sending: %s" % (msg))
127  return False
128 
129  # Send data.
130  try:
131  # Send next data junk (of max length given in _mMsgLength).
132  j = self._csocket_csocket.send(data[i:min(length, i+self._mMsgLength_mMsgLength)])
133  if (j == -1):
134  self._handleError_handleError(Error.SENDING_BLOCKED, "Error sending: couldn't send.")
135  return -1
136  i += j
137  except OSError as msg:
138  self._handleError_handleError(Error.ON_SENDING_2, "Error sending: %s" % (msg))
139  return False
140  return (i == length)
141  # --------------------------------------------------------------------------------------------}}}-
142 
143  # -- def recv --------------------------------------------------------------------------------{{{-
144 
147  def recv (self, timeout):
148  # Link must be up to receive data.
149  if not self._mConnected_mConnected:
150  self._handleError_handleError(Error.NOT_CONNECTED, "Not connected.")
151  return None
152 
153  # Receive the length of the data.
154  data = self._recv_recv(16, timeout)
155  if data in (b'', None):
156  return None
157  length = int(data)
158 
159  # Receive the actual data.
160  data = self._recv_recv(length, 1)
161  if isinstance(data, bytes):
162  data = data.decode('UTF-8')
163  if data == "##[empty]":
164  return ""
165  return data
166  # --------------------------------------------------------------------------------------------}}}-
167 
168  # -- def _recv -------------------------------------------------------------------------------{{{-
169 
174  def _recv (self, length, timeout):
175  data = b''
176  while len(data) < length:
177  # Wait for socket to get ready.
178  try:
179  (rlist, wlist, xlist) = select.select([self._csocket_csocket], [], [], timeout)
180  if self._csocket_csocket not in rlist:
181  self._handleError_handleError(Error.ON_RECEIVING_TIMEOUT, "Connection error (receiving).")
182  return None
183  except OSError as msg:
184  self._handleError_handleError(Error.ON_RECEIVING_1, "Error receiving: %s" % (msg))
185  return None
186 
187  # Receive data of given length.
188  try:
189  tdata = self._csocket_csocket.recv(length-len(data))
190  if tdata in (b'', None):
191  self._handleError_handleError(Error.EMPTY_MESSAGE, "Error receiving: empty message received")
192  return None
193  data += tdata
194  except OSError as msg:
195  self._handleError_handleError(Error.ON_RECEIVING_2, "Error receiving: %s" % (msg))
196  return None
197  return data
198  # --------------------------------------------------------------------------------------------}}}-
199 
200  # -- def getLastError ------------------------------------------------------------------------{{{-
201 
203  def getLastError (self):
204  return self._mErrorCode_mErrorCode
205  # --------------------------------------------------------------------------------------------}}}-
206 
207  # -- def getLastErrorWithMessage -------------------------------------------------------------{{{-
208 
211  return (self._mErrorCode_mErrorCode, self._mErrorMsg_mErrorMsg)
212  # --------------------------------------------------------------------------------------------}}}-
213 
214  # -- def _handleError ------------------------------------------------------------------------{{{-
215 
218  def _handleError (self, errorCode, errorMsg, silent=False):
219  if not silent:
220  print(errorMsg)
221  self._mErrorCode_mErrorCode = errorCode
222  self._mErrorMsg_mErrorMsg = errorMsg
223  self.disconnect()
224  # --------------------------------------------------------------------------------------------}}}-
225 # ----------------------------------------------------------------------------------------------}}}-
226 
227 # -- class ComMaster ---------------------------------------------------------------------------{{{-
228 
231  # -- def __init__ ----------------------------------------------------------------------------{{{-
232 
233  def __init__ (self, connectTimeout = 10):
234  super().__init__()
235  try:
236  self._connectTimeout_connectTimeout = connectTimeout
237  self._msocket_msocket_msocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
238  self._msocket_msocket_msocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
239  self._msocket_msocket_msocket.settimeout(self._connectTimeout_connectTimeout)
240  port = self._mPort_mPort_mPort
241  while port < 65535:
242  try:
243  self._msocket_msocket_msocket.bind((self._mHost_mHost, port))
244  if port != self._mPort_mPort_mPort:
245  print(f"Port {self._mPort} was blocked, using {port} instead.")
246  self._mPort_mPort_mPort = port
247  break
248  except OSError as e:
249  # error code 10013 happens on windows, even though it is about missing access permissions
250  # for the socket, it actually indicates that the port is already in use
251  if e.errno in (errno.EADDRINUSE, errno.EADDRNOTAVAIL, 10013):
252  port += 1
253  else:
254  raise
255  self._msocket_msocket_msocket.listen(1)
256  except OSError as msg:
257  self._handleError_handleError(Error.ON_INIT, "Socket error (server): %s " % (msg))
258  # --------------------------------------------------------------------------------------------}}}-
259 
260  # -- def __del__ -----------------------------------------------------------------------------{{{-
261 
262  def __del__ (self):
263  if self._mConnected_mConnected_mConnected:
264  self._csocket_csocket.close()
265  del self._csocket_csocket
266 
267  self._msocket_msocket_msocket.close()
268  del self._msocket_msocket_msocket
269  # --------------------------------------------------------------------------------------------}}}-
270 
271  # -- def connect -----------------------------------------------------------------------------{{{-
272 
273  def connect (self, timeout = 2):
274  # Reset error handler.
275  self._mErrorCode_mErrorCode_mErrorCode = Error.OK
276  self._mErrorMsg_mErrorMsg_mErrorMsg = ""
277  try:
278  (self._csocket_csocket, addr) = self._msocket_msocket_msocket.accept()
279  self._csocket_csocket.settimeout(timeout)
280  self._mConnected_mConnected_mConnected = True
281  except OSError as msg:
282  # can happen at the beginning, there are several re-tries, so be silent about it
283  self._handleError_handleError(Error.ON_CONNECTING, u"Error connecting (server): %s" % (msg), silent=True)
284  return False
285  return True
286  # --------------------------------------------------------------------------------------------}}}-
287 
288  # -- def disconnect --------------------------------------------------------------------------{{{-
289 
290  def disconnect (self):
291  if self._mConnected_mConnected_mConnected:
292  try:
293  self._mConnected_mConnected_mConnected = False
294  self._csocket_csocket.close()
295  del self._csocket_csocket
296  except OSError as msg:
297  self._handleError_handleError(Error.ON_DISCONNECTING, "Error disconnecting: %s" % (msg), silent=True)
298  return False
299  return True
300  # --------------------------------------------------------------------------------------------}}}-
301 
302  def getConnectTimeout(self):
303  '''Returns the timeout used by the Master while establishing the connection to the slave.'''
304  return self._connectTimeout_connectTimeout
305 # ----------------------------------------------------------------------------------------------}}}-
306 
307 # -- class ComSlave ----------------------------------------------------------------------------{{{-
308 
311  # -- def __init__ ----------------------------------------------------------------------------{{{-
312 
313  def __init__ (self):
314  super().__init__()
315  try:
316  self._csocket_csocket_csocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
317  except OSError as msg:
318  self._handleError_handleError(Error.ON_INIT, "Socket error (client): %s" % (msg))
319  # --------------------------------------------------------------------------------------------}}}-
320 
321  # -- def __del__ -----------------------------------------------------------------------------{{{-
322 
323  def __del__ (self):
324  if self._csocket_csocket_csocket != None:
325  self._csocket_csocket_csocket.close()
326  del self._csocket_csocket_csocket
327  # --------------------------------------------------------------------------------------------}}}-
328 
329  # -- def connect -----------------------------------------------------------------------------{{{-
330 
331  def connect (self):
332  try:
333  try:
334  self._csocket_csocket_csocket.connect((self._mHost_mHost, self._mPort_mPort))
335  except OSError as msg:
336  if sys.platform.startswith("win") or msg.errno != errno.EINTR:
337  raise
338  poll = select.poll()
339  poll.register(self._csocket_csocket_csocket, select.POLLOUT)
340  while poll.poll(-1) == -1:
341  if msg.errno != errno.EINTR:
342  print("poll() failed", file=sys.stderr)
343  raise
344  time.sleep(0.5)
345  error = self._csocket_csocket_csocket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
346  if error:
347  print("getsockopt() failed %s" % (error), file=sys.stderr)
348  raise
349 
350  self._csocket_csocket_csocket.settimeout(2)
351  self._csocket_csocket_csocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
352  self._mConnected_mConnected_mConnected = True
353  except OSError as msg:
354  self._handleError_handleError(Error.ON_CONNECTING, "Error connecting (client): %s" % (msg))
355  return False
356  return True
357  # --------------------------------------------------------------------------------------------}}}-
358 
359  # -- def disconnect --------------------------------------------------------------------------{{{-
360 
361  def disconnect (self):
362  if self._mConnected_mConnected_mConnected:
363  try:
364  self._mConnected_mConnected_mConnected = False
365  self._csocket_csocket_csocket.close()
366  except OSError as msg:
367  self._handleError_handleError(Error.ON_DISCONNECTING, "Error disconnecting: %s" % (msg), silent=True)
368  return False
369  return True
370  # --------------------------------------------------------------------------------------------}}}-
371 # ----------------------------------------------------------------------------------------------}}}-
372 
The connection's master.
Definition: IPC.py:230
def __init__(self, connectTimeout=10)
Constructor initializing the master socket.
Definition: IPC.py:233
def connect(self, timeout=2)
Wait for a slave to connect and configure the connection.
Definition: IPC.py:273
def disconnect(self)
Unlink connection to the slave.
Definition: IPC.py:290
def __del__(self)
Destructor making sure the socket is deleted.
Definition: IPC.py:262
The connection's slave.
Definition: IPC.py:310
def disconnect(self)
Disconnect from master.
Definition: IPC.py:361
def __init__(self)
The slave's constructor.
Definition: IPC.py:313
def connect(self)
Connect to master and configure connection.
Definition: IPC.py:331
def __del__(self)
The slave's destructor.
Definition: IPC.py:323
The Communicator is the superclass for the communicating entities.
Definition: IPC.py:51
def send(self, data, timeout)
Send the data via the socket using the given timeout.
Definition: IPC.py:91
def getLastErrorWithMessage(self)
Get a tuple describing the last error.
Definition: IPC.py:210
def _recv(self, length, timeout)
Definition: IPC.py:174
def getLastError(self)
Get a tuple describing the last error.
Definition: IPC.py:203
def recv(self, timeout)
Receive data via the socket using the given timeout.
Definition: IPC.py:147
def isConnected(self)
Get the connection status of the IPC client.
Definition: IPC.py:84
def getPort(self)
Get the port used for connections.
Definition: IPC.py:78
def _handleError(self, errorCode, errorMsg, silent=False)
Definition: IPC.py:218
def _send(self, data, length, timeout)
Definition: IPC.py:114