TestCenter Reference
IPC.py
Go to the documentation of this file.
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
13
14# -- system imports ----------------------------------------------------------------------------{{{-
15import os
16import socket
17import select
18import errno
19import time
20import sys
21from enum import IntEnum
22# ----------------------------------------------------------------------------------------------}}}-
23
24# -- error constants ---------------------------------------------------------------------------{{{-
25
26class 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
52
53
54 _mMsgLength = 8192
55
56 def __init__(self):
57 # -- member variables ------------------------------------------------------------------------{{{-
58
59 self._mHost = 'localhost'
60
61 self._mPort = 20000 if not 'MeVisLabTestCenterPort' in os.environ else int(os.environ['MeVisLabTestCenterPort'])
62
63 self._mConnected = False
64
65
66 self._msocket = None
67
68 self._csocket = None
69
70
71 self._mErrorCode = Error.OK
72
73 self._mErrorMsg = ""
74 # --------------------------------------------------------------------------------------------}}}-
75
76 # -- def getPort -----------------------------------------------------------------------------{{{-
77
78 def getPort (self):
79 return self._mPort
80 # --------------------------------------------------------------------------------------------}}}-
81
82 # -- def isConnected -------------------------------------------------------------------------{{{-
83
84 def isConnected (self):
85 return self._mConnected
86 # --------------------------------------------------------------------------------------------}}}-
87
88 # -- def send --------------------------------------------------------------------------------{{{-
89
91 def send (self, data, timeout):
92 # Sending requires to be connected.
93 if not self._mConnected:
94 self._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(str(length).ljust(16), 16, timeout):
103 return False
104
105 # Send actual data.
106 return self._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], [], timeout)
122 if self._csocket not in wlist:
123 self._handleError(Error.ON_SENDING_TIMEOUT, "Connection error (sending).")
124 return False
125 except select.error as msg:
126 self._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.send(data[i:min(length, i+self._mMsgLength)])
133 if (j == -1):
134 self._handleError(Error.SENDING_BLOCKED, "Error sending: couldn't send.")
135 return -1
136 i += j
137 except OSError as msg:
138 self._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:
150 self._handleError(Error.NOT_CONNECTED, "Not connected.")
151 return None
152
153 # Receive the length of the data.
154 data = self._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(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], [], [], timeout)
180 if self._csocket not in rlist:
181 self._handleError(Error.ON_RECEIVING_TIMEOUT, "Connection error (receiving).")
182 return None
183 except OSError as msg:
184 self._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.recv(length-len(data))
190 if tdata in (b'', None):
191 self._handleError(Error.EMPTY_MESSAGE, "Error receiving: empty message received")
192 return None
193 data += tdata
194 except OSError as msg:
195 self._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
205 # --------------------------------------------------------------------------------------------}}}-
206
207 # -- def getLastErrorWithMessage -------------------------------------------------------------{{{-
208
211 return (self._mErrorCode, self._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 = errorCode
222 self._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
237 self._msocket_msocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
238 self._msocket_msocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
239 self._msocket_msocket.settimeout(self._connectTimeout)
240 port = self._mPort_mPort
241 while port < 65535:
242 try:
243 self._msocket_msocket.bind((self._mHost_mHost, port))
244 if port != self._mPort_mPort:
245 print(f"Port {self._mPort} was blocked, using {port} instead.")
246 self._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.listen(1)
256 except OSError as msg:
257 self._handleError(Error.ON_INIT, "Socket error (server): %s " % (msg))
258 # --------------------------------------------------------------------------------------------}}}-
259
260 # -- def __del__ -----------------------------------------------------------------------------{{{-
261
262 def __del__ (self):
264 self._csocket_csocket.close()
265 del self._csocket_csocket
266
267 self._msocket_msocket.close()
268 del self._msocket_msocket
269 # --------------------------------------------------------------------------------------------}}}-
270
271 # -- def connect -----------------------------------------------------------------------------{{{-
272
273 def connect (self, timeout = 2):
274 # Reset error handler.
277 try:
278 (self._csocket_csocket, addr) = self._msocket_msocket.accept()
279 self._csocket_csocket.settimeout(timeout)
281 except OSError as msg:
282 # can happen at the beginning, there are several re-tries, so be silent about it
283 self._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):
292 try:
293 self._mConnected_mConnected = False
294 self._csocket_csocket.close()
295 del self._csocket_csocket
296 except OSError as msg:
297 self._handleError(Error.ON_DISCONNECTING, "Error disconnecting: %s" % (msg), silent=True)
298 return False
299 return True
300 # --------------------------------------------------------------------------------------------}}}-
301
303 '''Returns the timeout used by the Master while establishing the connection to the slave.'''
304 return self._connectTimeout
305# ----------------------------------------------------------------------------------------------}}}-
306
307# -- class ComSlave ----------------------------------------------------------------------------{{{-
308
311 # -- def __init__ ----------------------------------------------------------------------------{{{-
312
313 def __init__ (self):
314 super().__init__()
315 try:
316 self._csocket_csocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
317 except OSError as msg:
318 self._handleError(Error.ON_INIT, "Socket error (client): %s" % (msg))
319 # --------------------------------------------------------------------------------------------}}}-
320
321 # -- def __del__ -----------------------------------------------------------------------------{{{-
322
323 def __del__ (self):
324 if self._csocket_csocket != None:
325 self._csocket_csocket.close()
326 del self._csocket_csocket
327 # --------------------------------------------------------------------------------------------}}}-
328
329 # -- def connect -----------------------------------------------------------------------------{{{-
330
331 def connect (self):
332 try:
333 try:
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, 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.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.settimeout(2)
351 self._csocket_csocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
353 except OSError as msg:
354 self._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):
363 try:
364 self._mConnected_mConnected = False
365 self._csocket_csocket.close()
366 except OSError as msg:
367 self._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
__init__(self, connectTimeout=10)
Constructor initializing the master socket.
Definition IPC.py:233
__del__(self)
Destructor making sure the socket is deleted.
Definition IPC.py:262
connect(self, timeout=2)
Wait for a slave to connect and configure the connection.
Definition IPC.py:273
disconnect(self)
Unlink connection to the slave.
Definition IPC.py:290
The connection's slave.
Definition IPC.py:310
connect(self)
Connect to master and configure connection.
Definition IPC.py:331
__del__(self)
The slave's destructor.
Definition IPC.py:323
disconnect(self)
Disconnect from master.
Definition IPC.py:361
__init__(self)
The slave's constructor.
Definition IPC.py:313
The Communicator is the superclass for the communicating entities.
Definition IPC.py:51
int _mMsgLength
Length of the messages sent between the two entities.
Definition IPC.py:54
_handleError(self, errorCode, errorMsg, silent=False)
Set the error code and message and disconnect from network.
Definition IPC.py:218
_mConnected
Status of the connection.
Definition IPC.py:63
_csocket
Connection socket.
Definition IPC.py:68
_mErrorCode
Internal error code used to track the last error.
Definition IPC.py:71
getLastError(self)
Get a tuple describing the last error.
Definition IPC.py:203
getLastErrorWithMessage(self)
Get a tuple describing the last error.
Definition IPC.py:210
_mHost
IP to connect to.
Definition IPC.py:59
_recv(self, length, timeout)
Receive's little helper.
Definition IPC.py:174
_msocket
Main socket of the server.
Definition IPC.py:66
recv(self, timeout)
Receive data via the socket using the given timeout.
Definition IPC.py:147
getPort(self)
Get the port used for connections.
Definition IPC.py:78
isConnected(self)
Get the connection status of the IPC client.
Definition IPC.py:84
_send(self, data, length, timeout)
Send's little helper.
Definition IPC.py:114
send(self, data, timeout)
Send the data via the socket using the given timeout.
Definition IPC.py:91
_mPort
Port to use for the connection.
Definition IPC.py:61
_mErrorMsg
Internal error messages.
Definition IPC.py:73