__init__.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. #!/usr/bin/env python3.5
  2. # Copyright 2017 Digital
  3. #
  4. # This file is part of DigiLib.
  5. #
  6. # DigiLib is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # DigiLib is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with DigiLib. If not, see <http://www.gnu.org/licenses/>.
  18. import atexit
  19. import logging
  20. import logging.handlers
  21. import os
  22. import queue
  23. import select
  24. import socket
  25. import sys
  26. import threading
  27. import time
  28. import traceback
  29. import blinker
  30. import curio
  31. import digilib.misc
  32. lclient = logging.getLogger(__name__+".client")
  33. lserver = logging.getLogger(__name__+".server")
  34. lch = logging.getLogger(__name__+".chandler")
  35. lschat = logging.getLogger(__name__+".server.chat")
  36. lcchat = logging.getLogger(__name__+".client.chat")
  37. _tasks = []
  38. class ConnHandlerBase(object):
  39. """
  40. ConnectionHandlerBase is the base class for all connection handlers.
  41. It provides basic methodes. Consider inheriting form ConnectionHandler
  42. instead, as it provides better functionality.
  43. """
  44. addr = None
  45. block_size = 1024
  46. server = None
  47. def __init__(self, socket, addr, server):
  48. super(ConnHandlerBase, self).__init__()
  49. self.socket = socket
  50. self.addr = addr
  51. self.server = server
  52. async def disconnect(self):
  53. """
  54. disconenct explicitely disconnects the client, shuts the socket down
  55. and closes it.
  56. """
  57. try:
  58. await self.send(bytes())
  59. except:
  60. lch.debug("error during disconenct")
  61. try:
  62. await self.socket.shutdown(0)
  63. except:
  64. lch.debug("error during socket shutdown")
  65. try:
  66. await self.socket.close()
  67. except:
  68. lch.debug("error closing socket")
  69. async def handle(self, data):
  70. """
  71. This method is called for every message the server receives from the
  72. client. It should handle the data. Performing asynchronous blocking
  73. actions is ok, as the client loop does not wait for this method to
  74. finish. therefore, this method can be called multiple times at once,
  75. use curio locks if appropriate.
  76. """
  77. raise NotImplemented()
  78. async def recv(self,block_size=block_size):
  79. """
  80. This method waits for the client to send something and returns bytes!
  81. """
  82. data_received = await self.socket.recv(block_size)
  83. return data_received
  84. async def send(self, data, log_msg=None):
  85. """
  86. This method sends bytes to the client. Returns False if an exception
  87. was raised during sending, otherwise True.
  88. """
  89. if log_msg:
  90. lschat.info("server:"+str(log_msg))
  91. else:
  92. lschat.info("Server:"+str(data))
  93. try:
  94. await self.socket.send(data)
  95. return True
  96. except Exception as e:
  97. lch.error(e, exc_info=True)
  98. return False
  99. class ConnHandler(ConnHandlerBase):
  100. """
  101. More advanced connection handler than ConnectionHandlerBase. For
  102. instance, sends() takes a string and encodes it, recv() decodes
  103. the client's and returns a string and welcome_client is called after
  104. the ConnHandler is initialized (Not after the inheriting class is
  105. initialized though!)
  106. """
  107. def __init__(self, socket, addr, server):
  108. super(ConnHandler, self).__init__(socket,addr,server)
  109. async def disconnect(self):
  110. """
  111. disconenct() explicitely disconnects the client, performes a proper
  112. the shutdow on the socket and closes it.
  113. """
  114. try:
  115. await self.send("")
  116. except:
  117. lch.debug("error during disconenct")
  118. try:
  119. await self.socket.shutdown(0)
  120. except:
  121. lch.debug("error during socket shutdown")
  122. try:
  123. await self.socket.close()
  124. except:
  125. lch.debug("error closing socket")
  126. async def handle(self, data):
  127. return
  128. async def on_welcome(self):
  129. """
  130. This method can be used to send a welcome message to the client.
  131. """
  132. self.send("Welcome client, this is the server sending!")
  133. async def recv(self,block_size=None):
  134. """
  135. This method waits for the client to send something, decodes it and
  136. returns a string.
  137. """
  138. if block_size == None:
  139. block_size = self.block_size
  140. data_received = await self.socket.recv(block_size)
  141. data_decoded = data_received.decode("utf-8")
  142. return data_decoded
  143. async def send(self, data, log_msg=False):
  144. """
  145. This method takes a string, encodes it and sends it to the client.
  146. Returns False if an exception was raised during sending, otherwise True.
  147. """
  148. if log_msg:
  149. lschat.info("server:"+log_msg)
  150. else:
  151. lschat.info("Server:"+data)
  152. data_encoded = bytes(data, "utf-8")
  153. try:
  154. await self.socket.send(data_encoded)
  155. return True
  156. except Exception as e:
  157. lch.error(e, exc_info=True)
  158. return False
  159. class ConnHandlerEcho(ConnHandler):
  160. """
  161. A Conn handler which sends everything it receives to every other client
  162. connected to the server
  163. """
  164. def __init__(self, socket, addr, server):
  165. super(ConnHandlerEcho, self).__init__(socket, addr, server)
  166. def handle(self, data):
  167. for h in self.server.connection_handler:
  168. if not h is self:
  169. h.send(data)
  170. class Server(object):
  171. """
  172. Server opens either an unix or an inet connection. For every new client
  173. a new ClientHandler object is created.
  174. _string_ **host** and _int_ **port** are the filename, hostname or ip
  175. address and the port respectively on which the connection will be opened.
  176. If you make an AF_UNIX socket, port is ignored so simply pass None.
  177. _object_ **handler_class** is the class (not an object of the class) used
  178. for making connection handlers.
  179. _dict_ **handler_kwargs** is a dict which will be passed as keyword
  180. argumetns to the __init__ function of handler_class when creating a new
  181. connection handler. Default is an emtpy dict.
  182. _string_ **af_family** specifies the AF_FAMILY socket type, valid options
  183. are: "AF_INET" for a inet socket and "AF_UNIX" for a unix (file) socket.
  184. Default is "AF_INET".
  185. _bool_ **log_ip** specifies wether the ip address of the server/client is logged.
  186. Default is False.
  187. _int_ **max_allowed_clients** specifies the maximum amount of clients
  188. connected to the server at once. Default is 5 (for now. this will change
  189. in the future).
  190. """
  191. # set to true when the server shuts down, for instance after a
  192. # fatal exception
  193. exit_event = False
  194. def __init__(self,
  195. host,
  196. port,
  197. handler_class,
  198. handler_kwargs={},
  199. af_family="AF_INET",
  200. log_ip=False,
  201. max_allowed_clients=5,
  202. ):
  203. super(Server, self).__init__()
  204. self.host = host
  205. self.port = port
  206. self.handler_class = handler_class
  207. self.handler_kwargs = handler_kwargs
  208. self.af_family = af_family
  209. self.log_ip = log_ip
  210. self.max_allowed_clients = max_allowed_clients
  211. # don't make the socket yet, we don't need right now. will be created
  212. # when the start method is called
  213. self.socket = None
  214. # create a task group for handlers, so we can easily cancel/terminate
  215. # them all at once
  216. self.handle_tasks = curio.TaskGroup(name="tg_handle_clients")
  217. # list of all active connection handlers
  218. self.connection_handler = []
  219. # register our cleanup method to be executed when the program exits.
  220. # the cleanup function unregisters itself, so it won't get executed twice when the user called it befor the program exites
  221. atexit.register(self.shutdown)
  222. def make_socket(self):
  223. """
  224. factory method for sockets.
  225. this method makes a normal socket and wraps it in a curi.io.Socket wrapper
  226. """
  227. lserver.debug("making a {} socket".format(self.af_family))
  228. if self.af_family == "AF_INET":
  229. s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  230. elif self.af_family == "AF_UNIX":
  231. s = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
  232. else:
  233. raise ValueError(
  234. "AF_FAMILY '{}' not supported!".format(
  235. self.af_family
  236. )
  237. )
  238. s = curio.io.Socket(s)
  239. return s
  240. def make_handler(self, conn, addr):
  241. """
  242. factory method for handlers.
  243. this method creates a handler object from self.handler_class and self.handler_kwargs
  244. """
  245. return self.handler_class(conn, addr, self, **self.handler_kwargs)
  246. def setup(self):
  247. """
  248. creates a scoket, opens the connection and starts listening for
  249. clients. this method does not block, it returns after starting to
  250. listen.
  251. """
  252. lserver.info("setting up server")
  253. self.socket = self.make_socket()
  254. if self.af_family == "AF_INET":
  255. self.socket.bind((self.host, self.port))
  256. elif self.af_family == "AF_UNIX":
  257. if os.path.exists(self.host):
  258. lserver.debug("file already exists")
  259. lserver.debug("attempting to remove it")
  260. os.remove(self.host)
  261. self.socket.bind(self.host)
  262. self.socket.listen(self.max_allowed_clients)
  263. def shutdown(self):
  264. """
  265. This method properly shuts down the sockets and closes them.
  266. it unregisters itself from atexit, so it doesn't get executed twice
  267. when it was manually called before to program exits.
  268. """
  269. def error_handler(func,*args,log_text="error",async_=False,**kwargs):
  270. try:
  271. if async_:
  272. coro = func(*args,**kwargs)
  273. lserver.debug("{} {}".format(func,coro))
  274. else:
  275. func(*args,**kwargs)
  276. except Exception as exc:
  277. lserver.debug("error occured during "+log_text,exc_info=exc)
  278. atexit.unregister(self.shutdown)
  279. lserver.info("shutting down server")
  280. error_handler(self.handle_tasks.cancel_remaining,
  281. log_text="handler cancel",async_=True)
  282. # check if there is actually a socket. if the shutdown method is
  283. # executed before the start method, there is no socket.
  284. if self.socket:
  285. error_handler(self.socket.shutdown,socket.SHUT_RDWR,
  286. log_text="socket shutdown")
  287. error_handler(self.socket.close,log_text="socket close")
  288. def start(self):
  289. """
  290. this method starts the server. it is blocking.
  291. """
  292. self.setup()
  293. curio.run(self.run)
  294. async def run(self):
  295. """
  296. this method is the main loop of the Server. it waits for new client
  297. connections and creates a handle task for each of them. it does not
  298. receive or send anything.
  299. """
  300. lserver.debug("entering main loop")
  301. while ( not self.exit_event ):
  302. lserver.debug("waiting for client to connect")
  303. # wait for a client to connect
  304. conn,addr = await self.socket.accept()
  305. if self.log_ip:
  306. lserver.info(
  307. "new client connection, {}:{}!".format(*addr))
  308. else:
  309. lserver.info("a new client connected, let's handle it")
  310. handler = self.make_handler(conn, addr)
  311. # self.register_conn(conn, addr)
  312. # self.register_handler(handler, conn)
  313. await self.handle_tasks.spawn(self.handle_client(conn,handler))
  314. async def handle_client(self,socket,handler):
  315. """
  316. This method waits for the client to send something and calls the
  317. ClientHandler's handle method. there is a handle_client method running for each client connected.
  318. """
  319. if hasattr(handler,"on_welcome"):
  320. await handler.on_welcome()
  321. while True:
  322. try:
  323. if self.log_ip:
  324. lserver.debug("waiting for {} to send something"
  325. .format(socket.getsockname()))
  326. else:
  327. lserver.debug("waiting for the client to send something")
  328. # wait for the client to send something
  329. data = await handler.recv()
  330. # if there is no data the client disconnected. this is a
  331. # tcp protocoll specification.
  332. if not data:
  333. if self.log_ip:
  334. lserver.info("the connection to {} was closed"
  335. .format(socket.getsockname()))
  336. else:
  337. lserver.info("the connection to the client was closed")
  338. await handler.disconnect()
  339. # break out of the loop. don't return because we need to
  340. # do cleanup
  341. break
  342. else:
  343. # don't strip the data of its whitespaces, since they may
  344. # be important.
  345. lschat.info("Client:"+data.rstrip())
  346. coro = handler.handle(data)
  347. task = await curio.spawn(coro)
  348. except Exception as e:
  349. lserver.error(e, exc_info=True)
  350. # let's sleep a bit, in case something is broken and the
  351. # loop throws an exception every time
  352. await curio.sleep(0.01)
  353. # if a task exits and hasn't been joined, curio prints a warning.
  354. # we don't want the warning, so let's join the current task for 0
  355. # seconds. instead of task.join() we use task.wait(). the only
  356. # difference is that wait doesn't throw a exception if the task was
  357. # stopped or crashed
  358. cur_task = await curio.current_task()
  359. await curio.ignore_after(0,cur_task.wait)
  360. class Client(threading.Thread):
  361. """docstring for Client"""
  362. is_connecting = False
  363. is_connected = False
  364. status = "uninitialized"
  365. def __init__(self,
  366. host,
  367. port=None,
  368. af_family="AF_INET",
  369. handle_data_func=None,
  370. error_handler=None,
  371. block_size=1024,
  372. ):
  373. self.super_class = super(Client, self)
  374. self.super_class.__init__()
  375. self.name = "Client"
  376. self.exit_event = False
  377. self.host = host
  378. self.port = port
  379. self.af_family = af_family
  380. self.block_size = block_size
  381. self.handle_data_func = handle_data_func
  382. self.is_connected = False
  383. self.error_handler = error_handler
  384. # self.socket = self.make_socket()
  385. self.socket = None
  386. self.status = "disconnected"
  387. def connect(self):
  388. self.status = "connecting"
  389. self.socket = self.make_socket()
  390. lclient.info(
  391. "connecting to socket '{}' of type {}".format(
  392. self.host,
  393. self.af_family
  394. )
  395. )
  396. try:
  397. if self.af_family == "AF_INET":
  398. self.socket.connect((self.host, self.port))
  399. elif self.af_family == "AF_UNIX":
  400. if os.path.exists(self.host):
  401. self.socket.connect(self.host)
  402. else:
  403. lclient.warn("File not found. Aborting.")
  404. return
  405. self.is_connected = True
  406. self.status = "connected"
  407. lclient.info("connected")
  408. return True
  409. except Exception as e:
  410. lclient.debug(e, exc_info=True)
  411. if type(e) is ConnectionRefusedError:
  412. lclient.info("failed to connect to socket '{}'".format(self.host))
  413. self.disconnect()
  414. return False
  415. def disconnect(self):
  416. lclient.info("disconnecting from socket '{}'".format(self.host))
  417. self.is_connected = False
  418. self.status = "disconnected"
  419. if self.socket:
  420. try:
  421. self.socket.shutdown(socket.SHUT_RDWR)
  422. except Exception as e:
  423. lclient.error(e)
  424. try:
  425. self.socket.close()
  426. except Exception as e:
  427. lclient.error("error occured while closing the socket, " +
  428. "maybe it is already closed",exc_info=e)
  429. del self.socket
  430. self.socket = None
  431. def handle_data(self, data_received):
  432. data_decoded = data_received.decode("utf-8")
  433. lcchat.info("Server: "+data_decoded)
  434. if self.handle_data_func:
  435. try:
  436. self.handle_data_func(data_decoded)
  437. except Exception as e:
  438. lclient.error(e, exc_info=True)
  439. def is_running(self):
  440. return (self in threading.enumerate())
  441. def make_socket(self):
  442. lclient.info("creating a {} socket".format(self.af_family))
  443. if self.af_family == "AF_INET":
  444. s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  445. elif self.af_family == "AF_UNIX":
  446. s = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
  447. else:
  448. raise ValueError(
  449. "AF_FAMILY '{}' not supported!".format(
  450. self.af_family
  451. )
  452. )
  453. return s
  454. def main_loop(self):
  455. lclient.debug("starting main loop")
  456. while ( not self.exit_event ):
  457. if not self.status in ["connected"]:
  458. time.sleep(0.1)
  459. continue
  460. # print(0)
  461. read_confirmed, write_confirmed, exc_confirmed \
  462. = select.select(
  463. [self.socket],
  464. [],
  465. [self.socket],
  466. 1
  467. )
  468. if self.socket in exc_confirmed:
  469. self.is_connected = False
  470. lclient.warning("socket is expected to corrupt, exiting")
  471. self.disconnect()
  472. # self.stop()
  473. break
  474. elif self.socket in read_confirmed:
  475. try:
  476. data_received = self.read_from_socket()
  477. if data_received == b'':
  478. lclient.info("connection is broken, closing socket exiting")
  479. self.disconnect()
  480. # self.stop()
  481. # break
  482. else:
  483. try:
  484. self.handle_data(data_received)
  485. except Exception as e:
  486. lserver.error(
  487. "Error while handling data",
  488. exc_info=e
  489. )
  490. except Exception as e:
  491. lclient.error(e, exc_info=True)
  492. if type(e) is OSError:
  493. self.is_connected = False
  494. lclient.warn("connection broken, exiting")
  495. self.disconnect()
  496. # self.stop()
  497. # break
  498. else:
  499. raise
  500. else:
  501. time.sleep(0.1)
  502. def read_from_socket(self):
  503. data_received = self.socket.recv(self.block_size)
  504. return data_received
  505. def run(self):
  506. # self.connect()
  507. if self.error_handler:
  508. self.error_handler(self.main_loop)
  509. else:
  510. self.main_loop()
  511. def send(self, msg):
  512. msg = msg.rstrip()
  513. msg_encoded = bytes(msg+"\r\n", "utf-8")
  514. try:
  515. lcchat.info("Client: "+msg)
  516. self.socket.send(msg_encoded)
  517. except Exception as e:
  518. self.is_connected = False
  519. lclient.error(e, exc_info=True)
  520. self.status = "shutdown"
  521. def setup(self):
  522. pass
  523. def stop(self,reason=None):
  524. self.disconnect()
  525. self.exit_event = True
  526. if reason:
  527. print(reason)
  528. class AsyncClient(object):
  529. """docstring for Client"""
  530. is_connecting = False
  531. is_connected = False
  532. status = "uninitialized"
  533. def __init__(self,
  534. host,
  535. port=None,
  536. af_family="AF_INET",
  537. handle_data_func=None,
  538. error_handler=None,
  539. block_size=1024,
  540. ):
  541. self.super_class = super(AsyncClient, self)
  542. self.super_class.__init__()
  543. self.name = "Client"
  544. self.exit_event = False
  545. self.host = host
  546. self.port = port
  547. self.af_family = af_family
  548. self.block_size = block_size
  549. self.handle_data_func = handle_data_func
  550. self.is_connected = False
  551. self.error_handler = error_handler
  552. self.socket = None
  553. self.status = "disconnected"
  554. def connect(self):
  555. self.status = "connecting"
  556. self.socket = self.make_socket()
  557. lclient.info("connecting to socket '{}' of type {}".format(
  558. self.host,self.af_family))
  559. try:
  560. if self.af_family == "AF_INET":
  561. self.socket.connect((self.host, self.port))
  562. elif self.af_family == "AF_UNIX":
  563. self.socket.connect(self.host)
  564. self.is_connected = True
  565. self.status = "connected"
  566. lclient.info("connected")
  567. return True
  568. except Exception as e:
  569. lclient.debug(e, exc_info=True)
  570. if type(e) is ConnectionRefusedError:
  571. lclient.info("failed to connect to socket '{}'".format(self.host))
  572. self.disconnect()
  573. return False
  574. def disconnect(self):
  575. lclient.info("disconnecting from socket '{}'".format(self.host))
  576. self.is_connected = False
  577. self.status = "disconnected"
  578. if self.socket:
  579. try:
  580. self.socket.shutdown(socket.SHUT_RDWR)
  581. except Exception as e:
  582. lclient.error(e)
  583. try:
  584. self.socket.close()
  585. except Exception as e:
  586. lclient.error("error occured while closing the socket, " +
  587. "maybe it is already closed",exc_info=e)
  588. del self.socket
  589. self.socket = None
  590. def handle_data(self, data_received):
  591. data_decoded = data_received.decode("utf-8")
  592. lcchat.info("Server: "+data_decoded)
  593. if self.handle_data_func:
  594. try:
  595. self.handle_data_func(data_decoded)
  596. except Exception as e:
  597. lclient.error(e, exc_info=True)
  598. def is_running(self):
  599. return (self in threading.enumerate())
  600. def make_socket(self):
  601. lclient.info("creating a {} socket".format(self.af_family))
  602. if self.af_family == "AF_INET":
  603. s = trio.socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  604. elif self.af_family == "AF_UNIX":
  605. s = trio.socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
  606. else:
  607. raise ValueError(
  608. "AF_FAMILY '{}' not supported!".format(
  609. self.af_family
  610. )
  611. )
  612. return s
  613. async def read_from_socket(self):
  614. data_received = await self.socket.recv(self.block_size)
  615. return data_received
  616. async def recv(self):
  617. data_received = await self.socket.recv(self.block_size)
  618. data_decoded = data_received.decode("utf-8")
  619. return data_decoded
  620. async def run(self):
  621. lclient.debug("starting main loop")
  622. while ( not self.exit_event ):
  623. if not self.status in ["connected"]:
  624. time.sleep(0.1)
  625. continue
  626. # if self.socket in exc_confirmed:
  627. # self.is_connected = False
  628. # lclient.warning("socket is expected to corrupt, exiting")
  629. # self.disconnect()
  630. # # self.stop()
  631. # break
  632. # elif self.socket in read_confirmed:
  633. try:
  634. data = await self.read_from_socket()
  635. if not data:
  636. lclient.info("connection closed")
  637. self.disconnect()
  638. else:
  639. self.handle_data(data)
  640. except Exception as e:
  641. lclient.error(e, exc_info=True)
  642. if type(e) is OSError:
  643. self.is_connected = False
  644. lclient.warn("connection broken, exiting")
  645. self.disconnect()
  646. else:
  647. raise
  648. async def start(self,connect=False):
  649. if connect:
  650. self.connect()
  651. if self.error_handler:
  652. self.error_handler(self.run)
  653. else:
  654. self.run()
  655. def send(self, msg):
  656. msg = msg.rstrip()
  657. msg_encoded = bytes(msg+"\r\n", "utf-8")
  658. try:
  659. lcchat.info("Client: "+msg)
  660. self.socket.send(msg_encoded)
  661. except Exception as e:
  662. self.is_connected = False
  663. lclient.error(e, exc_info=True)
  664. self.disconnect()
  665. # self.exit_event = True
  666. # self.status = "shutdown"
  667. def setup(self):
  668. pass
  669. def stop(self,reason=None):
  670. self.disconnect()
  671. self.exit_event = True
  672. if reason:
  673. print(reason)
  674. #