text: #!/usr/bin/env python
  
  ###############################################################################
  # mjc-broker                                                                  #
  # client.py                                                                   #
  #                                                                             #
  # (C) 2016 David Zhong                                                        #
  #                                                                             #
  # Permission is hereby granted, free of charge, to any person obtaining a     #
  # copy of this software and associated documentation files (the "Software"),  #
  # to deal in the Software without restriction, including without limitation   #
  # the rights to use, copy, modify, merge, publish, distribute, sublicense,    #
  # and/or sell copies of the Software, and to permit persons to whom the       #
  # Software is furnished to do so, subject to the following conditions:        #
  #                                                                             #
  # The above copyright notice and this permission notice shall be included in  #
  # all copies or substantial portions of the Software.                         #
  #                                                                             #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  #
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    #
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #
  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER      #
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     #
  # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         #
  # DEALINGS IN THE SOFTWARE.                                                   #
  ###############################################################################
  
  import asyncore
  import json
  import logging
  import observable
  import socket
  
  usage = "usage: client.py <host> <port>"
  
  class BrokerClient(asyncore.dispatcher_with_send, observable.Observable):
      _observable_fields_ = ["status", "fatal", "error", "pair", "clients", "address"]
      def __init__(self, host, port):
          asyncore.dispatcher_with_send.__init__(self)
          observable.Observable.__init__(self)
          self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
          self.addr = (host, port)
          self.buffer = ""
          self.name = None
  
      def send(self, data):
          asyncore.dispatcher_with_send.send(self, json.dumps(data))
          asyncore.dispatcher_with_send.send(self, "\r\n\r\n")
  
      def handle_read(self):
          self.buffer += self.recv(1024)
          packets = self.buffer.split("\r\n\r\n")
          self.buffer = packets[-1]
          [self.ProcessPDU(x) for x in packets[:-1]]
  
      def handle_close(self):
          self._sendMessage("status", "closed")
          self.close()
  
      def handle_connect(self):
          self._sendMessage("status", "connected")
  
      def Connect(self):
          self.connect(self.addr)
  
      def DoRegister(self, name):
          if self.name is None:
              self.SendRegister(name)
  
      def DoRequestPair(self, name):
          if self.name is not None:
              self.SendPair(name)
  
      def ProcessPDU(self, packet):
          try:
              message = json.loads(packet)
          except ValueError:
              logging.warning("invalid packet: %s", packet)
              return
          try:
              typ = message["type"]
          except KeyError:
              logging.warning("invalid packet: %s", packet)
              return
          if typ == "fatal" or typ == "error":
              if "message" in message:
                  err = message["message"]
              else:
                  err = "no error specified"
              self._sendMessage(typ, err)
              if typ == "fatal":
                  self.handle_close()
          elif typ == "pair":
              try:
                  self._sendMessage("pair", (message["name"], message["local_port"], message["remote_address"], message["remote_port"]))
              except KeyError:
                  logging.error("problem with pair request: %s", packet)
          elif typ == "clients":
              try:
                  self._sendMessage("clients", message["clients"])
              except KeyError:
                  logging.error("problem with client list: %s", packet)
          elif typ == "registerok":
              if "name" not in message or "address" not in message:
                  logging.error("problem with registerok: %s", packet)
              else:
                  self.name = message["name"]
                  self._sendMessage("status", "registered")
                  self._sendMessage("address", message["address"])
  
      def SendRegister(self, name):
          packet = {
              "type": "register",
              "name": name
          }
          self.send(packet)
  
      def SendPair(self, name):
          packet = {
              "type": "pair",
              "respondent": name
          }
          self.send(packet)
  
  def main():
      import sys
      import threading
      import traceback
      if len(sys.argv) < 3:
          print usage
          return
      HOST = sys.argv[1]
      try:
          PORT = int(sys.argv[2])
      except ValueError:
          print "invalid port"
          print usage
          return
      msg_queue = []
      def printfunc(s): print s
      client = BrokerClient(HOST, PORT)
      client.subscribe(lambda topic, message: printfunc("%s: %s" % (topic, message)))
      client.Connect()
      while True:
          x = raw_input("> ")
          if x:
              try:
                  eval(x)
              except KeyboardInterrupt:
                  return
              except:
                  traceback.print_exc()
          asyncore.poll()
  
  if __name__ == "__main__":
      main()
  