import threading
import socket

class MyThread(threading.Thread):
  def __init__(self, conn):
    threading.Thread.__init__(self) # call superclass constructor
    self.conn = conn # add a field to this object
  def run(self): # will be run by .start() in the new thread
    while True:
      buffer = self.conn.recv(4096)
      if not buffer:
        print('client',self.conn.fileno(),'disconnected')
        break
      print(f"[{self.conn.fileno()}] recv() binary:", ' '.join('{:02X}'.format(byte) for byte in buffer))
      try:
        print(f"[{self.conn.fileno()}] recv():", buffer.decode('utf-8'))
      except UnicodeDecodeError as ex:
        print(f"[{self.conn.fileno()}] recv()ed non-string data")
      self.conn.send(f'recv()ed {len(buffer)} bytes\n'.encode('utf-8'))
    self.conn.close()

if __name__ == '__main__': # checks that this file was run, not imported
  import sys
  if len(sys.argv) != 2:
    print('USAGE:', sys.argv[0], '<port>')
    quit(1)
  port = int(sys.argv[1])
  print(f'Binding to port {port}. Visit http://localhost:{port}/ to interact with your server!')

  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.bind(('', port))
    sock.listen(10)
    while True:
      try:
        conn, addr = sock.accept()
        print("Client connected from", addr, f"(fd={conn.fileno()})")
        client_thread = MyThread(conn)
        client_thread.daemon = True
        client_thread.start()
      except: # this ensures the socket is closed on a Ctrl+C
        # raise # this will show the error instead of closing the socket
        break