crzcas

Server-Client-one-file

Jan 5th, 2021
126
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.00 KB | None | 0 0
  1. import socket
  2. import select
  3. import errno
  4.  
  5.  
  6. # User input to select option
  7. option = input("Type client or server >")
  8.  
  9.  
  10. # Setup based on user input
  11. if option == 'server':
  12.  
  13.  
  14.     # Python Socket Server. Line 15-151
  15.     HEADER_LENGTH = 10
  16.  
  17.     IP = "127.0.0.1"
  18.     PORT = 1234
  19.  
  20.     # Create a socket
  21.     # socket.AF_INET - address family, IPv4, some otehr possible are AF_INET6, AF_BLUETOOTH, AF_UNIX
  22.     # socket.SOCK_STREAM - TCP, conection-based, socket.SOCK_DGRAM - UDP, connectionless, datagrams, socket.SOCK_RAW - raw IP packets
  23.     server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  24.  
  25.     # SO_ - socket option
  26.     # SOL_ - socket option level
  27.     # Sets REUSEADDR (as a socket option) to 1 on socket
  28.     server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  29.  
  30.     # Bind, so server informs operating system that it's going to use given IP and port
  31.     # For a server using 0.0.0.0 means to listen on all available interfaces, useful to connect locally to 127.0.0.1 and remotely to LAN interface IP
  32.     server_socket.bind((IP, PORT))
  33.  
  34.     # This makes server listen to new connections
  35.     server_socket.listen()
  36.  
  37.     # List of sockets for select.select()
  38.     sockets_list = [server_socket]
  39.  
  40.     # List of connected clients - socket as a key, user header and name as data
  41.     clients = {}
  42.  
  43.     print(f'Listening for connections on {IP}:{PORT}...')
  44.  
  45.     # Handles message receiving
  46.     def receive_message(client_socket):
  47.  
  48.         try:
  49.  
  50.             # Receive our "header" containing message length, it's size is defined and constant
  51.             message_header = client_socket.recv(HEADER_LENGTH)
  52.  
  53.             # If we received no data, client gracefully closed a connection, for example using socket.close() or socket.shutdown(socket.SHUT_RDWR)
  54.             if not len(message_header):
  55.                 return False
  56.  
  57.             # Convert header to int value
  58.             message_length = int(message_header.decode('utf-8').strip())
  59.  
  60.             # Return an object of message header and message data
  61.             return {'header': message_header, 'data': client_socket.recv(message_length)}
  62.  
  63.         except:
  64.  
  65.             # If we are here, client closed connection violently, for example by pressing ctrl+c on his script
  66.             # or just lost his connection
  67.             # socket.close() also invokes socket.shutdown(socket.SHUT_RDWR) what sends information about closing the socket (shutdown read/write)
  68.             # and that's also a cause when we receive an empty message
  69.             return False
  70.  
  71.     while True:
  72.  
  73.         # Calls Unix select() system call or Windows select() WinSock call with three parameters:
  74.         #   - rlist - sockets to be monitored for incoming data
  75.         #   - wlist - sockets for data to be send to (checks if for example buffers are not full and socket is ready to send some data)
  76.         #   - xlist - sockets to be monitored for exceptions (we want to monitor all sockets for errors, so we can use rlist)
  77.         # Returns lists:
  78.         #   - reading - sockets we received some data on (that way we don't have to check sockets manually)
  79.         #   - writing - sockets ready for data to be send thru them
  80.         #   - errors  - sockets with some exceptions
  81.         # This is a blocking call, code execution will "wait" here and "get" notified in case any action should be taken
  82.         read_sockets, _, exception_sockets = select.select(sockets_list, [], sockets_list)
  83.  
  84.  
  85.         # Iterate over notified sockets
  86.         for notified_socket in read_sockets:
  87.  
  88.             # If notified socket is a server socket - new connection, accept it
  89.             if notified_socket == server_socket:
  90.  
  91.                 # Accept new connection
  92.                 # That gives us new socket - client socket, connected to this given client only, it's unique for that client
  93.                 # The other returned object is ip/port set
  94.                 client_socket, client_address = server_socket.accept()
  95.  
  96.                 # Client should send his name right away, receive it
  97.                 user = receive_message(client_socket)
  98.  
  99.                 # If False - client disconnected before he sent his name
  100.                 if user is False:
  101.                     continue
  102.  
  103.                 # Add accepted socket to select.select() list
  104.                 sockets_list.append(client_socket)
  105.  
  106.                 # Also save username and username header
  107.                 clients[client_socket] = user
  108.  
  109.                 print('Accepted new connection from {}:{}, username: {}'.format(*client_address, user['data'].decode('utf-8')))
  110.  
  111.             # Else existing socket is sending a message
  112.             else:
  113.  
  114.                 # Receive message
  115.                 message = receive_message(notified_socket)
  116.  
  117.                 # If False, client disconnected, cleanup
  118.                 if message is False:
  119.                     print('Closed connection from: {}'.format(clients[notified_socket]['data'].decode('utf-8')))
  120.  
  121.                     # Remove from list for socket.socket()
  122.                     sockets_list.remove(notified_socket)
  123.  
  124.                     # Remove from our list of users
  125.                     del clients[notified_socket]
  126.  
  127.                     continue
  128.  
  129.                 # Get user by notified socket, so we will know who sent the message
  130.                 user = clients[notified_socket]
  131.  
  132.                 print(f'Received message from {user["data"].decode("utf-8")}: {message["data"].decode("utf-8")}')
  133.  
  134.                 # Iterate over connected clients and broadcast message
  135.                 for client_socket in clients:
  136.  
  137.                     # But don't sent it to sender
  138.                     if client_socket != notified_socket:
  139.  
  140.                         # Send user and message (both with their headers)
  141.                         # We are reusing here message header sent by sender, and saved username header send by user when he connected
  142.                         client_socket.send(user['header'] + user['data'] + message['header'] + message['data'])
  143.  
  144.         # It's not really necessary to have this, but will handle some socket exceptions just in case
  145.         for notified_socket in exception_sockets:
  146.  
  147.             # Remove from list for socket.socket()
  148.             sockets_list.remove(notified_socket)
  149.  
  150.             # Remove from our list of users
  151.             del clients[notified_socket]    
  152.            
  153.  
  154.  
  155.  
  156. elif option == 'client':
  157.  
  158.  
  159.  
  160.     # Python Socket Client. Lines 161-236
  161.     HEADER_LENGTH = 10
  162.  
  163.     IP = "127.0.0.1"
  164.     PORT = 1234
  165.     my_username = input("Username: ")
  166.  
  167.     # Create a socket
  168.     # socket.AF_INET - address family, IPv4, some otehr possible are AF_INET6, AF_BLUETOOTH, AF_UNIX
  169.     # socket.SOCK_STREAM - TCP, conection-based, socket.SOCK_DGRAM - UDP, connectionless, datagrams, socket.SOCK_RAW - raw IP packets
  170.     client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  171.  
  172.     # Connect to a given ip and port
  173.     client_socket.connect((IP, PORT))
  174.  
  175.     # Set connection to non-blocking state, so .recv() call won;t block, just return some exception we'll handle
  176.     client_socket.setblocking(False)
  177.  
  178.     # Prepare username and header and send them
  179.     # We need to encode username to bytes, then count number of bytes and prepare header of fixed size, that we encode to bytes as well
  180.     username = my_username.encode('utf-8')
  181.     username_header = f"{len(username):<{HEADER_LENGTH}}".encode('utf-8')
  182.     client_socket.send(username_header + username)
  183.  
  184.     while True:
  185.  
  186.         # Wait for user to input a message
  187.         message = input(f'{my_username} > ')
  188.  
  189.  
  190.         # Encode message to bytes, prepare header and convert to bytes, like for username above, then send
  191.         message = message.encode('utf-8')
  192.         message_header = f"{len(message):<{HEADER_LENGTH}}".encode('utf-8')
  193.         client_socket.send(message_header + message)
  194.  
  195.         try:
  196.             # Now we want to loop over received messages (there might be more than one) and print them
  197.             while True:
  198.  
  199.                 # Receive our "header" containing username length, it's size is defined and constant
  200.                 username_header = client_socket.recv(HEADER_LENGTH)
  201.  
  202.                 # If we received no data, server gracefully closed a connection, for example using socket.close() or socket.shutdown(socket.SHUT_RDWR)
  203.                 if not len(username_header):
  204.                     print('Connection closed by the server')
  205.                     sys.exit()
  206.  
  207.                 # Convert header to int value
  208.                 username_length = int(username_header.decode('utf-8').strip())
  209.  
  210.                 # Receive and decode username
  211.                 username = client_socket.recv(username_length).decode('utf-8')
  212.  
  213.                 # Now do the same for message (as we received username, we received whole message, there's no need to check if it has any length)
  214.                 message_header = client_socket.recv(HEADER_LENGTH)
  215.                 message_length = int(message_header.decode('utf-8').strip())
  216.                 message = client_socket.recv(message_length).decode('utf-8')
  217.  
  218.                 # Print message
  219.                 print(f'{username} > {message}')
  220.  
  221.         except IOError as e:
  222.             # This is normal on non blocking connections - when there are no incoming data error is going to be raised
  223.             # Some operating systems will indicate that using AGAIN, and some using WOULDBLOCK error code
  224.             # We are going to check for both - if one of them - that's expected, means no incoming data, continue as normal
  225.             # If we got different error code - something happened
  226.             if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
  227.                 print('Reading error: {}'.format(str(e)))
  228.                 sys.exit()
  229.  
  230.             # We just did not receive anything
  231.             continue
  232.  
  233.         except Exception as e:
  234.             # Any other exception - something happened, exit
  235.             print('Reading error: '.format(str(e)))
  236.             sys.exit()
  237.  
  238.  
  239.  
  240. else:
  241.     print("Invalid option selected")
  242.     exit()
Advertisement
Add Comment
Please, Sign In to add comment