示例#1
0
    def __start(self, ip: str, port: int):
        """
        Start the server.
        Binds the server to the given server address on init.

        :param str ip: server ip to bind too
        :param int port: server port to bind too
        """

        # create a socket for the server as an IPv4 (AF_INET) using byte streams (SOCK_STREAM)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # allow the socket to reuse addresses already in use so that when
        # running program multiple times, the socket address does not need to be changed
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        # bind the server to the param server address
        sock.bind((ip, port))
        # start TCP listener for server socket
        sock.listen()

        # tell peer their server is running successfully, and on which address
        print(Style.info(f'Your server is listening for connections on {ip}:{port}.'))

        # continuously waiting for clients to connect to server
        while True:
            # on connect, extract the clients socket and their address
            client_socket, client_addr = sock.accept()

            # print(Style.info(f'A new peer [{client_addr[0]}:{client_addr[1]}] connected via your server'))

            # if servers peer is coordinator, handle connection
            if self.peer.is_coordinator():
                # add client to list of connected clients
                self.connections.append(client_socket)

                # tell peer connection succeeded
                msg_body = 'connected'
                message = Message('', 'SYSTEM', msg_body)
                client_socket.send(message.get_encoded())

                # create dedicated thread to start receiving messages from connecting client
                client_thread = threading.Thread(target=self.receive, args=(client_socket, ))
                # client thread is a daemon as it shouldn't prevent program from exiting
                client_thread.daemon = True
                # start the thread
                client_thread.start()

                # create thread to ping all peers and send updated list to connected clients
                ping_thread = threading.Thread(target=self.__ping_peers)
                ping_thread.daemon = True
                ping_thread.start()
            else:
                # this server isn't coordinator, so tell peer coordinator address to connect too
                msg_body = 'coordinator:' + json.dumps(self.peer.get_chat_coord())
                message = Message('', 'SYSTEM', msg_body)
                client_socket.send(message.get_encoded())
示例#2
0
    def __ping_peers(self):
        """
        Ping all peers
        Sends 'ping' sys message to all peers, checking for 'pong' response
        """
        # create ping message instance
        message = Message('', 'SYSTEM', msg_body='ping')
        # save time of ping
        ping_time = math.floor(time.time())
        # ping each connected client
        for connection in self.connections:
            # send ping message
            connection.send(message.get_encoded())

        # wait ping timeout seconds
        time.sleep(1)

        # check which peers pong'ed back
        dead_peers = []
        for peer_id, peer_data in self.peer.get_chat_peers().items():
            # get the last successful ping time of loop peer
            last_successful_ping = peer_data['last_successful_ping']
            # if the last successful ping response was earlier than this ping started
            if last_successful_ping < ping_time:
                # they didn't pong back, so they're inactive
                dead_peers.append(peer_id)

        # for each inactive peer
        for peer_id in dead_peers:
            # remove them from the chat peers list
            self.peer.remove_chat_peer(peer_id)

            # broadcast disconnect message (including remaining chat members)
            msg_body = 'output:' + (
                Style.error('- ' + peer_data['username'] + ' disconnected') +
                Style.info(' [Chat Members: ' + self.peer.get_chat_peers_str() + ']')
            )
            disconnect_msg = Message('', 'SYSTEM', msg_body)
            for connection in self.connections:
                # send disconnect message to each connected client
                connection.send(disconnect_msg.get_encoded())

        # send the remaining peers list to each client
        self.__send_peers()
示例#3
0
                chat_peers = peer.get_chat_peers()

                earliest_join_id = None
                for peer_id, peer_data in chat_peers.items():
                    # loop peer isn't the old chat coordinator
                    if not peer_data['is_coord']:
                        if not earliest_join_id:
                            earliest_join_id = peer_id
                        elif peer_data['joined_at'] < chat_peers[earliest_join_id]['joined_at']:
                            earliest_join_id = peer_id

                if earliest_join_id:
                    # found new peer to be coordinator
                    earliest_join_peer = chat_peers[earliest_join_id]
                    new_server_ip, new_server_port = earliest_join_peer['server_addr']
                    print(Style.info(f'Attempting to make {earliest_join_peer["username"]} the new chat coordinator'))
                else:
                    # no other peers to be coord, make own server coord
                    print(Style.info('Attempting to make you the new chat coordinator...'))
                    new_server_ip, new_server_port = peer.get_server_addr()

                peer.set_chat_coord(new_server_ip, new_server_port)
                peer.set_chat_peers({})


                for retries in range(5):
                    # random delay so less chance of all members trying connection at same time
                    rand_delay = random.uniform(0.0, 5.0)
                    time.sleep(rand_delay)

                    try:
示例#4
0
    def receive(self, client_sock: socket.socket):
        """
        Receive data from client.
        Loops continuously in dedicated thread, listening for data from client.

        :param socket.socket client_sock: the connected clients socket to receive from
        """
        while True:
            # header has 3 parts (client id, message type, message length)
            message_header = client_sock.recv(Message.header_length)

            # if blank header, client has disconnected
            if not message_header:
                # remove from connected clients list
                self.connections.remove(client_sock)

                # ping all peers to see who disconnected
                # (called in separate thread so program can continue)
                ping_thread = threading.Thread(target=self.__ping_peers)
                ping_thread.daemon = True
                ping_thread.start()

                # break out of receiving loop as no longer need to receive from this client
                break

            # extract 3 parts of header
            msg_client_id, msg_type, msg_length = Message.decode_msg_header(message_header)

            if msg_length > 0:
                # received message from server
                msg_body = client_sock.recv(msg_length).decode('utf-8')
                # instantiate Message with received data
                message = Message(msg_client_id, msg_type, msg_body)

                if message.get_type() == 'SYSTEM':
                    # system messages should only be handled by coord's server
                    if self.peer.is_coordinator():
                        if message.get_body() == 'pong':
                            # server has pinged client, and client returned successfully with 'pong'
                            # update last_successful_ping time of peer
                            self.peer.successful_ping(message.get_client_id())

                        else:
                            msg_subtype = message.get_body().split(':')[0]
                            msg_body_start = len(msg_subtype) + 1
                            msg_body = message.get_body()[msg_body_start:]
                            if msg_subtype == 'auth':
                                # peer is authenticating them-self with their id and username
                                auth_data = json.loads(msg_body)
                                client_peer_id = message.get_client_id()
                                client_peer_data = {
                                    'username': auth_data['username'],
                                    'server_addr': auth_data['server_addr']
                                }
                                # save peer to peer dict
                                self.peer.add_chat_peer(client_peer_id, client_peer_data)

                                # broadcast join message
                                msg_body = 'output:' + (
                                    Style.success('+ ' + client_peer_data['username'] + ' connected') +
                                    Style.info(' [Chat Members: ' + self.peer.get_chat_peers_str() + ']')
                                )
                                connect_msg = Message('', 'SYSTEM', msg_body)
                                for connection in self.connections:
                                    # send join message to all connected clients
                                    connection.send(connect_msg.get_encoded())

                elif message.get_type() == 'CHAT':
                    # server received chat message
                    encoded_msg = message.get_encoded()

                    for connection in self.connections:
                        # send too all connected clients
                        connection.send(encoded_msg)