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

__init__.py 17KB

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