Skip to content
Snippets Groups Projects
Commit 8b6872cc authored by Christian Dietrich's avatar Christian Dietrich
Browse files

Template

parents
Branches fourth-sheet
No related tags found
No related merge requests found
### Exercise Sheet: Implementing a UDP Broadcast Chat Client
#### Context:
In this exercise, you will implement a UDP Broadcast Chat Client using Python. The chat client allows users to send and receive messages in real-time. The client is designed with a graphical user interface (GUI) using Tkinter. You will complete the implementation by filling in the missing parts of the program.
### Background on UDP and Broadcasts:
UDP (User Datagram Protocol) is a connectionless protocol that allows sending messages without establishing a connection. It is suitable for applications that need fast, efficient transmission, such as real-time communication. Broadcasting, on the other hand, sends a message to all devices on a local network. UDP broadcasts use a special IP address (`255.255.255.255`) to ensure that the message is delivered to all devices in the subnet.
### Tasks:
#### Task 1 (T1): Initialize the UDP Socket and Handle Incoming Messages
In this task, you will set up the UDP socket and ensure the client can receive messages. First, you need to initialize `self.sock` as a UDP socket. This involves creating a socket that can communicate over the network using UDP. You also need to configure this socket to enable broadcast messages and allow the port to be reused quickly. Once the socket is set up, implement the logic to receive messages from it. Use the `recvfrom` method to receive messages, then parse these messages to extract the sender's nickname and the message content. Finally, display the received messages in the chat window using the `self.print()` method.
#### Task 2 (T2): Broadcast Messages to Other Clients
The objective of this task is to enable the client to send broadcast messages to other clients. Implement the functionality to send messages as broadcasts to all clients in the network. Ensure that the messages are encoded as byte arrays before sending them. Use the socket's `sendto` method to broadcast these messages. This task ensures that all clients can communicate with each other by broadcasting their messages to the entire network.
#### Task 3 (T3): Manage and Update the Buddy List
In this task, you will maintain a list of connected peers and update the GUI accordingly. Start by keeping a record of peers who send messages, including their nicknames, addresses, and timeout values. This record should be updated whenever a message is received. Additionally, periodically send keepalive messages to inform other clients that your client is still active. Schedule the `event_tick` method to run periodically, decrementing the timeout values and removing stale peers. Finally, refresh the buddy list in the GUI to display the current list of connected peers, ensuring that the buddy list accurately reflects the active users.
#### Task 4 (T4): Implement Direct Messages
The final task is to enable sending direct messages to specific clients. Implement the ability to send a message directly to a specific client by typing "NICK: MESSAGE". This involves checking if the message contains a colon (":") to identify it as a direct message. Then, look up the recipient's address in the peer list and send the message directly to that specific address. This task ensures that users can have private conversations with specific peers, in addition to broadcasting messages to the entire network.
By completing these tasks, you will gain experience in working with UDP sockets, handling real-time communication, and managing a GUI application in Python. Happy coding!
chat.py 0 → 100755
#!/usr/bin/env python3
# coding: utf-8
import socket
import threading
import tkinter as tk
from tkinter import scrolledtext, Listbox
import sys
import signal
import logging
try:
import coloredlogs
coloredlogs.install(level='DEBUG')
except ImportError:
logging.basicConfig(level=logging.DEBUG)
class ChatClient:
"""
A simple Tkinter GUI for a chat application
"""
def __init__(self):
"""
Initializes the GUI
"""
self.root = tk.Tk()
self.root.title("UDP Broadcast Chat")
main_frame = tk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True)
chat_frame = tk.Frame(main_frame)
chat_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.chat_window = scrolledtext.ScrolledText(chat_frame, wrap=tk.WORD, state=tk.DISABLED)
self.chat_window.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
buddy_frame = tk.Frame(main_frame)
buddy_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10)
buddy_label = tk.Label(buddy_frame, text="Buddy List")
buddy_label.pack()
self.buddy_list = Listbox(buddy_frame)
self.buddy_list.pack(fill=tk.Y, expand=True)
self.entry = tk.Entry(self.root)
self.entry.pack(padx=10, pady=10, fill=tk.X)
self.entry.bind("<Return>", self.__key_enter)
# Handle Ctrl-C for clean exit
self.root.protocol("WM_DELETE_WINDOW", self.signal_handler)
signal.signal(signal.SIGINT, self.signal_handler)
def __key_enter(self, event):
"""Event handler that is called by TK inter if someone presses
<Return> in the entry line. This coupling function calls
`self.event_enter' with the entered text
Args:
event (tk.Event): The Tkinter event object.
"""
message = self.entry.get()
self.entry.delete(0, tk.END)
self.event_enter(message)
def update_buddy_list(self, buddies):
"""
Updates the buddy list on the GUI's right side.
Args:
buddies (list): List of nicknames (as strings)
"""
# Clear the existing list
self.buddy_list.delete(0, tk.END)
# Add new buddies to the list
for buddy in buddies:
self.buddy_list.insert(tk.END, buddy)
def print(self, message):
"""
Updates the chat window with a new message.
Args:
message (str): The message to display.
"""
self.chat_window.config(state=tk.NORMAL)
self.chat_window.insert(tk.END, message)
self.chat_window.config(state=tk.DISABLED)
self.chat_window.yview(tk.END)
def signal_handler(self, *args):
"""
Handles the signal for cleanly exiting the program.
"""
print("Exiting...")
self.root.quit()
sys.exit(0)
class UDPBroadcastChatClient(ChatClient):
BROADCAST_IP = '255.255.255.255'
PORT = 5000
# Timeout for the buddy list in seconds.
TIMEOUT = 10
def __init__(self, nick):
super().__init__()
self.nick = nick
self.print(f"You chat as {self.nick}\n")
# Initialize socket
# FIXME: (T1) Initialize self.sock with an UDP (SOCK_DGRAM) socket.
# FIXME: (T1) Use setsockopt to set SO_BROADCAST and SO_REUSEADDR
# to enable broadcast messages and the quick reusage of the port.
self.sock = None
if self.sock is not None:
# Register the socket for being polled. If it becomes
# readable (new data is available),
# self.event_socket_readable is called automatically
self.root.createfilehandler(self.sock, tk.READABLE, self.event_socket_readable)
# Start the self.event_tick timer
self.root.after(1000, self.event_tick)
# Map of connected peers
self.peers = dict()
def event_enter(self, message):
addr = (self.BROADCAST_IP, self.PORT)
# FIXME: (T4) Implement direct messages
# HINT: (T4) For a direct message, the user types "NICK: MESSAGE"
# HINT: (T4) Use the peer information
self.print(f"<ENTER> {addr=} {message=}\n")
# FIXME: (T2) Use `sendto' to send the message as a broadcast message to others in the network
# HINT: (T2) The data must be encoded a byte array, which you can create with .encode() from a string
def event_socket_readable(self, sock, mask):
"""
Receives messages in the background and updates the GUI.
"""
# FIXME: (T1) Use recvfrom to receive the message and parse it into nick and message part.
# HINT: (T1) The result of recvfrom is a byte array. To display it, you have to decode it as a string with .decode()
# HINT: (T1) You can display a message in the chat window with self.print()
# FIXME: (T3) Keep a record of peers for which you have received messages
# HINT: (T3) The peer records should contain: nick, address, and current timeout value
def event_tick(self):
# Schedule another execution of self.event_tick after 1000ms
self.root.after(1000, self.event_tick)
# FIXME: (T3) Decrement the peer timeout and remove stale peers.
# FIXME: (T3) Send a keep alive message every second to inform
# the others you are still here, although you have
# not typed a message.
if __name__ == "__main__":
import getpass
import sys
nick = getpass.getuser()
if len(sys.argv) > 1:
nick = sys.argv[1]
client = UDPBroadcastChatClient(nick)
client.root.mainloop()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment