Python Library TCP Socket
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

__init__.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. """
  4. tcp_socket (TCP Socket)
  5. =======================
  6. **Author:**
  7. * Dirk Alders <sudo-dirk@mount-mockery.de>
  8. **Description:**
  9. This Module supports a client/ server tcp socket connection.
  10. **Submodules:**
  11. * :class:`tcp_socket.tcp_client`
  12. * :class:`tcp_socket.tcp_client_stp`
  13. * :class:`tcp_socket.tcp_server`
  14. * :class:`tcp_socket.tcp_server_stp`
  15. **Unittest:**
  16. See also the :download:`unittest <../../tcp_socket/_testresults_/unittest.pdf>` documentation.
  17. """
  18. __DEPENDENCIES__ = ['stringtools', 'task', ]
  19. import stringtools
  20. import task
  21. import logging
  22. import socket
  23. import time
  24. try:
  25. from config import APP_NAME as ROOT_LOGGER_NAME
  26. except ImportError:
  27. ROOT_LOGGER_NAME = 'root'
  28. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
  29. class tcp_base(object):
  30. """
  31. :param host: The host IP for the TCP socket functionality
  32. :type host: str
  33. :param port: The port for the TCP socket functionality
  34. :type port: int
  35. :param rx_log_lvl: The log level to log incomming RX-data
  36. :type rx_log_lvl: int
  37. This is the base class for other classes in this module.
  38. .. note:: This class is not designed for direct usage.
  39. """
  40. LOG_PREFIX = 'TCP_IP:'
  41. RX_LENGTH = 0xff
  42. COM_TIMEOUT = 0.5
  43. IS_CLIENT = False
  44. def __init__(self, host, port, rx_log_lvl=logging.INFO):
  45. self.host = host
  46. self.port = port
  47. self.__socket__ = None
  48. self.__data_available_callback__ = None
  49. self.__supress_data_available_callback__ = False
  50. self.__connect_callback__ = None
  51. self.__disconnect_callback__ = None
  52. self.__clean_receive_buffer__()
  53. self.__connection__ = None
  54. self.__listening_message_displayed__ = False
  55. self.__client_address__ = None
  56. self.__queue__ = task.threaded_queue()
  57. self.__queue__.enqueue(5, self.__receive_task__, rx_log_lvl)
  58. self.__queue__.run()
  59. def is_connected(self):
  60. return self.__connection__ is not None
  61. def client_address(self):
  62. """
  63. :return: The client address.
  64. :rtype: str
  65. This method returns the address of the connected client.
  66. """
  67. return self.__client_address__[0]
  68. def receive(self, timeout=1, num=None):
  69. """
  70. :param timeout: The timeout for receiving data (at least after the timeout the method returns data or None).
  71. :type timeout: float
  72. :param num: the number of bytes to receive (use None to get all available data).
  73. :type num: int
  74. :return: The received data.
  75. :rtype: str
  76. This method returns data received via the initiated communication channel.
  77. """
  78. rv = None
  79. if self.__connection__ is not None:
  80. tm = time.time()
  81. while (num is not None and len(self.__receive_buffer__) < num) or (num is None and len(self.__receive_buffer__) < 1):
  82. if self.__connection__ is None:
  83. return None
  84. if time.time() > tm + timeout:
  85. logger.warning('%s TIMEOUT (%ss): Not enough data in buffer. Requested %s and buffer size is %d.', self.LOG_PREFIX, repr(timeout), repr(num or 'all'), len(self.__receive_buffer__))
  86. return None
  87. time.sleep(0.05)
  88. if num is None:
  89. rv = self.__receive_buffer__
  90. self.__clean_receive_buffer__()
  91. else:
  92. rv = self.__receive_buffer__[:num]
  93. self.__receive_buffer__ = self.__receive_buffer__[num:]
  94. return rv
  95. def send(self, data, timeout=1, log_lvl=logging.INFO):
  96. """
  97. :param data: The data to be send over the communication channel.
  98. :type data: str
  99. :param timeout: The timeout for sending data (e.g. time to establish new connection).
  100. :type timeout: float
  101. :param rx_log_lvl: The log level to log outgoing TX-data
  102. :type rx_log_lvl: int
  103. :return: True if data had been sent, otherwise False.
  104. :rtype: bool
  105. This method sends data via the initiated communication channel.
  106. """
  107. cnt = 0
  108. while self.__connection__ is None and cnt < int(10 * timeout):
  109. time.sleep(.1) # give some time to establish the connection
  110. cnt += 1
  111. if self.__connection__ is not None:
  112. self.__connection__.sendall(data)
  113. logger.log(log_lvl, '%s TX -> "%s"', self.LOG_PREFIX, stringtools.hexlify(data))
  114. return True
  115. else:
  116. logger.warning('%s Cound NOT send -> "%s"', self.LOG_PREFIX, stringtools.hexlify(data))
  117. return False
  118. def register_callback(self, callback):
  119. """
  120. :param callback: The callback which will be executed, when data is available.
  121. :type callback: function
  122. This method sets the callback which is executed, if data is available. You need to execute :func:`receive` of the instance
  123. given with the first argument.
  124. .. note:: The :func:`callback` is executed with these arguments:
  125. :param self: This communication instance
  126. """
  127. self.__data_available_callback__ = callback
  128. def register_connect_callback(self, callback):
  129. """
  130. :param callback: The callback which will be executed, when a connect is identified.
  131. :type callback: function
  132. This method sets the callback which is executed, if a connect is identified.
  133. """
  134. self.__connect_callback__ = callback
  135. def register_disconnect_callback(self, callback):
  136. """
  137. :param callback: The callback which will be executed, when a disconnect is identified.
  138. :type callback: function
  139. This method sets the callback which is executed, if a disconnect is identified.
  140. """
  141. self.__disconnect_callback__ = callback
  142. def __receive_task__(self, queue_inst, rx_log_lvl):
  143. if self.__connection__ is not None:
  144. try:
  145. data = self.__connection__.recv(self.RX_LENGTH)
  146. except socket.error as e:
  147. if e.errno != 11:
  148. raise
  149. else:
  150. time.sleep(.05)
  151. else:
  152. if len(data) > 0:
  153. logger.log(rx_log_lvl, '%s RX <- "%s"', self.LOG_PREFIX, stringtools.hexlify(data))
  154. self.__receive_buffer__ += data
  155. else:
  156. self.__connection_lost__()
  157. self.__call_data_available_callback__()
  158. else:
  159. self.__connect__()
  160. queue_inst.enqueue(5, self.__receive_task__, rx_log_lvl)
  161. def __call_data_available_callback__(self):
  162. if len(self.__receive_buffer__) > 0 and not self.__supress_data_available_callback__ and self.__data_available_callback__ is not None:
  163. self.__supress_data_available_callback__ = True
  164. self.__data_available_callback__(self)
  165. self.__supress_data_available_callback__ = False
  166. def __connection_lost__(self):
  167. self.__listening_message_displayed__ = False
  168. self.__connection__.close()
  169. self.__connection__ = None
  170. self.__client_address__ = None
  171. logger.info('%s Connection lost...', self.LOG_PREFIX)
  172. if self.__disconnect_callback__ is not None:
  173. self.__disconnect_callback__()
  174. def __clean_receive_buffer__(self):
  175. logger.debug("%s Cleaning up receive-buffer", self.LOG_PREFIX)
  176. self.__receive_buffer__ = ""
  177. def close(self):
  178. """
  179. This method closes the active communication channel, if channel exists.
  180. """
  181. self.__queue__.stop()
  182. self.__queue__.join()
  183. if self.__connection__ is not None:
  184. self.__connection_lost__()
  185. if self.__socket__ is not None:
  186. self.__socket__.close()
  187. def __del__(self):
  188. self.close()
  189. class tcp_server(tcp_base):
  190. """
  191. :param host: The host IP for the TCP socket functionality
  192. :type host: str
  193. :param port: The port for the TCP socket functionality
  194. :type port: int
  195. :param rx_log_lvl: The log level to log incomming RX-data
  196. :type rx_log_lvl: int
  197. This class supports a tcp-server transfering a serial stream of bytes (characters).
  198. .. note:: You need a :class:`tcp_client` to communicate with the server.
  199. **Example:**
  200. .. literalinclude:: ../../tcp_socket/_examples_/tcp_socket__tcp_server.py
  201. .. literalinclude:: ../../tcp_socket/_examples_/tcp_socket__tcp_server.log
  202. """
  203. def __connect__(self):
  204. if self.__socket__ is None:
  205. # Create a TCP/IP socket
  206. self.__socket__ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  207. # Bind the socket to the port
  208. server_address = (self.host, self.port)
  209. self.__socket__.bind(server_address)
  210. # Listen for incoming connections
  211. self.__socket__.listen(1)
  212. self.__socket__.settimeout(self.COM_TIMEOUT)
  213. self.__socket__.setblocking(False)
  214. if not self.__listening_message_displayed__:
  215. logger.info('%s Server listening to %s:%d', self.LOG_PREFIX, self.host, self.port)
  216. self.__listening_message_displayed__ = True
  217. try:
  218. self.__connection__, self.__client_address__ = self.__socket__.accept()
  219. except socket.error as e:
  220. if e.errno != 11:
  221. raise
  222. else:
  223. time.sleep(.05)
  224. else:
  225. logger.info('%s Connection established... (from %s)', self.LOG_PREFIX, self.client_address())
  226. self.__clean_receive_buffer__()
  227. self.__connection__.setblocking(False)
  228. if self.__connect_callback__ is not None:
  229. self.__connect_callback__()
  230. class tcp_client(tcp_base):
  231. """
  232. :param host: The host IP for the TCP socket functionality
  233. :type host: str
  234. :param port: The port for the TCP socket functionality
  235. :type port: int
  236. :param rx_log_lvl: The log level to log incomming RX-data
  237. :type rx_log_lvl: int
  238. This class supports a tcp-client transfering a serial stream of bytes (characters).
  239. .. note:: You need a running :class:`tcp_server` listening at the given IP and port to communicate.
  240. **Example:**
  241. .. literalinclude:: ../../tcp_socket/_examples_/tcp_socket__tcp_client.py
  242. .. literalinclude:: ../../tcp_socket/_examples_/tcp_socket__tcp_client.log
  243. """
  244. IS_CLIENT = True
  245. def __connect__(self):
  246. if self.__socket__ is None:
  247. # Create a TCP/IP socket
  248. self.__socket__ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  249. self.__socket__.setblocking(False)
  250. # Connect the socket to the port where the server is listening
  251. try:
  252. self.__socket__.connect((self.host, self.port))
  253. except socket.error as e:
  254. if e.errno == 9:
  255. self.__socket__.close()
  256. elif e.errno != 115 and e.errno != 111 and e.errno != 114:
  257. raise
  258. else:
  259. self.__connection__ = None
  260. time.sleep(.05)
  261. else:
  262. logger.info('%s Connection to %s:%s established', self.LOG_PREFIX, self.host, self.port)
  263. self.__clean_receive_buffer__()
  264. self.__connection__ = self.__socket__
  265. if self.__connect_callback__ is not None:
  266. self.__connect_callback__()
  267. def __connection_lost__(self):
  268. self.__socket__ = None
  269. tcp_base.__connection_lost__(self)
  270. def reconnect(self):
  271. self.__connect__()
  272. class tcp_base_stp(tcp_base):
  273. """
  274. :param host: The host IP for the TCP socket functionality
  275. :type host: str
  276. :param port: The port for the TCP socket functionality
  277. :type port: int
  278. :param rx_log_lvl: The log level to log incomming RX-data
  279. :type rx_log_lvl: int
  280. This is the base class for other classes in this module.
  281. .. note:: This class is not designed for direct usage.
  282. """
  283. def __init__(self, host, port, rx_log_lvl=logging.INFO):
  284. tcp_base.__init__(self, host, port, rx_log_lvl=rx_log_lvl)
  285. self.__stp__ = stringtools.stp.stp()
  286. def __clean_receive_buffer__(self):
  287. logger.debug("%s Cleaning up receive-buffer", self.LOG_PREFIX)
  288. self.__receive_buffer__ = []
  289. def receive(self, timeout=1):
  290. """
  291. :param timeout: The timeout for receiving data (at least after the timeout the method returns data or None).
  292. :type timeout: float
  293. :return: The received data.
  294. :rtype: str
  295. This method returns one received messages via the initiated communication channel.
  296. """
  297. try:
  298. return tcp_base.receive(self, timeout=timeout, num=1)[0]
  299. except TypeError:
  300. return None
  301. def send(self, data, timeout=1, log_lvl=logging.INFO):
  302. """
  303. :param data: The message to be send over the communication channel.
  304. :type data: str
  305. :param timeout: The timeout for sending data (e.g. time to establish new connection).
  306. :type timeout: float
  307. :param rx_log_lvl: The log level to log outgoing TX-data
  308. :type rx_log_lvl: int
  309. :return: True if data had been sent, otherwise False.
  310. :rtype: bool
  311. This method sends one message via the initiated communication channel.
  312. """
  313. if tcp_base.send(self, stringtools.stp.build_frame(data), timeout=timeout, log_lvl=logging.DEBUG):
  314. logger.log(log_lvl, '%s TX -> "%s"', self.LOG_PREFIX, stringtools.hexlify(data))
  315. return True
  316. else:
  317. return False
  318. def __receive_task__(self, queue_inst, rx_log_lvl):
  319. if self.__connection__ is not None:
  320. try:
  321. data = self.__connection__.recv(self.RX_LENGTH)
  322. except socket.error as e:
  323. if e.errno == 104:
  324. self.__connection_lost__()
  325. elif e.errno != 11:
  326. raise
  327. else:
  328. time.sleep(.05)
  329. else:
  330. if len(data) > 0:
  331. logger.debug('%s -- <- "%s"', self.LOG_PREFIX, stringtools.hexlify(data))
  332. content = self.__stp__.process(data)
  333. for msg in content:
  334. logger.log(rx_log_lvl, '%s RX <- "%s"', self.LOG_PREFIX, stringtools.hexlify(msg))
  335. self.__receive_buffer__.append(msg)
  336. else:
  337. self.__connection_lost__()
  338. self.__call_data_available_callback__()
  339. else:
  340. self.__connect__()
  341. queue_inst.enqueue(5, self.__receive_task__, rx_log_lvl)
  342. class tcp_server_stp(tcp_server, tcp_base_stp):
  343. """
  344. :param host: The host IP for the TCP socket functionality
  345. :type host: str
  346. :param port: The port for the TCP socket functionality
  347. :type port: int
  348. :param rx_log_lvl: The log level to log incomming RX-data
  349. :type rx_log_lvl: int
  350. This class supports a tcp-server transfering a string. The string will be packed on send and unpacked on receive.
  351. See :mod:`stp` for more information on packing and unpacking.
  352. .. note:: You need a :class:`tcp_client_stp` to communicate with the server.
  353. **Example:**
  354. .. literalinclude:: ../../tcp_socket/_examples_/tcp_socket__tcp_server_stp.py
  355. .. literalinclude:: ../../tcp_socket/_examples_/tcp_socket__tcp_server_stp.log
  356. """
  357. pass
  358. class tcp_client_stp(tcp_client, tcp_base_stp):
  359. """
  360. :param host: The host IP for the TCP socket functionality
  361. :type host: str
  362. :param port: The port for the TCP socket functionality
  363. :type port: int
  364. This class supports a tcp-server transfering a string. The string will be packed on send and unpacked on receive.
  365. See :mod:`stp` for more information on packing and unpacking.
  366. .. note:: You need a running :class:`tcp_server_stp` listening at the given IP and port to communicate.
  367. **Example:**
  368. .. literalinclude:: ../../tcp_socket/_examples_/tcp_socket__tcp_server_stp.py
  369. .. literalinclude:: ../../tcp_socket/_examples_/tcp_socket__tcp_server_stp.log
  370. """
  371. pass