"""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 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 /help - print this help message /broadcast - sends a broadcast message /me - sets your 'status /block - blocks a user /unblock - unblocks a user /block-list - see the block list'""" 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.blocked = [] 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 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 == "me": print(msg.body) elif msg.src not in self.blocked: 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] == "/help": print(self.help_string) elif command[0] == "/broadcast": if len(command) < 2: print("Message format: /broadcast ") else: body = " ".join(command[1:]) self.send_message(Message(self.username, "server", "broadcast", body)) elif command[0] == '/me': if len(command) < 2: print("Message format: /me ") else: status = " ".join(command[1:]) self.send_message(Message(self.username, "server", "me", status)) elif command[0] == "/block": if command[1] not in self.blocked: self.blocked.append(command[1]) elif command[0] == "/unblock": if command[1] in self.blocked: del self.blocked[self.blocked.index(command[1])] else: print("Error: Tried to unblock a user that wasn't blocked!") elif command[0] == "/block-list": print("Blocked: {}".format(self.blocked)) 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()