Python Library TCP Socket
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

__init__.py 16KB

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