Python 远程聊天代码
要实现一个远程聊天代码,我们需要使用 socket
进行远程连接,使用多线程 threading
同时接收消息和界面输入。
前置知识
Socket 套接字
Python 内置的 socket 模块正是为网络远程通信而设计,它提供了底层的网络访问接口,允许我们直接控制数据包的发送与接收过程,从而实现自定义的网络协议设计。
在深入了解socket模块之前,首先我们需要明确几个核心概念:
- 套接字(Socket):用于描述IP地址和端口的组合,它是网络通信的基本单位。
- 导入库:
import socket
- 创建套接字:使用
socket.socket()
函数创建一个新的套接字对象。 - 绑定地址:通过调用
bind()
方法将套接字与特定的本地地址(即IP地址和端口号)关联起来。 - 监听连接:服务器端需要调用
listen()
方法进入监听状态,等待客户端发起连接请求。 - 接受连接:当有新的连接请求到达时,服务器可以通过
accept()
方法接受这个连接,并返回一个新套接字用于后续通信。 - 发送/接收数据:使用
sendall()
和recv()
方法进行数据的发送与接收操作。
内容摘自腾讯开发者社区,有删改。
Threading 多线程
线程(Thread
)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以和其他线程同时运行。
内容摘自博客园,有删改。
服务器端
import socket
import threading
import tkinter as tk
from tkinter import messagebox
from tkinter import font as tkFont
class ChatServer:
def __init__(self, master):
# 初始化聊天服务器
self.master = master
self.master.title("聊天服务器 Chat Server")
self.setup_widgets() # 设置界面组件
self.sock = None # 套接字初始化
self.start = False # 服务器启动状态
self.clients = [] # 存储连接的客户端
self.client_names = {} # 存储客户端名称
self.muted_clients = set() # 存储被禁言的客户端
def setup_widgets(self):
# 设置界面组件
font = tkFont.Font(family="华文楷体", size=12)
self.messages = tk.Text(self.master, state='disabled', font=font) # 消息显示框
self.messages.grid(row=0, column=0, columnspan=2)
self.client_listbox = tk.Listbox(self.master, font=font) # 客户端列表框
self.client_listbox.grid(row=0, column=2, rowspan=2)
self.input_server = tk.Entry(self.master, font=font) # 服务器输入框
self.input_server.grid(row=2, column=0)
self.send_button = tk.Button(self.master, text="发送 Send", command=self.send_server_message, font=font) # 发送按钮
self.send_button.grid(row=3, column=0)
self.kick_button = tk.Button(self.master, text="踢出 Kick", command=self.kick_selected_user, font=font) # 踢出按钮
self.kick_button.grid(row=3, column=1)
self.mute_button = tk.Button(self.master, text="禁言 Mute", command=self.mute_selected_user, font=font) # 禁言按钮
self.mute_button.grid(row=4, column=1)
self.unmute_button = tk.Button(self.master, text="解除禁言 Unmute", command=self.unmute_selected_user, font=font) # 解除禁言按钮
self.unmute_button.grid(row=5, column=1)
tk.Label(self.master, text="服务器IP地址 Server IP address: " + str(socket.gethostbyname(socket.gethostname())), font=font).grid(row=1, column=2) # 显示服务器IP地址
self.start_button = tk.Button(self.master, text="启动服务器 Start server", command=self.start_server, font=font) # 启动服务器按钮
self.start_button.grid(row=1, column=0)
self.master.bind("<Return>", self.send_server_message_event) # 绑定回车键发送消息
self.master.protocol("WM_DELETE_WINDOW", self.on_close) # 关闭窗口时的处理
def start_server(self):
# 启动聊天服务器
if self.start == True:
messagebox.showerror("服务器已启动 The server is up", "服务器已启动,请勿重复启动服务器!The server has been started, do not start the server repeatedly!") # 已启动提示
return
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建套接字
self.sock.bind(('0.0.0.0', 8888)) # 绑定IP和端口
self.sock.listen(1000) # 监听连接
threading.Thread(target=self.accept_connections, daemon=True).start() # 启动接收连接的线程
self.start = True # 设置服务器为启动状态
def accept_connections(self):
# 接受客户端连接
while True:
client, addr = self.sock.accept() # 接受连接
self.clients.append(client) # 添加到客户端列表
threading.Thread(target=self.handle_client, args=(client,), daemon=True).start() # 启动处理客户端的线程
def handle_client(self, client):
# 处理客户端消息
while True:
try:
data = client.recv(1024) # 接收数据
message = data.decode('utf-8') # 解码消息
if message.startswith("/kick "):
self.kick_user(message.split()[1]) # 踢出用户
elif message.startswith("/mute "):
self.mute_user(message.split()[1]) # 禁言用户
elif message.startswith("/unmute "):
self.unmute_user(message.split()[1]) # 解除禁言用户
elif message.startswith("/name "):
self.client_names[client] = message.split()[1] # 更新客户端名称
self.update_client_list() # 更新用户列表
elif client not in self.muted_clients:
for other in self.clients:
if other != client:
other.sendall(data) # 发送消息给其他客户端
self.messages.configure(state='normal')
self.messages.insert(tk.END, message + '\n') # 显示消息
self.messages.configure(state='disabled')
self.messages.yview(tk.END) # 滚动到消息框底部
except socket.error as e:
self.clients.remove(client) # 移除断开的客户端
if client in self.client_names:
del self.client_names[client] # 删除客户端名称
self.update_client_list() # 更新用户列表
break
def send_server_message_event(self, event=None):
# 发送服务器消息
self.send_server_message()
def send_server_message(self):
# 发送消息到所有客户端
message = self.input_server.get() # 获取输入框的消息
if message.startswith("/kick "):
self.kick_user(message.split()[1]) # 踢出用户
elif message.startswith("/mute "):
self.mute_user(message.split()[1]) # 禁言用户
elif message.startswith("/unmute "):
self.unmute_user(message.split()[1]) # 解除禁言用户
else:
message = f"服务器 Chat server: {message}" # 格式化消息
for client in self.clients:
client.sendall(message.encode('utf-8')) # 发送消息到所有客户端
self.messages.configure(state='normal')
self.messages.insert(tk.END, message + '\n') # 显示发送的消息
self.messages.configure(state='disabled')
self.messages.yview(tk.END) # 滚动到消息框底部
self.input_server.delete(0, tk.END) # 清空输入框
def kick_user(self, username):
# 踢出指定用户
for client, name in self.client_names.items():
if name == username:
client.sendall(f"{username} 已被踢出聊天室。{username} ,you have been kicked out of the chat room.".encode('utf-8')) # 通知用户被踢出
client.close() # 关闭连接
self.clients.remove(client) # 从客户端列表中移除
del self.client_names[client] # 删除客户端名称
self.update_client_list() # 更新用户列表
break
def mute_user(self, username):
# 禁言指定用户
for client, name in self.client_names.items():
if name == username:
self.muted_clients.add(client) # 添加到禁言列表
client.sendall(f"用户 {username} 被禁言。{username} ,you have been banned.".encode('utf-8')) # 通知用户被禁言
break
def unmute_user(self, username):
# 解除禁言指定用户
for client, name in self.client_names.items():
if name == username:
self.muted_clients.discard(client) # 从禁言列表中移除
client.sendall(f"用户 {username} 被解除禁言。{username} ,you have been unbanned.".encode('utf-8')) # 通知用户解除禁言
break
def kick_selected_user(self):
# 踢出选中的用户
selected_user = self.client_listbox.get(tk.ACTIVE) # 获取选中的用户
if selected_user:
self.kick_user(selected_user) # 踢出用户
def mute_selected_user(self):
# 禁言选中的用户
selected_user = self.client_listbox.get(tk.ACTIVE) # 获取选中的用户
if selected_user:
self.mute_user(selected_user) # 禁言用户
def unmute_selected_user(self):
# 解除禁言选中的用户
selected_user = self.client_listbox.get(tk.ACTIVE) # 获取选中的用户
if selected_user:
self.unmute_user(selected_user) # 解除禁言用户
def update_client_list(self):
# 更新用户列表
self.client_listbox.delete(0, tk.END) # 清空列表框
for name in self.client_names.values():
self.client_listbox.insert(tk.END, name) # 添加用户到列表框
# 向所有客户端发送更新后的用户列表
client_list_message = "/name " + " ".join(self.client_names.values()) # 格式化用户列表消息
for client in self.clients:
client.sendall(client_list_message.encode('utf-8')) # 发送用户列表到所有客户端
def on_close(self):
# 关闭服务器
if self.sock:
for client in self.clients:
client.sendall(b"/quit") # 通知所有客户端服务器关闭
client.close() # 关闭连接
self.sock.close() # 关闭套接字
self.master.destroy() # 销毁窗口
if __name__ == '__main__':
root = tk.Tk() # 创建主窗口
server = ChatServer(root) # 创建聊天服务器实例
root.mainloop() # 运行主循环
客户端
import socket
import threading
import tkinter as tk
from tkinter import messagebox, simpledialog
from tkinter import font as tkFont
class ChatClient:
def __init__(self, master):
# 初始化聊天客户端
self.master = master
self.master.title("聊天客户端 Chat Client")
self.setup_widgets() # 设置界面组件
self.sock = None # 套接字初始化
self.muted = False # 用户是否被禁言的状态
def setup_widgets(self):
# 设置界面组件
font = tkFont.Font(family="华文楷体", size=12)
self.messages = tk.Text(self.master, state='disabled', font=font) # 消息显示框
self.messages.grid(row=0, column=0, columnspan=2)
self.client_listbox = tk.Listbox(self.master, font=font) # 客户端列表框
self.client_listbox.grid(row=0, column=2, rowspan=2)
self.input_user = tk.Entry(self.master, font=font) # 用户输入框
self.input_user.grid(row=1, column=0)
self.send_button = tk.Button(self.master, text="发送 Sent", command=self.send_message, font=font) # 发送按钮
self.send_button.grid(row=1, column=1)
self.master.bind("<Return>", self.send_message) # 绑定回车键发送消息
self.master.protocol("WM_DELETE_WINDOW", self.on_close) # 关闭窗口时的处理
def connect(self, host, port):
# 连接到聊天服务器
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建套接字
try:
self.sock.connect((host, port)) # 连接到指定的主机和端口
threading.Thread(target=self.receive_message, args=(user,), daemon=True).start() # 启动接收消息的线程
except socket.error as e:
messagebox.showerror("连接错误 Connect Error", f"无法连接到服务器 Can't connect to server: {e}") # 连接失败提示
self.master.destroy() # 销毁窗口
else:
messagebox.showinfo("连接成功 Connected accept.", f"已连接到服务器 {host}:{port} Connected to server.") # 连接成功提示
self.sock.sendall(f"/name {user}".encode('utf-8')) # 发送用户名到服务器
self.sock.sendall(f"用户 {user} 加入了聊天室。User {user} has joined the chat room.".encode('utf-8')) # 通知其他用户
def send_message(self, event=None):
# 发送消息
if self.muted:
messagebox.showwarning("禁言 Banned", "你已被禁言,无法发送消息。You have been banned from sending messages.") # 被禁言提示
return
message = f"{user}: " + self.input_user.get() # 获取用户输入的消息
if message:
try:
self.sock.sendall(message.encode('utf-8')) # 发送消息
self.input_user.delete(0, tk.END) # 清空输入框
self.messages.configure(state='normal') # 使消息框可编辑
self.messages.insert(tk.END, message + '\n') # 显示发送的消息
self.messages.configure(state='disabled') # 使消息框不可编辑
self.messages.yview(tk.END) # 滚动到消息框底部
except socket.error as e:
messagebox.showerror("发送错误 Sent Error", f"无法发送消息 Can't to sent message: {e}") # 发送失败提示
def receive_message(self, username):
# 接收消息
while True:
try:
data = self.sock.recv(1024) # 接收数据
message = data.decode('utf-8') # 解码消息
if message.startswith("/name "):
self.update_client_list(message.split()[1:]) # 更新用户列表
self.messages.configure(state='normal')
self.messages.insert(tk.END, '正在刷新成员列表。Refreshing user list.\n') # 刷新提示
self.messages.configure(state='disabled')
self.messages.yview(tk.END)
elif message == f"{username} 已被踢出聊天室。{username} ,you have been kicked out of the chat room.":
messagebox.showinfo("提示 Prompt", "你已被踢出聊天室。You have been kicked out of the chat room.") # 被踢出提示
self.on_close() # 关闭连接
break
elif message == f"用户 {username} 被禁言。{username} ,you have been banned.":
self.muted = True # 设置为禁言状态
messagebox.showinfo("提示", "你已被禁言。You have been banned.") # 禁言提示
elif message == f"用户 {username} 被解除禁言。{username} ,you have been unbanned.":
self.muted = False # 解除禁言状态
messagebox.showinfo("提示", "你已被解除禁言。You have been unbanned.") # 解除禁言提示
elif message == f"/quit":
messagebox.showinfo("提示", "服务器已关闭。The server has been closed.") # 服务器关闭提示
self.on_close() # 关闭连接
break
else:
self.messages.configure(state='normal')
self.messages.insert(tk.END, message + '\n') # 显示接收到的消息
self.messages.configure(state='disabled')
self.messages.yview(tk.END)
except socket.error as e:
print(f"接收错误 Receive error: {e}") # 接收错误提示
break
def update_client_list(self, names):
# 更新用户列表
self.client_listbox.delete(0, tk.END) # 清空列表框
for name in names:
self.client_listbox.insert(tk.END, name) # 添加用户到列表框
def on_close(self):
# 关闭连接
if self.sock:
if user != '':
self.sock.sendall(f"用户 {user} 离开了聊天室。User {user} has left the chat room.".encode('utf-8')) # 通知用户离开
else:
self.sock.sendall("无名用户离开了聊天室。Unnamed user has left the chat room.".encode('utf-8')) # 通知无名用户离开
self.sock.close() # 关闭套接字
self.master.destroy() # 销毁窗口
if __name__ == '__main__':
root = tk.Tk() # 创建主窗口
client = ChatClient(root) # 创建聊天客户端实例
host = simpledialog.askstring("连接 Connect", "服务器地址 IP: ", initialvalue="127.0.0.1") # 获取服务器地址
port = simpledialog.askinteger("连接 Connect", "端口号 Port: ", initialvalue=8888) # 获取端口号
user = simpledialog.askstring("连接 Connect", "用户名 Username: ") # 获取用户名
client.connect(host, port) # 连接到服务器
root.mainloop() # 运行主循环