I am writing a chat application. Everyone who connects to the client chats with each other by writing the messages they want. But I want to add the feature of creating private groups.
client chatting After creating the private room, the room I am in should be written next to my name. After joining the room, people who do not join the room should not exchange messages. but after joining the room the program crashes. Even if I leave the room, I cannot send messages etc.
import os
import socket
import logging
import hashlib
import argparse
import threading
from datetime import datetime
from colorama import init, Fore, Style
clients = {}
clients_lock = threading.Lock()
# Groups dictionary to keep track of groups
groups = {}
groups_lock = threading.Lock()
class Group:
def __init__(self, name, password=None):
self.name = name
self.password = password # Store the hashed password
self.members = set()
def add_member(self, client_handler):
with groups_lock:
self.members.add(client_handler)
def remove_member(self, client_handler):
with groups_lock:
self.members.remove(client_handler)
def send_to_all(self, message, from_member):
with groups_lock:
for member in self.members:
if member != from_member:
member.client_socket.send(message.encode('utf-8'))
def log_setup(loglevel, logfile):
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError(f"Invalid log level: {loglevel}")
logging.basicConfig(level=numeric_level,
format="%(asctime)s [%(levelname)s] - %(message)s",
handlers=[logging.FileHandler(logfile),
logging.StreamHandler()])
class ClientHandler(threading.Thread):
def __init__(self, client_socket):
threading.Thread.__init__(self)
self.client_socket = client_socket
self.username = None
self.group = None
def run(self):
global clients
logging.info(f"New client connected: {self.client_socket.getpeername()}") # Eklenen log
# Ask for and validate the username
while True:
try:
self.client_socket.send("Enter your username: ".encode('utf-8'))
username = self.client_socket.recv(1024).decode('utf-8').strip()
with clients_lock:
if username in clients or not username:
self.client_socket.send(
"This username is already taken or invalid. Please enter a different name.".encode('utf-8'))
continue # After sending the error message, return to the beginning of the loop
else:
self.username = username
clients[self.username] = self.client_socket
self.client_socket.send("Username set successfully.".encode('utf-8'))
break
except BrokenPipeError as e:
if e.errno == 32:
pass
else:
print(f"An unknown error occurred: {e}")
return
# Process messages
try:
while True:
message = self.client_socket.recv(1024).decode('utf-8')
if message == "/userlist":
with clients_lock:
userlist = "\n".join([f"\t{i + 1}) {user}" for i, user in enumerate(clients.keys())])
response = f"Connected Users:\n{userlist}"
self.client_socket.send(response.encode('utf-8'))
continue
if message == "/help":
response = Fore.BLUE + "Help Menu:\n" \
"\t/help -> Help Menu\n" \
"\t/exit -> Exit the program.\n" \
"\t/userlist -> View the list of connected users.\n" \
"\t/dm [user] [message] -> Send a direct message to a user.\n" \
"\t/changeuser [new_username] -> Change your username.\n" \
"\t/creategroup [group_name] [password] -> Create a new group with an optional password.\n" \
"\t/joingroup [group_name] [password] -> Join an existing group with the correct password.\n" \
"\t/leavegroup -> Leave the current group.\n" \
"\t/listgroups -> List all available groups.\n" + Style.RESET_ALL
self.client_socket.send(response.encode('utf-8'))
continue
if message.startswith("/changeuser "):
_, new_username = message.split()
with clients_lock:
if new_username in clients:
self.client_socket.send(
"This username is already taken. Please choose another one.".encode('utf-8'))
else:
# Remove the old username and add the new one
del clients[self.username]
self.username = new_username
clients[self.username] = self.client_socket
self.client_socket.send(f"Username changed to {new_username}.".encode('utf-8'))
continue
if message.startswith("/dm "):
_, recipient, *dm_msg_parts = message.split()
dm_message = " ".join(dm_msg_parts)
with clients_lock:
if recipient in clients:
clients[recipient].send(f"[DM from {self.username}] {dm_message}".encode('utf-8'))
self.client_socket.send(f"[DM to {recipient}] {dm_message}".encode('utf-8'))
else:
self.client_socket.send("Specified user not found.".encode('utf-8'))
continue
""" this part is the group start start"""
if message.startswith("/creategroup "):
_, group_name, *password_parts = message.split()
password = " ".join(password_parts)
hashed_password = hashlib.sha256(password.encode('utf-8')).hexdigest() if password else None
group = Group(group_name, hashed_password)
with groups_lock:
groups[group_name] = group
group.add_member(self)
self.group = group
self.client_socket.send(f"Group '{group_name}' created.".encode('utf-8'))
continue
if message.startswith("/joingroup "):
_, group_name, *password_parts = message.split()
password = " ".join(password_parts)
hashed_password = hashlib.sha256(password.encode('utf-8')).hexdigest() if password else None
with groups_lock:
group = groups.get(group_name)
if group:
print(f"Group found: {group_name}") # Debug print
if group.password is None or group.password == hashed_password:
group.add_member(self)
self.group = group
print(f"Sending join confirmation to {self.client_socket}") # Debug print
self.client_socket.send(f"Joined group '{group_name}'.".encode('utf-8'))
else:
print(f"Password mismatch for group: {group_name}") # Debug print
self.client_socket.send("Failed to join group: Wrong password.".encode('utf-8'))
else:
print(f"Group not found: {group_name}") # Debug print
self.client_socket.send("Failed to join group: Group does not exist.".encode('utf-8'))
continue
if message == "/leavegroup":
if self.group:
self.group.remove_member(self)
self.client_socket.send(f"Left group '{self.group.name}'.".encode('utf-8'))
self.group = None
else:
self.client_socket.send("You are not in a group.".encode('utf-8'))
continue
if message == "/listgroups":
with groups_lock:
group_list = ["name".ljust(20) + "password"]
for i, (group_name, group) in enumerate(groups.items(), 1):
indicator = '*' if group.password else ''
group_list.append(f"{i}) {group_name.ljust(20)}{indicator}")
response = "Available Groups:\n\t" + "\n\t".join(group_list)
self.client_socket.send(response.encode('utf-8'))
continue
""" and this part is the group stop"""
if not message or message == "/exit":
break
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
broadcast_message = f"[{current_time}] {self.username}: {message}"
with clients_lock:
for usr, client in clients.items():
if usr != self.username:
client.send(broadcast_message.encode('utf-8'))
except:
pass
# Cleanup when the client exits
with clients_lock:
del clients[self.username]
self.client_socket.close()
def start_server(host, port):
try:
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((host, port))
host_ip, host_port = server_socket.getsockname()
server_socket.listen(5)
print("Server started. Waiting for clients...")
print(f"{Fore.YELLOW}Host information: {Style.RESET_ALL}{host_ip}:{host_port}")
logging.info(f"Server started on {host_ip}:{host_port}") # Eklenen log
while True:
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
client_socket, client_address = server_socket.accept()
print(f"[{current_time}] {client_address} Connected.")
logging.info(f"Accepted connection from {client_address}") # Eklenen log
handler = ClientHandler(client_socket)
handler.start()
except OSError as e:
if e.errno == 98:
print("Address already in use, you wild thing :D")
logging.error("Address already in use") # Eklenen log
else:
print(f"An error occurred while starting the server: {e}")
logging.error(f"An error occurred: {e}") # Eklenen log
except KeyboardInterrupt:
print("Program terminated.....")
logging.info("Server was terminated by keyboard interrupt") # Eklenen log
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Start the chat server.")
parser.add_argument("--host", default="0.0.0.0", help="The IP address to bind the server to. (Default: 0.0.0.0)")
parser.add_argument("--port", type=int, default=12345, help="The port number to bind the server to. (Default: 12345)")
parser.add_argument("--loglevel", default="INFO", choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],help="Set the logging level (Default: INFO)")
parser.add_argument("--logfile", default="server.log", help="Set the log file name. (Default: server.log")
args = parser.parse_args()
log_setup(args.loglevel, args.logfile) # Log ayarlarını başlatma
start_server(args.host, args.port)
and client.py
import socket
import argparse
import threading
from colorama import init, Fore, Style
init(autoreset=True)
def start_client(host, port):
try:
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((host, port))
message_lock = threading.Lock()
except ConnectionRefusedError as e:
if e.errno == 111:
print("Connection refused")
else:
print(f"An unknown error occurred {e}")
return
# Firstly, get the username
while True:
username_prompt = client_socket.recv(1024).decode('utf-8')
print(Fore.CYAN + username_prompt, end="")
username = input()
client_socket.send(username.encode('utf-8'))
response = client_socket.recv(1024).decode('utf-8')
if "Please enter a different name." not in response:
break
print(Fore.RED + response)
print(Fore.BLUE + "Help Menu:")
print("\t/help -> Help menu")
# Listen for messages from the server
def listen_to_server():
nonlocal username # Make sure we can modify the outer scope's username variable
while True:
data = client_socket.recv(1024).decode('utf-8')
if not data:
break
with message_lock:
# Check for username change confirmation and update if found
if "Username changed to " in data:
username = data.split("Username changed to ")[1].rstrip(".") # Extract the new username
print(f"{Fore.GREEN}\n{data}\n{Style.RESET_ALL}{username}:{Fore.YELLOW} Enter your message: {Style.RESET_ALL}",end='')
else:
print(f"{Fore.GREEN}\n{data}\n{Style.RESET_ALL}{username}:{Fore.YELLOW} Enter your message: {Style.RESET_ALL}",end='')
threading.Thread(target=listen_to_server, daemon=True).start()
while True:
try:
print(f"{username}: {Fore.YELLOW}Enter your message: {Style.RESET_ALL}", end='')
message = input()
if message == "/exit":
client_socket.send(message.encode('utf-8'))
break
client_socket.send(message.encode('utf-8'))
except ConnectionRefusedError as e:
if e.errno == 111:
print("Connection refused")
else:
print(f"An unknown error occurred {e}")
except KeyboardInterrupt:
print(Fore.RED + "\nClosing connection...")
client_socket.send("/exit".encode('utf-8'))
break
client_socket.close()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Connect to the chat server.")
parser.add_argument("--host", default="127.0.0.1", help="The server's IP address.")
parser.add_argument("--port", type=int, default=12345, help="The port number of the server.")
args = parser.parse_args()
start_client(args.host, args.port)