"""Chat Client for Client/Server programming lab in UC Berkeley's CS61A Protocol Description: Logging In: Startup for 61AChat is a traditional three way handshake of the following format: Client -> Server "Client|Server|hello|" Server -> Client "Server|Client|welcome|" Client -> Server "Client|Server|thanks|" Messaging Another Client: To send a message to another client, one simply sends a "send-msg" message to the Server, which it forwards along to the correct recipient in a "receive-msg": Client1 -> Server "Client1|Client2|send-msg|message here" Server -> Client2 "Client1|Client2|receive-msg|message here" Logging Out: To log out, a client sends the logout message to the server: Client -> Server "Client|Server|logout|" At which point the Server removes the client from the table. In addition to removing the client upon request, we have the Listing users: To get a list of users, the client sends the server a clients-list message. The server then replies with a receive-msg message with a body which lists all users. Client -> Server "Client|Server|clients-list|" Server -> Client "Server|Client|receive-msg|" Written by Tom Magrino Updated 4/8/2012 -Client no longer blocks while waiting for user input (using select module) -More messages and exception handling -ChatClient.logout now sends logout message to server -Disallow usernames with pipe character """ from ucb import main from socket import socket, error, AF_INET, SOCK_DGRAM from chatcommon import MSG_SIZE_LIMIT, decode_message, Message from select import select import sys from threading import Thread import pig_gui from time import sleep class ChatClient(object): help_string = """Known Commands: /exit - leave the chat program /who - see who's available to chat /msg [username] - message the person with the given username /challenge [username] - challenges the person to a game of pig (games won't work initially) /accept - accepts the last challenge received /help - print this help message""" def __init__(self, username, server_address): self.username = username self.sock = socket(AF_INET, SOCK_DGRAM) self.server_address = server_address self.connected = self.connect() self.challenger = None self.challenge_accepted = False self.gui = None def connect(self): if self.sock.connect_ex(self.server_address) != 0: return False return self.handshake() def receive_message(self, blocking=False): if blocking: self.sock.settimeout(None) else: self.sock.setblocking(0.0) buff = bytearray(b" " * MSG_SIZE_LIMIT) try: self.sock.recv_into(buff) except error as e: buff = bytearray(b" " * MSG_SIZE_LIMIT) data = buff.decode().strip() if data == "": return None return decode_message(data) def send_message(self, message): s = socket(AF_INET, SOCK_DGRAM) s.connect(self.server_address) s.send(bytearray(str(message).encode())) s.close() def handshake(self): hello_msg = Message(self.username, "server", "hello", "") self.sock.send(bytearray(str(hello_msg).encode())) print("Sent hello message to server.") print("Waiting for welcome message from server.") welcome_msg = self.receive_message(blocking=True) if welcome_msg.action == "welcome": thanks_msg = Message(self.username, "server", "thanks", "") self.sock.send(bytearray(str(thanks_msg).encode())) print("Sent thanks message to server. You are now connected.") if welcome_msg.action == "welcome": self.connected = True else: print("Something went wrong!:") print(welcome_msg.body) self.sock.close() self.connected = False return self.connected def logout(self): print("Logging out...") logout_message = Message(self.username, "server", "logout", "") self.send_message(logout_message) self.connected = False def get_msgs(self): msgs = [] msg_to_me = self.receive_message() while msg_to_me: msgs.append(msg_to_me) msg_to_me = self.receive_message() return msgs def disconnect(self): print("Kill message recieved from server. Disconnecting...") self.sock.close() self.connected = False def get_command(self): return input("CS61AChat ~ {0}: ".format(self.username)).split() def display_prompt(self): print("CS61AChat ~ {0}: ".format(self.username), end="") sys.stdout.flush() def im(self, dest, body): msg = Message(self.username, dest, "send-msg", body) self.send_message(msg) def start_game(self, playerId): t = Thread(target=pig_gui.run, args=(self, playerId)) t.start() def set_gui(self, gui): self.gui = gui def send_roll(self, dice_value): self.send_message(Message(self.username, self.challenger, "_pig-roll", str(dice_value))) def send_hold(self): self.send_message(Message(self.username, self.challenger, "_pig-hold", "")) def chat_repl(self): while self.connected: #Waits until one of the input streams has data to be read r_ports, w_ports, exc_ports = select([sys.stdin, self.sock], [], []) #sys.stdin and self.sock appear in r_ports if there is data to be read #Process incoming messages if self.sock in r_ports: print() msgs = self.get_msgs() for msg in msgs: if msg.action == "kill-conn": self.disconnect() elif msg.action == "_pig-accept": self.start_game(0) print("Your turn!") continue elif msg.action == "_pig-challenge": self.challenger = msg.src print(msg.src+" challenges you! Type /accept to play pig!") elif msg.action == "_pig-roll": print(self.challenger+" rolled.") self.gui.roll(int(msg.body),send=False) elif msg.action == "_pig-hold": print(self.challenger+" held the die.") self.gui.hold(send=False) else: print("{0} says: {1}".format(msg.src, msg.body)) self.display_prompt() #Process user commands if sys.stdin in r_ports: command = sys.stdin.readline().strip().split(" ", 2) if len(command) == 0: pass elif command[0] == "/exit": self.logout() return elif command[0] == "/who": self.send_message(Message(self.username, "server", "clients-list", "")) elif command[0] == "/msg": if len(command) < 3: print("Message format: /msg ") else: dest = command[1] body = command[2] self.im(dest, body) elif command[0] == "/challenge": dest = command[1] self.send_message(Message(self.username, dest, "_pig-challenge", "")) self.challenger = dest elif command[0] == "/accept": if self.challenger: self.send_message(Message(self.username, self.challenger, "_pig-accept", "")) self.start_game(1) else: print("There is no challenge to accept!") elif command[0] == "/help": print(self.help_string) else: print("I'm sorry, I don't know that one!") if command[0] != "/who": self.display_prompt() return @main def run(*args): name = input("Please give a username: ") assert "|" not in name, "Username must not contain '|' character" server_ip = input("Please give the server ip: ") server_port = int(input("Please give the server port: ")) myclient = ChatClient(name, (server_ip, server_port)) #With select try: myclient.chat_repl() except Exception as e: print("Client exception: {0}".format(e)) myclient.logout() except KeyboardInterrupt: myclient.logout()