chattle/server.py

98 lines
3.8 KiB
Python
Raw Normal View History

2021-09-30 01:21:18 +02:00
__author__ = "Kieran Osborne"
__version__ = "0.0.1"
__status__ = "Development"
if (__name__ == "__main__"):
import threading
2021-10-01 01:16:27 +02:00
import chattle
2021-09-30 01:21:18 +02:00
import config
import socket
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
# 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:
2021-10-01 01:16:27 +02:00
# Avoid "bind() exception: OSError: [Errno 48] Address already in use" error
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
client_socket.bind((config.host, config.port))
2021-10-01 01:16:27 +02:00
client_socket.listen()
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
clients = set()
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
def broadcast(broadcasting_connection, message: str) -> None:
"""
Handles requests from a client to broadcast a message to all other connected clients but the one requesting
the broadcast.
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
:param broadcasting_connection: Originating connection that requested the message be broadcast.
:param message: Message requested to be broadcast.
:return: Nothing.
"""
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
print(message)
for client in clients:
# 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:
client.send(message.encode("utf-8"))
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
except socket.error:
# Remove clients who's connections to the server have been broken
client.close()
clients.discard(client)
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
def handle_client(client_connection, client_address) -> None:
"""
Handles a single client connection, awaiting and processing input from it as and when it comes.
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
:param client_connection: Client connection.
:param client_address: Client IP and port address tuple.
:return: Nothing.
"""
2021-09-30 17:47:26 +02:00
2021-10-01 01:16:27 +02:00
client_ip = client_address[0]
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
# Roll out the red carpet.
client_connection.send(config.server_greeting.encode("utf-8"))
2021-09-30 17:47:26 +02:00
2021-10-01 01:16:27 +02:00
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()
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
if (command):
# It's a command message!
if (command == "quit"):
clients.discard(client_connection)
broadcast(client_connection, client_ip + " disconnected")
client_connection.close()
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
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)
2021-09-30 01:21:18 +02:00
while True:
2021-10-01 01:16:27 +02:00
# Handle each incoming connection as and when they come.
connection, address = client_socket.accept()
2021-09-30 01:21:18 +02:00
2021-10-01 01:16:27 +02:00
clients.add(connection)
print(address[0] + " connected")
2021-10-01 01:16:27 +02:00
# 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()