这个软件是一个基于Python的简单聊天客户端和服务器程序,主要用于实现多用户即时聊天功能。它使用了Python的socket
库来处理网络通信,以及tkinter
库来构建图形用户界面(GUI)。以下是软件的主要功能和特点的详细介绍:
用户登录与身份管理:
用户在首次启动客户端时需要输入一个昵称,昵称将被保存到本地文件(identity.txt
),下次启动时会自动加载该昵称。
用户可以通过“注销”按钮退出当前账号,同时删除本地保存的昵称文件。
聊天功能:
用户可以发送消息到聊天区域,消息会显示在聊天文本框中。
支持群聊功能,所有用户发送的消息都会广播到所有在线用户。
通讯录管理:
通讯录区域显示了“世界群”和用户的好友列表。
用户可以通过双击在线用户列表中的用户名称,将该用户添加为好友,好友会自动出现在通讯录中。
如果好友注销,服务器会通知客户端从通讯录中移除该好友。
在线用户管理:
用户可以通过点击“全部”按钮,请求服务器获取当前所有在线用户的列表,并显示在在线用户列表区域。
在线用户列表区域支持双击操作,用户可以将在线用户添加为好友。
用户管理:
服务器维护一个在线用户列表,记录所有连接到服务器的用户昵称。
当用户连接时,服务器会提示用户输入昵称,并将昵名加入在线用户列表。
当用户断开连接时,服务器会从在线用户列表中移除该用户,并通知所有客户端。
消息广播:
服务器接收来自客户端的消息,并将消息广播到所有连接的客户端。
支持特殊指令,如GET_USERS
(获取在线用户列表)、ADD_FRIEND
(添加好友)和REMOVE_FRIEND
(移除好友)。
使用tkinter
库构建的图形用户界面,操作简单直观。
界面布局清晰,分为通讯录区域、聊天内容区域和在线用户列表区域,方便用户进行聊天和管理好友。
支持多用户实时聊天,消息即时显示。
在线用户列表和好友列表可以实时更新,用户可以随时了解当前的在线状态。
客户端和服务器的代码简洁明了,易于理解和扩展。
使用Python编写,运行环境要求低,只要有Python环境即可运行。
小型团队协作:团队成员可以通过这个聊天工具进行实时沟通,方便项目协作。
学习交流:学生和教师可以使用这个工具进行在线讨论,分享学习心得。
个人社交:用户可以添加好友,进行一对一或群组聊天,满足日常社交需求。
网络通信:使用socket
库建立与服务器的连接,发送和接收消息。
多线程:使用threading
库创建一个线程来处理消息接收,避免阻塞主线程。
图形界面:使用tkinter
库构建用户界面,包括输入框、按钮、聊天区域和列表框。
网络通信:使用socket
库监听客户端连接,处理客户端发送的消息。
多线程:为每个客户端连接创建一个线程,处理客户端的请求和消息转发。
日志记录:使用logging
库记录服务器运行日志,方便调试和监控。
私密聊天:支持用户之间的一对一私密聊天。
文件传输:允许用户发送和接收文件。
群组管理:支持创建和管理多个群组,用户可以加入或退出群组。
用户认证:增加用户密码登录功能,提高安全性。
客户端.py
import socket
import threading
import tkinter as tk
from tkinter import scrolledtext, messagebox, ttk
from tkinter import simpledialog
import os
class ChatClient:
def __init__(self, host='127.0.0.1', port=12345):
self.host = host
self.port = port
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client_socket.connect((self.host, self.port))
# 设置窗口
self.root = tk.Tk()
self.root.title("Chat Client")
self.root.geometry("1200x600") # 调整窗口宽度以容纳新的区域
self.root.resizable(False, False)
self.root.configure(bg="#f0f0f0")
# 设置字体和颜色
self.font = ("Arial", 12)
self.bg_color = "#f0f0f0"
self.input_bg_color = "#ffffff"
self.button_color = "#4CAF50"
self.text_color = "#333333"
# 检查身份文件
self.identity_file = "identity.txt"
if os.path.exists(self.identity_file):
with open(self.identity_file, "r") as file:
self.nickname = file.read().strip()
self.client_socket.send(self.nickname.encode('utf-8'))
else:
self.nickname = simpledialog.askstring("Nickname", "Choose your nickname:", parent=self.root)
if not self.nickname:
messagebox.showerror("Error", "Nickname is required!")
self.root.destroy()
return
self.client_socket.send(self.nickname.encode('utf-8'))
with open(self.identity_file, "w") as file:
file.write(self.nickname)
# 主聊天区域和用户列表区域
self.main_frame = tk.Frame(self.root, bg=self.bg_color)
self.main_frame.pack(fill='both', expand=True, padx=20, pady=20)
# 通讯录区域
self.contact_frame = tk.Frame(self.main_frame, bg=self.bg_color, width=200)
self.contact_frame.pack(side='left', fill='both', expand=False, padx=10, pady=10)
self.contact_label = tk.Label(self.contact_frame, text="通讯录", font=self.font, bg=self.bg_color, fg=self.text_color)
self.contact_label.pack(fill='x')
self.contact_list = tk.Listbox(self.contact_frame, font=self.font, bg=self.bg_color, fg=self.text_color)
self.contact_list.pack(fill='both', expand=True)
self.contact_list.bind("", self.on_contact_click)
# 聊天内容区域
self.chat_frame = tk.Frame(self.main_frame, bg=self.bg_color)
self.chat_frame.pack(side='left', fill='both', expand=True, padx=10, pady=10)
self.chat_area = scrolledtext.ScrolledText(self.chat_frame, state='disabled', height=20, width=50,
font=self.font, bg=self.bg_color, fg=self.text_color)
self.chat_area.pack(fill='both', expand=True)
# 在线用户列表区域
self.user_list_frame = tk.Frame(self.main_frame, bg=self.bg_color, width=200)
self.user_list_frame.pack(side='right', fill='both', expand=False, padx=10, pady=10)
self.user_list_label = tk.Label(self.user_list_frame, text="在线用户", font=self.font, bg=self.bg_color, fg=self.text_color)
self.user_list_label.pack(fill='x')
self.user_list = tk.Listbox(self.user_list_frame, font=self.font, bg=self.bg_color, fg=self.text_color)
self.user_list.pack(fill='both', expand=True)
self.user_list.bind("", self.on_user_click)
# 输入框和按钮的容器
self.input_frame = tk.Frame(self.root, bg=self.bg_color)
self.input_frame.pack(fill='x', padx=20, pady=20)
# 输入框
self.entry = tk.Entry(self.input_frame, width=50, font=self.font, bg=self.input_bg_color, fg=self.text_color)
self.entry.pack(side='left', fill='x', expand=True, padx=10)
self.entry.focus()
# 发送按钮
self.send_button = ttk.Button(self.input_frame, text="Send", command=self.send_message, style="TButton")
self.send_button.pack(side='left', padx=20)
# 全部按钮
self.all_button = ttk.Button(self.input_frame, text="全部", command=self.show_all_users, style="TButton")
self.all_button.pack(side='left', padx=20)
# 注销按钮
self.logout_button = ttk.Button(self.input_frame, text="Logout", command=self.logout, style="TButton")
self.logout_button.pack(side='left', padx=20)
# 设置样式
style = ttk.Style()
style.configure("TButton", font=self.font, background=self.button_color, foreground="white")
# 初始化通讯录
self.contacts = ["世界群"]
self.update_contact_list()
# 接收消息的线程
threading.Thread(target=self.receive_messages, daemon=True).start()
# 启动主循环
self.root.mainloop()
def send_message(self):
message = self.entry.get()
if message:
selected_contact = self.contact_list.get(self.contact_list.curselection())
self.client_socket.send(f"{self.nickname} to {selected_contact}: {message}".encode('utf-8'))
self.entry.delete(0, tk.END)
self.entry.focus()
def receive_messages(self):
while True:
try:
message = self.client_socket.recv(1024).decode('utf-8')
if message.startswith("USERS:"):
# 更新用户列表
users = message.split(":")[1].strip().split(",")
self.update_user_list(users)
elif message.startswith("REMOVE_FRIEND:"):
# 移除好友
friend = message.split(":")[1].strip()
if friend in self.contacts:
self.contacts.remove(friend)
self.update_contact_list()
else:
# 显示聊天消息
self.chat_area.configure(state='normal')
self.chat_area.insert(tk.END, message + "\n")
self.chat_area.configure(state='disabled')
self.chat_area.see(tk.END)
except Exception as e:
messagebox.showerror("Error", f"An error occurred: {e}")
self.client_socket.close()
break
def logout(self):
os.remove(self.identity_file)
self.root.destroy()
def show_all_users(self):
# 请求服务器发送当前所有用户
self.client_socket.send("GET_USERS".encode('utf-8'))
def update_user_list(self, users):
self.user_list.delete(0, tk.END) # 清空当前用户列表
for user in users:
self.user_list.insert(tk.END, user)
def update_contact_list(self):
self.contact_list.delete(0, tk.END) # 清空当前通讯录
for contact in self.contacts:
self.contact_list.insert(tk.END, contact)
def on_contact_click(self, event):
selection = event.widget.curselection()
if selection:
index = selection[0]
contact = event.widget.get(index)
self.chat_area.configure(state='normal')
self.chat_area.delete(1.0, tk.END)
self.chat_area.insert(tk.END, f"Chatting with: {contact}\n")
self.chat_area.configure(state='disabled')
def on_user_click(self, event):
selection = event.widget.curselection()
if selection:
index = selection[0]
user = event.widget.get(index)
if user not in self.contacts:
self.contacts.append(user)
self.update_contact_list()
self.client_socket.send(f"ADD_FRIEND:{user}".encode('utf-8'))
if __name__ == "__main__":
client = ChatClient()
服务器端.py
import socket
import threading
import logging
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class ChatServer:
def __init__(self, host='127.0.0.1', port=12345):
self.host = host
self.port = port
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.bind((self.host, self.port))
self.server_socket.listen()
logging.info(f"Server started on {self.host}:{self.port}")
self.clients = []
self.nicknames = []
def broadcast(self, message):
for client in self.clients:
try:
client.send(message)
except:
self.clients.remove(client)
client.close()
def handle_client(self, client):
while True:
try:
message = client.recv(1024)
if not message:
break
message = message.decode('utf-8')
logging.info(f"Received message: {message}")
if message == "GET_USERS":
# 发送当前所有用户
user_list = ",".join(self.nicknames)
client.send(f"USERS:{user_list}".encode('utf-8'))
elif message.startswith("ADD_FRIEND:"):
# 处理添加好友请求
friend = message.split(":")[1].strip()
self.broadcast(f"ADD_FRIEND:{friend}".encode('utf-8'))
else:
# 广播消息
self.broadcast(message.encode('utf-8'))
except Exception as e:
logging.error(f"Error handling client: {e}")
break
index = self.clients.index(client)
self.clients.remove(client)
client.close()
nickname = self.nicknames[index]
self.nicknames.remove(nickname)
self.broadcast(f"REMOVE_FRIEND:{nickname}".encode('utf-8'))
logging.info(f'{nickname} left the chat!')
def receive(self):
while True:
try:
client, address = self.server_socket.accept()
logging.info(f"Connected with {address}")
client.send("NICK".encode('utf-8'))
nickname = client.recv(1024).decode('utf-8')
self.nicknames.append(nickname)
self.clients.append(client)
logging.info(f"Nickname of the client is {nickname}")
self.broadcast(f"{nickname} joined the chat!".encode('utf-8'))
client.send("Connected to the server!".encode('utf-8'))
thread = threading.Thread(target=self.handle_client, args=(client,))
thread.start()
except Exception as e:
logging.error(f"Server error occurred: {e}")
break
if __name__ == "__main__":
server = ChatServer()
server.receive()
这个软件是一个很好的学习项目,可以帮助开发者理解网络编程、多线程和图形用户界面开发的基本概念。同时,它也可以作为一个简单的即时通讯工具,用于日常交流和协作。