123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- #!/usr/bin/env python3
- import configparser
- import threading
- import socket
- import argparse
- import os
- import json
- import random
- import time
- titles = [
- "Python",
- "3",
- "JavaScript",
- "Hamburger",
- "Wieloryb",
- "Szkoła",
- "Uniwersytet",
- "Remiza",
- "Pomidor",
- "Radość",
- "Młot",
- "Baran",
- "Hiacynt",
- "Jedzenie w samolocie",
- "Rozpacz",
- "Ciasto",
- "Słońce",
- "Pantofel",
- "Widmo",
- "Dama",
- "Krawędź",
- "Japonki",
- "Zamek",
- "Szmalcownik",
- "Bazy danych",
- "Szarada",
- "Głaz",
- "Piwnica",
- "Pióro",
- "Rachunek prawdopodobieństwa",
- "Jabłko",
- "Granat",
- "Przystań",
- "Sumo",
- "Baba",
- "Tygrys",
- "Staw",
- "Oliwa",
- "Grzebień",
- "Kuba",
- "Piła",
- "Polka",
- "Warta",
- "Róża",
- "Kosa",
- "Narcyz",
- "Zebra",
- "Kapelusz",
- "Kule",
- "Mars",
- "Kiwi",
- "Mysz"
- ]
- class Server(threading.Thread):
- """
- Supports management of server connections.
- Attributes:
- connections (list): A list of ServerSocket objects representing the active connections.
- host (str): The IP address of the listening socket.
- port (int): The port number of the listening socket.
- """
- def __init__(self, host, port):
- super().__init__()
- self.config = configparser.ConfigParser()
- self.config.read("config.ini", "UTF8")
- self.connections = []
- self.host = host
- self.port = port
- # self.players = []
- self.data = {}
- self.data["title"] = "undefined"
- self.data["state"] = "wait"
- self.data["users"] = {}
- self.data["messages"] = {}
- self.data["scores"] = {}
- self.data["total_scores"] = {}
- self.votes = {}
- def reset(self):
- time.sleep(10)
- self.votes = {}
- self.data["scores"] = {}
- self.data["messages"] = {}
- self.data["title"] = random.choice(titles)
- self.data["state"] = "wait"
- if self.player_count() >= 3:
- self.data["state"] = "game"
- else:
- self.data["state"] = "wait"
- dump = json.dumps(self.data, ensure_ascii=False)
- self.broadcast_all(dump)
-
- def run(self):
- """
- Creates the listening socket. The listening socket will use the SO_REUSEADDR option to
- allow binding to a previously-used socket address. This is a small-scale application which
- only supports one waiting connection at a time.
- For each new connection, a ServerSocket thread is started to facilitate communications with
- that particular client. All ServerSocket objects are stored in the connections attribute.
- """
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- sock.bind((self.host, self.port))
- sock.listen(1)
- print('Listening at', sock.getsockname())
- idx = 0
- try:
- while True:
- # Accept new connection
- sc, sockname = sock.accept()
- print('{} has joined'.format(idx))
- # Create new thread
- server_socket = ServerSocket(sc, sockname, self, idx)
- idx += 1
- # Start new thread
- server_socket.start()
- # Add thread to active connections
- self.connections.append(server_socket)
- except KeyboardInterrupt:
- self.quit()
- def quit(self):
- with open("config.ini", 'w') as config_file:
- self.config.write(config_file)
- os._exit(0)
- def broadcast(self, message, source):
- """
- Sends a message to all connected clients, except the source of the message.
- Args:
- message (str): The message to broadcast.
- source (tuple): The socket address of the source client.
- """
- for connection in self.connections:
- # Send to all connected clients except the source client
- if connection.sockname != source:
- connection.send(message)
- def broadcast_all(self, message):
- """
- Sends a message to all connected clients
- Args:
- message (str): The message to broadcast.
- """
- for connection in self.connections:
- connection.send(message)
-
- def remove_connection(self, connection):
- """
- Removes a ServerSocket thread from the connections attribute.
- Args:
- connection (ServerSocket): The ServerSocket thread to remove.
- """
- self.connections.remove(connection)
- def player_count(self):
- return len(self.connections)
- class ServerSocket(threading.Thread):
- """
- Supports communications with a connected client.
- Attributes:
- sc (socket.socket): The connected socket.
- sockname (tuple): The client socket address.
- server (Server): The parent thread.
- """
- def __init__(self, sc, sockname, server, id):
- super().__init__()
- self.sc = sc
- self.id = id
- self.sockname = sockname
- self.server = server
- self.username = ""
- def call_quit(self):
- print('{} has left'.format(self.id))
- self.server.data["users"].pop(self.id, None)
- self.server.data["messages"].pop(self.id, None)
- self.server.data["scores"].pop(self.id, None)
- self.server.data["total_scores"].pop(self.id, None)
- self.server.votes.pop(self.id, None)
- self.sc.close()
- server.remove_connection(self)
- if self.server.player_count() == 0:
- self.server.data["state"] = "wait"
- def run(self):
- """
- Receives data from the connected client and broadcasts the message to all other clients.
- If the client has left the connection, closes the connected socket and removes itself
- from the list of ServerSocket threads in the parent Server thread.
- """
- try:
- while True:
- message = self.sc.recv(1024).decode('utf8')
- if not message:
- self.call_quit()
- return
- elif message:
- if self.username == "":
- self.username = message
- self.server.data["users"][self.id] = self.username
- if len(self.server.data["users"]) >= 3 and self.server.data["state"] == "wait":
- self.server.data["title"] = random.choice(titles)
- self.server.data["state"] = "game"
- dump = json.dumps(self.server.data, ensure_ascii=False)
- self.server.broadcast(dump, self.sockname)
- data_copy = self.server.data.copy()
- data_copy["playerid"] = self.id
- dump = json.dumps(data_copy, ensure_ascii=False)
- self.send(dump)
- elif self.server.data["state"] == "game":
- self.server.data["messages"][self.id] = message
- if len(self.server.data["messages"]) == self.server.player_count():
- self.server.data["state"] = "vote"
- dump = json.dumps(self.server.data, ensure_ascii=False)
- self.server.broadcast_all(dump)
- elif self.server.data["state"] == "vote":
- self.server.votes[self.id] = int(message)
- print(self.server.votes)
- if len(self.server.votes) == self.server.player_count():
- for key in self.server.votes:
- player = self.server.votes[key]
- if player not in self.server.data["scores"]:
- self.server.data["scores"][player] = 0
- self.server.data["scores"][player] += 1
- for player in self.server.data["scores"]:
- name = self.server.data["users"][player]
- print(name)
- if player not in self.server.data["total_scores"]:
- if name in self.server.config["DEFAULT"]:
- self.server.data["total_scores"][player] = int(self.server.config["DEFAULT"][name])
- else:
- self.server.data["total_scores"][player] = 0
- self.server.config["DEFAULT"][name] = "0"
- self.server.data["total_scores"][player] += self.server.data["scores"][player]
- self.server.config["DEFAULT"][name] = str(int(int(self.server.config["DEFAULT"][name]) + int(self.server.data["scores"][player])))
- self.server.data["state"] = "display"
- dump = json.dumps(self.server.data, ensure_ascii=False)
- self.server.broadcast_all(dump)
- with open("config.ini", 'w') as config_file:
- self.server.config.write(config_file)
- self.server.reset()
- #print(self.server.data)
- except KeyboardInterrupt or EOFError:
- print("Caught keyboard interrupt, exiting")
- call_quit()
- os._exit(0)
- except ConnectionResetError:
- self.call_quit()
-
- def send(self, message):
- """
- Sends a message to the connected server.
- Args:
- message (str): The message to be sent.
- """
- self.sc.sendall(message.encode('utf8'))
- def quit(server):
- print('Closing all connections...')
- for connection in server.connections:
- connection.sc.close()
- print('Shutting down the server...')
- os._exit(0)
- def exit(server):
- """
- Allows the server administrator to shut down the server.
- Typing 'q' in the command line will close all active connections and exit the application.
- """
- try:
- while True:
- input('')
- except KeyboardInterrupt:
- quit(server)
- except EOFError:
- quit(server)
- if __name__ == '__main__':
- parser = argparse.ArgumentParser(description='Chatroom Server')
- parser.add_argument('host', help='Interface the server listens at')
- parser.add_argument('-p', metavar='PORT', type=int, default=1060,
- help='TCP port (default 1060)')
- args = parser.parse_args()
- # Create and start server thread
- server = Server(args.host, args.p)
- server.start()
- exit = threading.Thread(target = exit, args = (server,))
- exit.start()
|