diff --git a/client.py b/client.py new file mode 100755 index 00000000..9c8eb4d3 --- /dev/null +++ b/client.py @@ -0,0 +1,76 @@ +import socket +import threading +import time +import sys +import os +import errno + + +if len(sys.argv) != 3: + print("Select IP and server port!") + sys.exit() + +client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +client_socket.connect((sys.argv[1], int(sys.argv[2]))) +client_socket.setblocking(False) + +my_nick = input("Your nickname: ") +nickname = my_nick.encode('ascii') +nickname_header = len(nickname).to_bytes(5, byteorder='big') +client_socket.send(nickname_header + nickname) + + +def get_package(sock): + header = sock.recv(5) + + if not len(header): + print('Connection was closed by server') + client_socket.close() + os._exit(0) + + length = int.from_bytes(header, byteorder='big', signed=False) + + return sock.recv(length).decode('ascii') + + +def receiving(): + while True: + try: + nick = get_package(client_socket) + message = get_package(client_socket) + + if message == f'Nickname {nick} is already in use!': + print(message) + print('Please log in to the chat again with a new nickname') + client_socket.close() + os._exit(0) + + print('<{}> [{}] {}'.format(time.strftime('%H:%M', time.localtime()), nick, message)) + + # for blocking sockets + except IOError as e: + if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK: + print('Reading error: {}'.format(str(e))) + client_socket.close() + sys.exit() + + +def sending(): + while True: + try: + message = input() + + if message: + enc_message = message.encode('ascii') + message_header = len(enc_message).to_bytes(5, byteorder='big') + client_socket.send(message_header + enc_message) + # print('<{}> [{}] {}'.format(time.strftime('%H:%M', time.localtime()), my_nick, message)) + except KeyboardInterrupt: + os._exit(0) + + +receive_thread = threading.Thread(target=receiving) +receive_thread.daemon = True +receive_thread.start() + +sending() diff --git a/server.py b/server.py new file mode 100755 index 00000000..948cfda7 --- /dev/null +++ b/server.py @@ -0,0 +1,91 @@ +import socket +import select +import sys + + +if len(sys.argv) != 3: + print("Select IP and port") + sys.exit() + + +ip = sys.argv[1] +port = int(sys.argv[2]) + + +server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +server.bind((ip, port)) +server.listen() +sockets_list = [server] # list of sockets +clients = {} # list of connected clients +print(f'Server online') + + +def broadcast(sock, nick, msg, header): + for tmp in clients: + if tmp != sock: + tmp.send(nick['header'] + nick['data'] + header + msg) + + +def receive_message(sock): + try: + header = sock.recv(5) + except ConnectionResetError: + return False + + if not len(header): + return False + + length = int.from_bytes(header, byteorder='big', signed=False) + + return {'header': header, 'data': sock.recv(length)} + + +def close_connection(sock): + nick = clients[sock] + print(f"Connection from {clients[sock]['data'].decode('ascii')} was closed") + data = f"{nick['data'].decode('ascii')} left the chat".encode('ascii') + header = len(data).to_bytes(5, byteorder='big') + + broadcast(some_socket, nick, data, header) + + sockets_list.remove(sock) + del clients[sock] + + +while True: + read_sockets, _, exception_sockets = select.select(sockets_list, [], sockets_list) + + for some_socket in read_sockets: + if some_socket == server: + client_socket, client_address = server.accept() + nickname = receive_message(client_socket) + + if nickname is False: + continue + + if nickname in clients.values(): + ms = f"Nickname {nickname['data'].decode('ascii')} is already in use!".encode('ascii') + header = len(ms).to_bytes(5, byteorder='big') + client_socket.send(nickname['header'] + nickname['data'] + header + ms) + continue + + sockets_list.append(client_socket) + clients[client_socket] = nickname + + print(f"{nickname['data'].decode('ascii')} was connected from {client_address}") + + message = f"{nickname['data'].decode('ascii')} joined!".encode('ascii') + message_header = len(message).to_bytes(5, byteorder='big') + + broadcast(client_socket, nickname, message, message_header) + + else: + message = receive_message(some_socket) + + if message is False: + close_connection(some_socket) + continue + + nickname = clients[some_socket] + broadcast(some_socket, nickname, message['data'], message['header'])