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
25# -- error constants ---------------------------------------------------------------------------{{{-
26
27
28class Error(IntEnum):
29 OK = -1
30 ON_INIT = 1
31 ON_CONNECTING = 2
32 ON_DISCONNECTING = 3
33 NOT_CONNECTED = 10
34 ON_SENDING_TIMEOUT = 20
35 ON_RECEIVING_TIMEOUT = 21
36 ON_SENDING_1 = 30
37 SENDING_BLOCKED = 31
38 ON_SENDING_2 = 32
39 ON_RECEIVING_1 = 40
40 EMPTY_MESSAGE = 41
41 ON_RECEIVING_2 = 42
42
43
44# ----------------------------------------------------------------------------------------------}}}-
45
46
47# -- class Communicator ------------------------------------------------------------------------{{{-
48
56
57
58 _mMsgLength = 8192
59
60 def __init__(self):
61 # -- member variables ------------------------------------------------------------------------{{{-
62
63 self._mHost = "localhost"
64
65 self._mPort = 21319 if not "MeVisLabTestCenterPort" in os.environ else int(os.environ["MeVisLabTestCenterPort"])
66
67 self._mConnected = False
68
69
70 self._msocket = None
71
72 self._csocket = None
73
74
75 self._mErrorCode = Error.OK
76
77 self._mErrorMsg = ""
78 # --------------------------------------------------------------------------------------------}}}-
79
80 # -- def getPort -----------------------------------------------------------------------------{{{-
81
82 def getPort(self):
83 return self._mPort
84
85 # --------------------------------------------------------------------------------------------}}}-
86
87 # -- def isConnected -------------------------------------------------------------------------{{{-
88
89 def isConnected(self):
90 return self._mConnected
91
92 # --------------------------------------------------------------------------------------------}}}-
93
94 # -- def send --------------------------------------------------------------------------------{{{-
95
97 def send(self, data, timeout):
98 # Sending requires to be connected.
99 if not self._mConnected:
100 self._handleError(Error.NOT_CONNECTED, "Not connected.")
101 return False
102
103 if data == "":
104 data == "##[empty]"
105
106 # Send length of following data.
107 length = len(data)
108 if not self._send(str(length).ljust(16), 16, timeout):
109 return False
110
111 # Send actual data.
112 return self._send(data, length, 1)
113
114 # --------------------------------------------------------------------------------------------}}}-
115
116 # -- def _send -------------------------------------------------------------------------------{{{-
117
121 def _send(self, data, length, timeout):
122 if not isinstance(data, bytes):
123 data = data.encode("UTF-8")
124 i = 0
125 while i < length:
126 # Wait for socket to get ready.
127 try:
128 (rlist, wlist, xlist) = select.select([], [self._csocket], [], timeout)
129 if self._csocket not in wlist:
130 self._handleError(Error.ON_SENDING_TIMEOUT, "Connection error (sending).")
131 return False
132 except select.error as msg:
133 self._handleError(Error.ON_SENDING_1, "Error sending: %s" % (msg))
134 return False
135
136 # Send data.
137 try:
138 # Send next data junk (of max length given in _mMsgLength).
139 j = self._csocket.send(data[i : min(length, i + self._mMsgLength)])
140 if j == -1:
141 self._handleError(Error.SENDING_BLOCKED, "Error sending: couldn't send.")
142 return -1
143 i += j
144 except OSError as msg:
145 self._handleError(Error.ON_SENDING_2, "Error sending: %s" % (msg))
146 return False
147 return i == length
148
149 # --------------------------------------------------------------------------------------------}}}-
150
151 # -- def recv --------------------------------------------------------------------------------{{{-
152
155 def recv(self, timeout):
156 # Link must be up to receive data.
157 if not self._mConnected:
158 self._handleError(Error.NOT_CONNECTED, "Not connected.")
159 return None
160
161 # Receive the length of the data.
162 data = self._recv(16, timeout)
163 if data in (b"", None):
164 return None
165 length = int(data)
166
167 # Receive the actual data.
168 data = self._recv(length, 1)
169 if isinstance(data, bytes):
170 data = data.decode("UTF-8")
171 if data == "##[empty]":
172 return ""
173 return data
174
175 # --------------------------------------------------------------------------------------------}}}-
176
177 # -- def _recv -------------------------------------------------------------------------------{{{-
178
183 def _recv(self, length, timeout):
184 data = b""
185 while len(data) < length:
186 # Wait for socket to get ready.
187 try:
188 (rlist, wlist, xlist) = select.select([self._csocket], [], [], timeout)
189 if self._csocket not in rlist:
190 self._handleError(Error.ON_RECEIVING_TIMEOUT, "Connection error (receiving).")
191 return None
192 except OSError as msg:
193 self._handleError(Error.ON_RECEIVING_1, "Error receiving: %s" % (msg))
194 return None
195
196 # Receive data of given length.
197 try:
198 tdata = self._csocket.recv(length - len(data))
199 if tdata in (b"", None):
200 self._handleError(Error.EMPTY_MESSAGE, "Error receiving: empty message received")
201 return None
202 data += tdata
203 except OSError as msg:
204 self._handleError(Error.ON_RECEIVING_2, "Error receiving: %s" % (msg))
205 return None
206 return data
207
208 # --------------------------------------------------------------------------------------------}}}-
209
210 # -- def getLastError ------------------------------------------------------------------------{{{-
211
213 def getLastError(self):
214 return self._mErrorCode
215
216 # --------------------------------------------------------------------------------------------}}}-
217
218 # -- def getLastErrorWithMessage -------------------------------------------------------------{{{-
219
222 return (self._mErrorCode, self._mErrorMsg)
223
224 # --------------------------------------------------------------------------------------------}}}-
225
226 # -- def _handleError ------------------------------------------------------------------------{{{-
227
230 def _handleError(self, errorCode, errorMsg, silent=False):
231 if not silent:
232 print(errorMsg)
233 self._mErrorCode = errorCode
234 self._mErrorMsg = errorMsg
235 self.disconnect()
236
237 # --------------------------------------------------------------------------------------------}}}-
238
239
240# ----------------------------------------------------------------------------------------------}}}-
241
242
243# -- class ComMaster ---------------------------------------------------------------------------{{{-
244
247 # -- def __init__ ----------------------------------------------------------------------------{{{-
248
249 def __init__(self, connectTimeout=10):
250 super().__init__()
251 try:
252 self._connectTimeout = connectTimeout
253 self._msocket_msocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
254 self._msocket_msocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
255 self._msocket_msocket.settimeout(self._connectTimeout)
256 port = self._mPort_mPort
257 while port < 65535:
258 try:
259 self._msocket_msocket.bind((self._mHost_mHost, port))
260 if port != self._mPort_mPort:
261 print(f"Port {self._mPort} was blocked, using {port} instead.")
262 self._mPort_mPort = port
263 break
264 except OSError as e:
265 # error code 10013 happens on windows, even though it is about missing access permissions
266 # for the socket, it actually indicates that the port is already in use
267 if e.errno in (errno.EADDRINUSE, errno.EADDRNOTAVAIL, 10013):
268 port += 1
269 else:
270 raise
271 self._msocket_msocket.listen(1)
272 except OSError as msg:
273 self._handleError(Error.ON_INIT, "Socket error (server): %s " % (msg))
274
275 # --------------------------------------------------------------------------------------------}}}-
276
277 # -- def __del__ -----------------------------------------------------------------------------{{{-
278
279 def __del__(self):
281 self._csocket_csocket.close()
282 del self._csocket_csocket
283
284 self._msocket_msocket.close()
285 del self._msocket_msocket
286
287 # --------------------------------------------------------------------------------------------}}}-
288
289 # -- def connect -----------------------------------------------------------------------------{{{-
290
291 def connect(self, timeout=2):
292 # Reset error handler.
295 try:
296 (self._csocket_csocket, addr) = self._msocket_msocket.accept()
297 self._csocket_csocket.settimeout(timeout)
299 except OSError as msg:
300 # can happen at the beginning, there are several re-tries, so be silent about it
301 self._handleError(Error.ON_CONNECTING, "Error connecting (server): %s" % (msg), silent=True)
302 return False
303 return True
304
305 # --------------------------------------------------------------------------------------------}}}-
306
307 # -- def disconnect --------------------------------------------------------------------------{{{-
308
309 def disconnect(self):
311 try:
312 self._mConnected_mConnected = False
313 self._csocket_csocket.close()
314 del self._csocket_csocket
315 except OSError as msg:
316 self._handleError(Error.ON_DISCONNECTING, "Error disconnecting: %s" % (msg), silent=True)
317 return False
318 return True
319
320 # --------------------------------------------------------------------------------------------}}}-
321
323 """Returns the timeout used by the Master while establishing the connection to the slave."""
324 return self._connectTimeout
325
326
327# ----------------------------------------------------------------------------------------------}}}-
328
329
330# -- class ComSlave ----------------------------------------------------------------------------{{{-
331
334 # -- def __init__ ----------------------------------------------------------------------------{{{-
335
336 def __init__(self):
337 super().__init__()
338 try:
339 self._csocket_csocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
340 except OSError as msg:
341 self._handleError(Error.ON_INIT, "Socket error (client): %s" % (msg))
342
343 # --------------------------------------------------------------------------------------------}}}-
344
345 # -- def __del__ -----------------------------------------------------------------------------{{{-
346
347 def __del__(self):
348 if self._csocket_csocket != None:
349 self._csocket_csocket.close()
350 del self._csocket_csocket
351
352 # --------------------------------------------------------------------------------------------}}}-
353
354 # -- def connect -----------------------------------------------------------------------------{{{-
355
356 def connect(self):
357 try:
358 try:
360 except OSError as msg:
361 if sys.platform.startswith("win") or msg.errno != errno.EINTR:
362 raise
363 poll = select.poll()
364 poll.register(self._csocket_csocket, select.POLLOUT)
365 while poll.poll(-1) == -1:
366 if msg.errno != errno.EINTR:
367 print("poll() failed", file=sys.stderr)
368 raise
369 time.sleep(0.5)
370 error = self._csocket_csocket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
371 if error:
372 print("getsockopt() failed %s" % (error), file=sys.stderr)
373 raise
374
375 self._csocket_csocket.settimeout(2)
376 self._csocket_csocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
378 except OSError as msg:
379 self._handleError(Error.ON_CONNECTING, "Error connecting (client): %s" % (msg))
380 return False
381 return True
382
383 # --------------------------------------------------------------------------------------------}}}-
384
385 # -- def disconnect --------------------------------------------------------------------------{{{-
386
387 def disconnect(self):
389 try:
390 self._mConnected_mConnected = False
391 self._csocket_csocket.close()
392 except OSError as msg:
393 self._handleError(Error.ON_DISCONNECTING, "Error disconnecting: %s" % (msg), silent=True)
394 return False
395 return True
396
397 # --------------------------------------------------------------------------------------------}}}-
398
399
400# ----------------------------------------------------------------------------------------------}}}-
The connection's master.
Definition IPC.py:246
__init__(self, connectTimeout=10)
Constructor initializing the master socket.
Definition IPC.py:249
__del__(self)
Destructor making sure the socket is deleted.
Definition IPC.py:279
connect(self, timeout=2)
Wait for a slave to connect and configure the connection.
Definition IPC.py:291
disconnect(self)
Unlink connection to the slave.
Definition IPC.py:309
The connection's slave.
Definition IPC.py:333
connect(self)
Connect to master and configure connection.
Definition IPC.py:356
__del__(self)
The slave's destructor.
Definition IPC.py:347
disconnect(self)
Disconnect from master.
Definition IPC.py:387
__init__(self)
The slave's constructor.
Definition IPC.py:336
The Communicator is the superclass for the communicating entities.
Definition IPC.py:55
int _mMsgLength
Length of the messages sent between the two entities.
Definition IPC.py:58
_handleError(self, errorCode, errorMsg, silent=False)
Set the error code and message and disconnect from network.
Definition IPC.py:230
_mConnected
Status of the connection.
Definition IPC.py:67
_csocket
Connection socket.
Definition IPC.py:72
_mErrorCode
Internal error code used to track the last error.
Definition IPC.py:75
getLastError(self)
Get a tuple describing the last error.
Definition IPC.py:213
getLastErrorWithMessage(self)
Get a tuple describing the last error.
Definition IPC.py:221
_mHost
IP to connect to.
Definition IPC.py:63
_recv(self, length, timeout)
Receive's little helper.
Definition IPC.py:183
_msocket
Main socket of the server.
Definition IPC.py:70
recv(self, timeout)
Receive data via the socket using the given timeout.
Definition IPC.py:155
getPort(self)
Get the port used for connections.
Definition IPC.py:82
isConnected(self)
Get the connection status of the IPC client.
Definition IPC.py:89
_send(self, data, length, timeout)
Send's little helper.
Definition IPC.py:121
send(self, data, timeout)
Send the data via the socket using the given timeout.
Definition IPC.py:97
_mPort
Port to use for the connection.
Definition IPC.py:65
_mErrorMsg
Internal error messages.
Definition IPC.py:77