Add usernames and command messages
This commit is contained in:
parent
e95e2e984d
commit
c6266c1cae
25
client.py
25
client.py
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
101
server.py
101
server.py
|
@ -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.
|
||||||
|
|
||||||
|
:param broadcasting_connection: Originating connection that requested the message be broadcast.
|
||||||
|
:param message: Message requested to be broadcast.
|
||||||
|
:return: Nothing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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"))
|
||||||
|
|
||||||
|
except socket.error:
|
||||||
|
# Remove clients who's connections to the server have been broken
|
||||||
|
client.close()
|
||||||
|
clients.discard(client)
|
||||||
|
|
||||||
|
def handle_client(client_connection, client_address) -> None:
|
||||||
|
"""
|
||||||
|
Handles a single client connection, awaiting and processing input from it as and when it comes.
|
||||||
|
|
||||||
|
:param client_connection: Client connection.
|
||||||
|
:param client_address: Client IP and port address tuple.
|
||||||
|
:return: Nothing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
client_ip = client_address[0]
|
||||||
|
|
||||||
|
# Roll out the red carpet.
|
||||||
|
client_connection.send(config.server_greeting.encode("utf-8"))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print("Waiting for data")
|
# Handle each incoming message from the connected client.
|
||||||
data = client_connection.recv(4096)
|
data = client_connection.recv(config.message_max)
|
||||||
|
|
||||||
while data:
|
while data:
|
||||||
message = f"<{client_address[0]}> {data}"
|
message = chattle.Message(data)
|
||||||
|
command = message.as_command()
|
||||||
|
|
||||||
print(message)
|
if (command):
|
||||||
print("Propagating to clients")
|
# It's a command message!
|
||||||
|
if (command == "quit"):
|
||||||
|
clients.discard(client_connection)
|
||||||
|
broadcast(client_connection, client_ip + " disconnected")
|
||||||
|
client_connection.close()
|
||||||
|
|
||||||
for client in clients:
|
return
|
||||||
if client != client_connection:
|
|
||||||
try:
|
|
||||||
client.send(message.encode("utf-8"))
|
|
||||||
|
|
||||||
except:
|
else:
|
||||||
client.close()
|
client_connection.send(f"Unknown command: {command}".encode("utf-8"))
|
||||||
|
|
||||||
# if the link is broken, we remove the client
|
else:
|
||||||
if client_connection in clients:
|
# It's a regular message
|
||||||
clients.remove(client_connection)
|
broadcast(client_connection, f"<{message.author}@{client_ip}> {message.body}")
|
||||||
|
|
||||||
print("Finished propagating")
|
data = client_connection.recv(config.message_max)
|
||||||
|
|
||||||
print("Waiting for data")
|
except socket.timeout:
|
||||||
data = client_connection.recv(4096)
|
print(client_ip + " timed out")
|
||||||
|
client_connection.close()
|
||||||
if client_connection in clients:
|
clients.discard(client_connection)
|
||||||
print("dead")
|
|
||||||
clients.remove(client_connection)
|
|
||||||
|
|
||||||
except:
|
|
||||||
return
|
|
||||||
|
|
||||||
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()
|
||||||
|
|
Loading…
Reference in New Issue