2021-09-30 01:21:18 +02:00
|
|
|
__author__ = "Kieran Osborne"
|
|
|
|
__version__ = "0.0.1"
|
|
|
|
__status__ = "Development"
|
|
|
|
|
|
|
|
if (__name__ == "__main__"):
|
2021-09-30 02:25:55 +02:00
|
|
|
import threading
|
2021-10-01 01:16:27 +02:00
|
|
|
import chattle
|
2021-09-30 01:21:18 +02:00
|
|
|
import config
|
2021-09-30 02:25:55 +02:00
|
|
|
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.
|
2021-09-30 02:25:55 +02:00
|
|
|
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
|
2021-09-30 02:25:55 +02:00
|
|
|
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
|
|
|
|
2021-09-30 02:25:55 +02:00
|
|
|
while True:
|
2021-10-01 01:16:27 +02:00
|
|
|
# Handle each incoming connection as and when they come.
|
2021-09-30 02:25:55 +02:00
|
|
|
connection, address = client_socket.accept()
|
2021-09-30 01:21:18 +02:00
|
|
|
|
2021-10-01 01:16:27 +02:00
|
|
|
clients.add(connection)
|
2021-09-30 02:25:55 +02:00
|
|
|
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()
|