<filename>StmayMahjong/mahjong_server.py<gh_stars>0
import socket
import sys
import argparse
import re
import signal
import queue
import threading
from pathlib import Path
from messages import messages as msg
from messages import messages_pb2


MAX_NUM_CLIENTS = 4
FILE_PATH = str(Path(__file__).absolute())
SERVER_NAME = 'SERVER'


class MahjongServer:
    """
    Mahjong Server Class
    """
    def __init__(self, host_ip, host_port):
        """
        Establish the server at the desired host IP and Port
        """
        # Handle Sigterms
        signal.signal(signal.SIGINT, self.kill_process)
        signal.signal(signal.SIGTERM, self.kill_process)
        self.kill_flag = False

        # Establish instance variables
        self.player_dict = dict()
        self.msg_queue = queue.Queue()

        # Connect to players then start main listener thread
        self.get_player_connections(host_ip, host_port)
        self.client_listener()

    def kill_process(self, *args, **kwargs):
        """
        Kills the server process by setting the kill flag
        :return: N/A
        """
        self.kill_flag = True
        for player in self.player_dict.values():
            player.kill_player()

    def get_player_connections(self, host_ip, host_port):
        """
        Waits for all the players connected to the server
        :return: N/A
        """
        tcp_msg = msg.TcpMessage(player_name=SERVER_NAME)

        # Create Socket and listen for new connections
        listening_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        listening_sock.settimeout(0.5)
        listening_sock.bind((host_ip, host_port))
        listening_sock.listen(4)
        while len(self.player_dict.values()) < MAX_NUM_CLIENTS:
            if self.kill_flag:
                break
            try:
                conn, addr = listening_sock.accept()
            except socket.timeout:
                self.check_for_disconnects()
                continue

            # Get Player Name
            # TODO: HANDLE PLAYERS WITH SAME NAME ... DONT LET THEM DO
            msg_len = int.from_bytes(conn.recv(2), byteorder='big')
            msg_header = conn.recv(msg_len)
            _, msg_data = tcp_msg.decode_message(msg_header)
            player_name = msg_data.playerName

            # Create a new player object
            self.player_dict[player_name] = Player(player_name, conn, addr, self.msg_queue)

            # Alert players of a new member joining
            tcp_msg = msg.TcpMessage(player_name=SERVER_NAME)
            self.broadcast_message(tcp_msg.create_GetNumConnectedPlayers(len(self.player_dict.values())))

        # Close socket once all players are connected
        listening_sock.close()

    def check_for_disconnects(self):
        """
        Checks to see if any players have disconnected
        :return: N/A
        """
        tcp_msg = msg.TcpMessage(player_name=SERVER_NAME)
        if len(self.player_dict.values()) == 1:
            return
        for player in self.player_dict.values():
            try:
                self.send_message(player, tcp_msg.create_Heartbeat())
            except socket.error:
                player.conn.close()
                del player
                self.broadcast_message(tcp_msg.create_GetNumConnectedPlayers(len(self.player_dict.values())))

    @staticmethod
    def send_message(player, msg):
        """
        Send a proto message to a player's client
        :param player: Player object in the Server class
        :param msg: MessageHeader proto object
        :return: N/A
        """
        msg_len = len(msg).to_bytes(2, byteorder='big')
        player.conn.send(msg_len + msg)

    def broadcast_message(self, msg):
        """
        Broadcast a message to all clients
        :param msg: MessageHeader proto object
        :return: N/A
        """
        for player in self.player_dict.values():
            self.send_message(player, msg)

    def client_listener(self):
        """
        Listens for messages from the clients
        :return:
        """
        tcp_msg = msg.TcpMessage(player_name=SERVER_NAME)
        while not self.kill_flag:
            msg_header = self.msg_queue.get(block=True)
            msg_header, msg_data = tcp_msg.decode_message(msg_header)
            self.handle_msg(msg_header, msg_data)

    def handle_msg(self, msg_header, msg_data):
        """
        Handles proto messages based on message type
        :param msg_header: Message Header Object
        :param msg_data: Mesesage Data for that message type
        :return: N/A
        """
        msgTypeEnum = messages_pb2.MessageHeader.MessageType
        handle_dict = {
        }
        msg_type = msg_header.msgType
        player = self.player_dict[msg_header.playerName]
        try:
            handle_dict[msg_type](player, msg_data)
        except Exception as error:
            print(f'{error}')


class Player:
    """
    A class to hold player information
    """
    def __init__(self, player_name, conn, addr, msg_queue):
        """
        A player object which handles the Player conn
        :param conn: TCP connection
        :param addr: IP address of player
        :param msg_queue: Queue to put received messages on
        """
        self.player_name = player_name
        self.conn = conn
        self.conn.settimeout(0.1)
        self.addr = addr

        # Create listening thread
        self.kill_flag = threading.Event()
        self.listen_thread = threading.Thread(target=self.conn_listen, args=(self.conn, msg_queue, self.kill_flag,))
        self.listen_thread.start()

    def kill_player(self):
        """
        Kills player connection
        :return: N/A
        """
        self.kill_flag.set()
        self.listen_thread.join(5)
        self.conn.close()

    @staticmethod
    def conn_listen(conn, msg_queue, kill_flag):
        """
        Thread to listen on TCP connection for Messages
        :return: N/A
        """
        while not kill_flag.is_set():
            try:
                msg_len = int.from_bytes(conn.recv(2), byteorder='big')
                msg_header = conn.recv(msg_len)
                msg_queue.put(msg_header)
            except socket.timeout:
                pass


def mahjong_server(argv):
    ip_reg = re.compile('^(?:[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}$')
    parser = argparse.ArgumentParser(argv)
    parser.add_argument("-server_addr", type=str,
                        help="IP address and port for the Mahjong Server: format=xxx.xxx.xxx.xxx:yyyy")
    args = parser.parse_args()
    server_addr = args.server_addr
    assert re.fullmatch(ip_reg, server_addr) is not None, \
        f'Improper IP Address: {server_addr}'
    server_ip, server_port = server_addr.split(':')
    assert int(server_port) <= 65535, \
        f'Invalid TCP Port Value: {server_port}'
    server = MahjongServer(server_ip, int(server_port))


if __name__ == '__main__':
    mahjong_server(sys.argv[1:])

<filename>StmayMahjong/mahjong_server.py<gh_stars>0
import socket
import sys
import argparse
import re
import signal
import queue
import threading
from pathlib import Path
from messages import messages as msg
from messages import messages_pb2


MAX_NUM_CLIENTS = 4
FILE_PATH = str(Path(__file__).absolute())
SERVER_NAME = 'SERVER'


class MahjongServer:
    """
    Mahjong Server Class
    """
    def __init__(self, host_ip, host_port):
        """
        Establish the server at the desired host IP and Port
        """
        # Handle Sigterms
        signal.signal(signal.SIGINT, self.kill_process)
        signal.signal(signal.SIGTERM, self.kill_process)
        self.kill_flag = False

        # Establish instance variables
        self.player_dict = dict()
        self.msg_queue = queue.Queue()

        # Connect to players then start main listener thread
        self.get_player_connections(host_ip, host_port)
        self.client_listener()

    def kill_process(self, *args, **kwargs):
        """
        Kills the server process by setting the kill flag
        :return: N/A
        """
        self.kill_flag = True
        for player in self.player_dict.values():
            player.kill_player()

    def get_player_connections(self, host_ip, host_port):
        """
        Waits for all the players connected to the server
        :return: N/A
        """
        tcp_msg = msg.TcpMessage(player_name=SERVER_NAME)

        # Create Socket and listen for new connections
        listening_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        listening_sock.settimeout(0.5)
        listening_sock.bind((host_ip, host_port))
        listening_sock.listen(4)
        while len(self.player_dict.values()) < MAX_NUM_CLIENTS:
            if self.kill_flag:
                break
            try:
                conn, addr = listening_sock.accept()
            except socket.timeout:
                self.check_for_disconnects()
                continue

            # Get Player Name
            # TODO: HANDLE PLAYERS WITH SAME NAME ... DONT LET THEM DO
            msg_len = int.from_bytes(conn.recv(2), byteorder='big')
            msg_header = conn.recv(msg_len)
            _, msg_data = tcp_msg.decode_message(msg_header)
            player_name = msg_data.playerName

            # Create a new player object
            self.player_dict[player_name] = Player(player_name, conn, addr, self.msg_queue)

            # Alert players of a new member joining
            tcp_msg = msg.TcpMessage(player_name=SERVER_NAME)
            self.broadcast_message(tcp_msg.create_GetNumConnectedPlayers(len(self.player_dict.values())))

        # Close socket once all players are connected
        listening_sock.close()

    def check_for_disconnects(self):
        """
        Checks to see if any players have disconnected
        :return: N/A
        """
        tcp_msg = msg.TcpMessage(player_name=SERVER_NAME)
        if len(self.player_dict.values()) == 1:
            return
        for player in self.player_dict.values():
            try:
                self.send_message(player, tcp_msg.create_Heartbeat())
            except socket.error:
                player.conn.close()
                del player
                self.broadcast_message(tcp_msg.create_GetNumConnectedPlayers(len(self.player_dict.values())))

    @staticmethod
    def send_message(player, msg):
        """
        Send a proto message to a player's client
        :param player: Player object in the Server class
        :param msg: MessageHeader proto object
        :return: N/A
        """
        msg_len = len(msg).to_bytes(2, byteorder='big')
        player.conn.send(msg_len + msg)

    def broadcast_message(self, msg):
        """
        Broadcast a message to all clients
        :param msg: MessageHeader proto object
        :return: N/A
        """
        for player in self.player_dict.values():
            self.send_message(player, msg)

    def client_listener(self):
        """
        Listens for messages from the clients
        :return:
        """
        tcp_msg = msg.TcpMessage(player_name=SERVER_NAME)
        while not self.kill_flag:
            msg_header = self.msg_queue.get(block=True)
            msg_header, msg_data = tcp_msg.decode_message(msg_header)
            self.handle_msg(msg_header, msg_data)

    def handle_msg(self, msg_header, msg_data):
        """
        Handles proto messages based on message type
        :param msg_header: Message Header Object
        :param msg_data: Mesesage Data for that message type
        :return: N/A
        """
        msgTypeEnum = messages_pb2.MessageHeader.MessageType
        handle_dict = {
        }
        msg_type = msg_header.msgType
        player = self.player_dict[msg_header.playerName]
        try:
            handle_dict[msg_type](player, msg_data)
        except Exception as error:
            print(f'{error}')


class Player:
    """
    A class to hold player information
    """
    def __init__(self, player_name, conn, addr, msg_queue):
        """
        A player object which handles the Player conn
        :param conn: TCP connection
        :param addr: IP address of player
        :param msg_queue: Queue to put received messages on
        """
        self.player_name = player_name
        self.conn = conn
        self.conn.settimeout(0.1)
        self.addr = addr

        # Create listening thread
        self.kill_flag = threading.Event()
        self.listen_thread = threading.Thread(target=self.conn_listen, args=(self.conn, msg_queue, self.kill_flag,))
        self.listen_thread.start()

    def kill_player(self):
        """
        Kills player connection
        :return: N/A
        """
        self.kill_flag.set()
        self.listen_thread.join(5)
        self.conn.close()

    @staticmethod
    def conn_listen(conn, msg_queue, kill_flag):
        """
        Thread to listen on TCP connection for Messages
        :return: N/A
        """
        while not kill_flag.is_set():
            try:
                msg_len = int.from_bytes(conn.recv(2), byteorder='big')
                msg_header = conn.recv(msg_len)
                msg_queue.put(msg_header)
            except socket.timeout:
                pass


def mahjong_server(argv):
    ip_reg = re.compile('^(?:[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}$')
    parser = argparse.ArgumentParser(argv)
    parser.add_argument("-server_addr", type=str,
                        help="IP address and port for the Mahjong Server: format=xxx.xxx.xxx.xxx:yyyy")
    args = parser.parse_args()
    server_addr = args.server_addr
    assert re.fullmatch(ip_reg, server_addr) is not None, \
        f'Improper IP Address: {server_addr}'
    server_ip, server_port = server_addr.split(':')
    assert int(server_port) <= 65535, \
        f'Invalid TCP Port Value: {server_port}'
    server = MahjongServer(server_ip, int(server_port))


if __name__ == '__main__':
    mahjong_server(sys.argv[1:])

Mahjong Server Class Establish the server at the desired host IP and Port # Handle Sigterms # Establish instance variables # Connect to players then start main listener thread Kills the server process by setting the kill flag         :return: N/A Waits for all the players connected to the server         :return: N/A # Create Socket and listen for new connections # Get Player Name # TODO: HANDLE PLAYERS WITH SAME NAME ... DONT LET THEM DO # Create a new player object # Alert players of a new member joining # Close socket once all players are connected Checks to see if any players have disconnected         :return: N/A Send a proto message to a player's client         :param player: Player object in the Server class         :param msg: MessageHeader proto object         :return: N/A Broadcast a message to all clients         :param msg: MessageHeader proto object         :return: N/A Listens for messages from the clients         :return: Handles proto messages based on message type         :param msg_header: Message Header Object         :param msg_data: Mesesage Data for that message type         :return: N/A A class to hold player information A player object which handles the Player conn         :param conn: TCP connection         :param addr: IP address of player         :param msg_queue: Queue to put received messages on # Create listening thread Kills player connection         :return: N/A Thread to listen on TCP connection for Messages         :return: N/A