Add usernames and command messages

This commit is contained in:
Kayomn 2021-10-01 00:16:27 +01:00
parent e95e2e984d
commit c6266c1cae
3 changed files with 89 additions and 39 deletions

View File

@ -3,11 +3,13 @@ __version__ = "0.0.1"
__status__ = "Development" __status__ = "Development"
if (__name__ == "__main__"): if (__name__ == "__main__"):
import chattle
import config import config
import socket import socket
import select import select
import sys import sys
username = input("username: ")
address = (config.host, config.port) address = (config.host, config.port)
print("Starting connection to", address) print("Starting connection to", address)
@ -16,20 +18,31 @@ if (__name__ == "__main__"):
server_socket.setblocking(False) server_socket.setblocking(False)
server_socket.connect_ex(address) server_socket.connect_ex(address)
# Input may be written by the user or received from the server. Both cases have to be handled.
inputs = [sys.stdin, server_socket] inputs = [sys.stdin, server_socket]
while True: while True:
# Listen for input.
readable_io, _, _ = select.select(inputs, [], []) readable_io, _, _ = select.select(inputs, [], [])
for io in readable_io: for io in readable_io:
if (io == server_socket): if (io == server_socket):
print(io.recv(4096)) response_data = io.recv(config.message_max)
if not response_data:
exit(0)
print(response_data.decode("utf-8"))
elif (io == sys.stdin): elif (io == sys.stdin):
message = sys.stdin.readline() # Messages produced by this client are written to the terminal locally, rather than sending it to
# server to then receive it back.
line = sys.stdin.readline()
server_socket.send(message.encode("utf-8")) if not line.startswith("/"):
sys.stdout.write("<You> ") sys.stdout.write("<You> ")
sys.stdout.write(message) sys.stdout.write(line)
sys.stdout.flush() sys.stdout.flush()
server_socket.send(chattle.encode_message(username, line))

View File

@ -2,5 +2,7 @@ __author__ = "Kieran Osborne"
__version__ = "0.0.1" __version__ = "0.0.1"
__status__ = "Development" __status__ = "Development"
server_greeting = "Welcome to Chattle"
message_max = 4096
host = "127.0.0.1" host = "127.0.0.1"
port = 12345 port = 12345

View File

@ -4,59 +4,94 @@ __status__ = "Development"
if (__name__ == "__main__"): if (__name__ == "__main__"):
import threading import threading
import chattle
import config import config
import socket import socket
# Sockets don't automatically close on their own, making a with statement is necessary.
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
# Avoid bind() exception: OSError: [Errno 48] Address already in use # Avoid "bind() exception: OSError: [Errno 48] Address already in use" error
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
client_socket.bind((config.host, config.port)) client_socket.bind((config.host, config.port))
client_socket.listen(100) client_socket.listen()
clients = [] clients = set()
def spawn_client(client_connection, client_address): def broadcast(broadcasting_connection, message: str) -> None:
client_connection.send("Welcome to this chatroom!".encode("utf-8")) """
Handles requests from a client to broadcast a message to all other connected clients but the one requesting
the broadcast.
try: :param broadcasting_connection: Originating connection that requested the message be broadcast.
print("Waiting for data") :param message: Message requested to be broadcast.
data = client_connection.recv(4096) :return: Nothing.
"""
while data:
message = f"<{client_address[0]}> {data}"
print(message) print(message)
print("Propagating to clients")
for client in clients: for client in clients:
if client != client_connection: # No point broadcasting the message that the client sent the server back to the same client, it already
# has its own local copy since it was the one that wrote it.
if client != broadcasting_connection:
try: try:
client.send(message.encode("utf-8")) client.send(message.encode("utf-8"))
except: except socket.error:
# Remove clients who's connections to the server have been broken
client.close() client.close()
clients.discard(client)
# if the link is broken, we remove the client def handle_client(client_connection, client_address) -> None:
if client_connection in clients: """
clients.remove(client_connection) Handles a single client connection, awaiting and processing input from it as and when it comes.
print("Finished propagating") :param client_connection: Client connection.
:param client_address: Client IP and port address tuple.
:return: Nothing.
"""
print("Waiting for data") client_ip = client_address[0]
data = client_connection.recv(4096)
if client_connection in clients: # Roll out the red carpet.
print("dead") client_connection.send(config.server_greeting.encode("utf-8"))
clients.remove(client_connection)
try:
# Handle each incoming message from the connected client.
data = client_connection.recv(config.message_max)
while data:
message = chattle.Message(data)
command = message.as_command()
if (command):
# It's a command message!
if (command == "quit"):
clients.discard(client_connection)
broadcast(client_connection, client_ip + " disconnected")
client_connection.close()
except:
return return
else:
client_connection.send(f"Unknown command: {command}".encode("utf-8"))
else:
# It's a regular message
broadcast(client_connection, f"<{message.author}@{client_ip}> {message.body}")
data = client_connection.recv(config.message_max)
except socket.timeout:
print(client_ip + " timed out")
client_connection.close()
clients.discard(client_connection)
while True: while True:
# Handle each incoming connection as and when they come.
connection, address = client_socket.accept() connection, address = client_socket.accept()
clients.append(connection) clients.add(connection)
# prints the address of the user that just connected
print(address[0] + " connected") print(address[0] + " connected")
threading.Thread(target=spawn_client, args=(connection, address)).start() # Once a connection has been received it can be sent off to be handled elsewhere and this loop continues to
# handle connecting users.
threading.Thread(target=handle_client, args=(connection, address)).start()