Module tcpcom
[hide private]
[frames] | no frames]

Source Code for Module tcpcom

  1  # tcpcom.py 
  2  # AP 
  3   
  4  ''' 
  5   This software is part of the TCPCom library. 
  6   It is Open Source Free Software, so you may 
  7   - run the code for any purpose 
  8   - study how the code works and adapt it to your needs 
  9   - integrate all or parts of the code in your own programs 
 10   - redistribute copies of the code 
 11   - improve the code and release your improvements to the public 
 12   However the use of the code is entirely your responsibility. 
 13   ''' 
 14   
 15  from threading import Thread 
 16  import thread 
 17  import socket 
 18  import time 
 19  import sys 
 20   
 21  TCPCOM_VERSION = "1.15 - Feb. 15, 2016" 
22 23 # ================================== Server ================================ 24 # ---------------------- class TCPServer ------------------------ 25 -class TCPServer(Thread):
26 ''' 27 Class that represents a TCP socket based server. 28 ''' 29 isVerbose = False 30 PORT_IN_USE = "PORT_IN_USE" 31 CONNECTED = "CONNECTED" 32 LISTENING = "LISTENING" 33 TERMINATED = "TERMINATED" 34 MESSAGE = "MESSAGE" 35
36 - def __init__(self, port, stateChanged, isVerbose = False):
37 ''' 38 Creates a TCP socket server that listens on TCP port 39 for a connecting client. The server runs in its own thread, so the 40 constructor returns immediately. State changes invoke the callback 41 onStateChanged(). 42 @param port: the IP port where to listen (0..65535) 43 @param stateChange: the callback function to register 44 @param isVerbose: if true, debug messages are written to System.out, default: False 45 ''' 46 Thread.__init__(self) 47 self.port = port 48 self.stateChanged = stateChanged 49 TCPServer.isVerbose = isVerbose 50 self.isClientConnected = False 51 self.terminateServer = False 52 self.isServerRunning = False 53 self.start()
54
55 - def run(self):
56 TCPServer.debug("TCPServer thread started") 57 HOSTNAME = "" # Symbolic name meaning all available interfaces 58 self.conn = None 59 self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 60 self.serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # close port when process exits 61 TCPServer.debug("Socket created") 62 try: 63 self.serverSocket.bind((HOSTNAME, self.port)) 64 except socket.error as msg: 65 print "Fatal error while creating TCPServer: Bind failed.", msg[0], msg[1] 66 sys.exit() 67 try: 68 self.serverSocket.listen(10) 69 except: 70 print "Fatal error while creating TCPServer: Port", self.port, "already in use" 71 try: 72 self.stateChanged(TCPServer.PORT_IN_USE, str(self.port)) 73 except Exception, e: 74 print "Caught exception in TCPServer.PORT_IN_USE:", e 75 sys.exit() 76 77 try: 78 self.stateChanged(TCPServer.LISTENING, str(self.port)) 79 except Exception, e: 80 print "Caught exception in TCPServer.LISTENING:", e 81 82 self.isServerRunning = True 83 84 while True: 85 TCPServer.debug("Calling blocking accept()...") 86 conn, self.addr = self.serverSocket.accept() 87 if self.terminateServer: 88 self.conn = conn 89 break 90 if self.isClientConnected: 91 TCPServer.debug("Returning form blocking accept(). Client refused") 92 try: 93 conn.shutdown(socket.SHUT_RDWR) 94 except: 95 pass 96 conn.close() 97 continue 98 self.conn = conn 99 self.isClientConnected = True 100 self.socketHandler = ServerHandler(self) 101 self.socketHandler.setDaemon(True) # necessary to terminate thread at program termination 102 self.socketHandler.start() 103 try: 104 self.stateChanged(TCPServer.CONNECTED, self.addr[0]) 105 except Exception, e: 106 print "Caught exception in TCPServer.CONNECTED:", e 107 self.conn.close() 108 self.serverSocket.close() 109 self.isClientConnected = False 110 try: 111 self.stateChanged(TCPServer.TERMINATED, "") 112 except Exception, e: 113 print "Caught exception in TCPServer.TERMINATED:", e 114 self.isServerRunning = False 115 TCPServer.debug("TCPServer thread terminated")
116
117 - def terminate(self):
118 ''' 119 Closes the connection and terminates the server thread. 120 Releases the IP port. 121 ''' 122 TCPServer.debug("Calling terminate()") 123 if not self.isServerRunning: 124 TCPServer.debug("Server not running") 125 return 126 self.terminateServer = True 127 TCPServer.debug("Disconnect by a dummy connection...") 128 if self.conn != None: 129 self.conn.close() 130 self.isClientConnected = False 131 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 132 client_socket.connect(('localhost', self.port)) # dummy connection to get out of accept()
133
134 - def disconnect(self):
135 ''' 136 Closes the connection with the client and enters 137 the LISTENING state 138 ''' 139 TCPServer.debug("Calling Server.disconnect()") 140 if self.isClientConnected: 141 self.isClientConnected = False 142 try: 143 self.stateChanged(TCPServer.LISTENING, str(self.port)) 144 except Exception, e: 145 print "Caught exception in TCPServer.LISTENING:", e 146 TCPServer.debug("Shutdown socket now") 147 try: 148 self.conn.shutdown(socket.SHUT_RDWR) 149 except: 150 pass 151 self.conn.close()
152
153 - def sendMessage(self, msg):
154 ''' 155 Sends the information msg to the client (as String, the character \0 (ASCII 0) serves as end of 156 string indicator, it is transparently added and removed) 157 @param msg: the message to send 158 ''' 159 TCPServer.debug("sendMessage() with msg: " + msg) 160 if not self.isClientConnected: 161 TCPServer.debug("Not connected") 162 return 163 try: 164 self.conn.sendall(msg + "\0") 165 except: 166 TCPClient.debug("Exception in sendMessage()")
167
168 - def isConnected(self):
169 ''' 170 Returns True, if a client is connected to the server. 171 @return: True, if the communication link is established 172 ''' 173 return self.isClientConnected
174
175 - def isTerminated(self):
176 ''' 177 Returns True, if the server is in TERMINATED state. 178 @return: True, if the server thread is terminated 179 ''' 180 return self.terminateServer
181 182 @staticmethod
183 - def debug(msg):
184 if TCPServer.isVerbose: 185 print " TCPServer-> " + msg
186 187 @staticmethod
188 - def getVersion():
189 ''' 190 Returns the library version. 191 @return: the current version of the library 192 ''' 193 return TCPCOM_VERSION
194
195 # ---------------------- class ServerHandler ------------------------ 196 -class ServerHandler(Thread):
197 - def __init__(self, server):
198 Thread.__init__(self) 199 self.server = server
200
201 - def run(self):
202 TCPServer.debug("ServerHandler started") 203 bufSize = 4096 204 try: 205 while True: 206 data = "" 207 reply = "" 208 isRunning = True 209 while not reply[-1:] == "\0": 210 TCPServer.debug("Calling blocking conn.recv()") 211 reply = self.server.conn.recv(bufSize) 212 if reply == None or len(reply) == 0: # Client disconnected 213 TCPServer.debug("conn.recv() returned None") 214 isRunning = False 215 break 216 data += reply 217 if not isRunning: 218 break 219 TCPServer.debug("Received msg: " + data + " len: " + str(len(data))) 220 junk = data.split("\0") # more than 1 message may be received if 221 # transfer is fast. data: xxxx\0yyyyy\0zzz\0 222 for i in range(len(junk) - 1): 223 try: 224 self.server.stateChanged(TCPServer.MESSAGE, junk[i]) 225 except Exception, e: 226 print "Caught exception in TCPServer.MESSAGE:", e 227 except: # May happen if client peer is resetted 228 TCPServer.debug("Exception from blocking conn.recv(), Msg: " + str(sys.exc_info()[0]) + \ 229 " at line # " + str(sys.exc_info()[-1].tb_lineno)) 230 231 self.server.disconnect() 232 TCPServer.debug("ServerHandler terminated")
233
234 235 # ================================== Client ================================ 236 # -------------------------------- class TCPClient -------------------------- 237 -class TCPClient():
238 ''' 239 Class that represents a TCP socket based client. 240 ''' 241 isVerbose = False 242 CONNECTING = "CONNECTING" 243 SERVER_OCCUPIED = "SERVER_OCCUPIED" 244 CONNECTION_FAILED = "CONNECTION_FAILED" 245 CONNECTED = "CONNECTED" 246 DISCONNECTED = "DISCONNECTED" 247 MESSAGE = "MESSAGE" 248
249 - def __init__(self, ipAddress, port, stateChanged, isVerbose = False):
250 ''' 251 Creates a TCP socket client prepared for a connection with a 252 TCPServer at given address and port. 253 @param host: the IP address of the host 254 @param port: the IP port where to listen (0..65535) 255 @param stateChanged: the callback function to register 256 @param isVerbose: if true, debug messages are written to System.out 257 ''' 258 self.isClientConnected = False 259 self.isClientConnecting = False 260 self.ipAddress = ipAddress 261 self.port = port 262 self.stateChanged = stateChanged 263 self.checkRefused = False 264 self.isRefused = False 265 266 TCPClient.isVerbose = isVerbose
267
268 - def sendMessage(self, msg, responseTime = 0):
269 ''' 270 Sends the information msg to the server (as String, the character \0 271 (ASCII 0) serves as end of string indicator, it is transparently added 272 and removed). For responseTime > 0 the method blocks and waits 273 for maximum responseTime seconds for a server reply. 274 @param msg: the message to send 275 @param responseTime: the maximum time to wait for a server reply (in s) 276 @return: the message or null, if a timeout occured 277 ''' 278 TCPClient.debug("sendMessage() with msg = " + msg) 279 if not self.isClientConnected: 280 TCPClient.debug("sendMessage(): Connection closed.") 281 return None 282 reply = None 283 try: 284 msg += "\0"; # Append \0 285 rc = self.sock.sendall(msg) 286 if responseTime > 0: 287 reply = self._waitForReply(responseTime) # Blocking 288 except: 289 TCPClient.debug("Exception in sendMessage()") 290 self.disconnect() 291 292 return reply
293
294 - def _waitForReply(self, responseTime):
295 TCPClient.debug("Calling _waitForReply()") 296 self.receiverResponse = None 297 startTime = time.time() 298 while self.isClientConnected and self.receiverResponse == None and time.time() - startTime < responseTime: 299 time.sleep(0.01) 300 if self.receiverResponse == None: 301 TCPClient.debug("Timeout while waiting for reply") 302 else: 303 TCPClient.debug("Response = " + self.receiverResponse + " time elapsed: " + str(int(1000 * (time.time() - startTime))) + " ms") 304 return self.receiverResponse
305
306 - def connect(self, timeout = 0):
307 ''' 308 Creates a connection to the server (blocking until timeout). 309 @param timeout: the maximum time (in s) for the connection trial (0: for default timeout) 310 @return: True, if the connection is established; False, if the server 311 is not available or occupied 312 ''' 313 if timeout == 0: 314 timeout = None 315 try: 316 self.stateChanged(TCPClient.CONNECTING, self.ipAddress + ":" + str(self.port)) 317 except Exception, e: 318 print "Caught exception in TCPClient.CONNECTING:", e 319 try: 320 self.isClientConnecting = True 321 host = (self.ipAddress, self.port) 322 if self.ipAddress == "localhost" or self.ipAddress == "127.0.0.1": 323 timeout = None # do not use timeout for local host, to avoid error message "java.net..." 324 self.sock = socket.create_connection(host, timeout) 325 self.sock.settimeout(None) 326 self.isClientConnecting = False 327 self.isClientConnected = True 328 except: 329 self.isClientConnecting = False 330 try: 331 self.stateChanged(TCPClient.CONNECTION_FAILED, self.ipAddress + ":" + str(self.port)) 332 except Exception, e: 333 print "Caught exception in TCPClient.CONNECTION_FAILED:", e 334 TCPClient.debug("Connection failed.") 335 return False 336 ClientHandler(self) 337 338 # Check if connection is refused 339 self.checkRefused = True 340 self.isRefused = False 341 startTime = time.time() 342 while time.time() - startTime < 2 and not self.isRefused: 343 time.sleep(0.001) 344 if self.isRefused: 345 TCPClient.debug("Connection refused") 346 try: 347 self.stateChanged(TCPClient.SERVER_OCCUPIED, self.ipAddress + ":" + str(self.port)) 348 except Exception, e: 349 print "Caught exception in TCPClient.SERVER_OCCUPIED:", e 350 return False 351 352 try: 353 self.stateChanged(TCPClient.CONNECTED, self.ipAddress + ":" + str(self.port)) 354 except Exception, e: 355 print "Caught exception in TCPClient.CONNECTED:", e 356 TCPClient.debug("Successfully connected") 357 return True
358
359 - def disconnect(self):
360 ''' 361 Closes the connection with the server. 362 ''' 363 TCPClient.debug("Client.disconnect()") 364 if not self.isClientConnected: 365 TCPClient.debug("Connection already closed") 366 return 367 self.isClientConnected = False 368 TCPClient.debug("Closing socket") 369 try: # catch Exception "transport endpoint is not connected" 370 self.sock.shutdown(socket.SHUT_RDWR) 371 except: 372 pass 373 self.sock.close()
374
375 - def isConnecting(self):
376 ''' 377 Returns True during a connection trial. 378 @return: True, while the client tries to connect 379 ''' 380 return self.isClientConnecting
381
382 - def isConnected(self):
383 ''' 384 Returns True of client is connnected to the server. 385 @return: True, if the connection is established 386 ''' 387 return self.isClientConnected
388 389 @staticmethod
390 - def debug(msg):
391 if TCPClient.isVerbose: 392 print " TCPClient-> " + msg
393 394 @staticmethod
395 - def getVersion():
396 ''' 397 Returns the library version. 398 @return: the current version of the library 399 ''' 400 return TCPCOM_VERSION
401
402 # -------------------------------- class ClientHandler --------------------------- 403 -class ClientHandler(Thread):
404 - def __init__(self, client):
405 Thread.__init__(self) 406 self.client = client 407 self.start()
408
409 - def run(self):
410 TCPClient.debug("ClientHandler thread started") 411 while True: 412 try: 413 junk = self.readResponse().split("\0") 414 # more than 1 message may be received 415 # if transfer is fast. data: xxxx\0yyyyy\0zzz\0 416 for i in range(len(junk) - 1): 417 try: 418 self.client.stateChanged(TCPClient.MESSAGE, junk[i]) 419 except Exception, e: 420 print "Caught exception in TCPClient.MESSAGE:", e 421 except: 422 TCPClient.debug("Exception in readResponse() Msg: " + str(sys.exc_info()[0]) + \ 423 " at line # " + str(sys.exc_info()[-1].tb_lineno)) 424 if self.client.checkRefused: 425 self.client.isRefused = True 426 break 427 try: 428 self.client.stateChanged(TCPClient.DISCONNECTED, "") 429 except Exception, e: 430 print "Caught exception in TCPClient.DISCONNECTED:", e 431 TCPClient.debug("ClientHandler thread terminated")
432
433 - def readResponse(self):
434 TCPClient.debug("Calling readResponse") 435 bufSize = 4096 436 data = "" 437 while not data[-1:] == "\0": 438 try: 439 reply = self.client.sock.recv(bufSize) # blocking 440 if len(reply) == 0: 441 TCPClient.debug("recv returns null length") 442 raise Exception("recv returns null length") 443 except: 444 TCPClient.debug("Exception from blocking conn.recv(), Msg: " + str(sys.exc_info()[0]) + \ 445 " at line # " + str(sys.exc_info()[-1].tb_lineno)) 446 raise Exception("Exception from blocking sock.recv()") 447 data += reply 448 self.receiverResponse = data[:-1] 449 return data
450