在运输层网络编程需要用到套接字socket编程,主要包括UDP和TCP编程。
1.1 UDP为无连接服务,其编程模型如图1.1所示。

图1.1 UDP编程模型
整个编程分为服务器端编程和客户单编程。
1.2 UDP服务器代码
python
import tkinter as tk
from tkinter import scrolledtext, messagebox
import socket
import threading
import time
class UDPServerGUI:
def __init__(self, root):
self.root = root
self.root.title("UDP服务器")
self.root.geometry("600x800")
self.root.resizable(True, True)
# 服务器状态变量
self.server_socket = None
self.server_running = False
self.server_thread = None
# 创建界面组件
self.create_widgets()
def create_widgets(self):
# 顶部控制面板
control_frame = tk.Frame(self.root)
control_frame.pack(pady=10, padx=10, fill=tk.X)
tk.Label(control_frame, text="端口号:").pack(side=tk.LEFT, padx=(0, 5))
self.port_entry = tk.Entry(control_frame, width=10)
self.port_entry.insert(0, "9999")
self.port_entry.pack(side=tk.LEFT, padx=(0, 10))
self.start_button = tk.Button(control_frame, text="启动服务器", command=self.start_server)
self.start_button.pack(side=tk.LEFT, padx=(0, 10))
self.stop_button = tk.Button(control_frame, text="停止服务器", command=self.stop_server, state=tk.DISABLED)
self.stop_button.pack(side=tk.LEFT, padx=(0, 10))
self.clear_button = tk.Button(control_frame, text="清空日志", command=self.clear_log)
self.clear_button.pack(side=tk.LEFT)
# 状态显示
status_frame = tk.Frame(self.root)
status_frame.pack(pady=5, padx=10, fill=tk.X)
self.status_label = tk.Label(status_frame, text="服务器状态: 未运行", fg="red")
self.status_label.pack(side=tk.LEFT)
# 日志显示区域
log_frame = tk.LabelFrame(self.root, text="服务器日志")
log_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=20)
self.log_text.pack(pady=5, padx=5, fill=tk.BOTH, expand=True)
# 发送消息区域
send_frame = tk.LabelFrame(self.root, text="发送消息")
send_frame.pack(pady=10, padx=10, fill=tk.X)
tk.Label(send_frame, text="消息内容:").pack(anchor=tk.W)
self.message_entry = tk.Entry(send_frame)
self.message_entry.pack(pady=5, padx=5, fill=tk.X)
self.send_button = tk.Button(send_frame, text="发送消息", command=self.send_message, state=tk.DISABLED)
self.send_button.pack(pady=5)
# 客户端列表
client_frame = tk.LabelFrame(self.root, text="已连接客户端")
client_frame.pack(pady=10, padx=10, fill=tk.X)
self.client_listbox = tk.Listbox(client_frame, height=4)
self.client_listbox.pack(pady=5, padx=5, fill=tk.X)
self.client_addresses = {} # 存储客户端地址信息
def log_message(self, message):
"""在日志区域添加消息"""
timestamp = time.strftime("%H:%M:%S", time.localtime())
self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
self.log_text.see(tk.END)
def start_server(self):
"""启动UDP服务器"""
try:
port = int(self.port_entry.get())
if port < 1 or port > 65535:
raise ValueError("端口号必须在1-65535之间")
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.server_socket.bind(('localhost', port))
self.server_running = True
self.server_thread = threading.Thread(target=self.server_loop, daemon=True)
self.server_thread.start()
self.start_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.NORMAL)
self.send_button.config(state=tk.NORMAL)
self.port_entry.config(state=tk.DISABLED)
self.status_label.config(text="服务器状态: 运行中", fg="green")
self.log_message(f"UDP服务器已在端口 {port} 启动")
except ValueError as e:
messagebox.showerror("错误", f"端口号无效: {e}")
except Exception as e:
messagebox.showerror("错误", f"启动服务器失败: {e}")
def stop_server(self):
"""停止UDP服务器"""
self.server_running = False
if self.server_socket:
self.server_socket.close()
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
self.send_button.config(state=tk.DISABLED)
self.port_entry.config(state=tk.NORMAL)
self.status_label.config(text="服务器状态: 未运行", fg="red")
self.log_message("UDP服务器已停止")
def server_loop(self):
"""服务器主循环"""
while self.server_running:
try:
# 设置接收超时,以便能及时响应停止命令
self.server_socket.settimeout(1.0)
data, client_address = self.server_socket.recvfrom(1024)
message = data.decode('utf-8')
self.log_message(f"收到来自 {client_address} 的消息: {message}")
# 添加到客户端列表
if client_address not in self.client_addresses:
self.client_addresses[client_address] = True
self.client_listbox.insert(tk.END, f"{client_address}:{client_address}")
# 回复客户端
response = f"服务器收到消息: {message}"
self.server_socket.sendto(response.encode('utf-8'), client_address)
except socket.timeout:
# 超时继续循环,检查服务器是否仍在运行
continue
except Exception as e:
if self.server_running:
self.log_message(f"服务器错误: {e}")
break
def send_message(self):
"""向所有客户端发送消息"""
message = self.message_entry.get()
if not message:
messagebox.showwarning("警告", "请输入要发送的消息")
return
if not self.client_addresses:
messagebox.showwarning("警告", "没有已连接的客户端")
return
try:
for client_address in self.client_addresses:
self.server_socket.sendto(message.encode('utf-8'), client_address)
self.log_message(f"向所有客户端发送消息: {message}")
self.message_entry.delete(0, tk.END)
except Exception as e:
messagebox.showerror("错误", f"发送消息失败: {e}")
def clear_log(self):
"""清空日志"""
self.log_text.delete(1.0, tk.END)
def main():
root = tk.Tk()
app = UDPServerGUI(root)
root.mainloop()
if __name__ == "__main__":
main()
1.3 UDP客户端编程
python
import tkinter as tk
from tkinter import scrolledtext, messagebox
import socket
import threading
import time
class UDPClientGUI:
def __init__(self, root):
self.root = root
self.root.title("UDP客户端")
self.root.geometry("600x600")
self.root.resizable(True, True)
# 客户端状态变量
self.client_socket = None
self.server_address = None
self.client_running = False
self.receive_thread = None
# 创建界面组件
self.create_widgets()
def create_widgets(self):
# 顶部连接面板
connection_frame = tk.Frame(self.root)
connection_frame.pack(pady=10, padx=10, fill=tk.X)
tk.Label(connection_frame, text="服务器地址:").pack(side=tk.LEFT, padx=(0, 5))
self.host_entry = tk.Entry(connection_frame, width=15)
self.host_entry.insert(0, "localhost")
self.host_entry.pack(side=tk.LEFT, padx=(0, 5))
tk.Label(connection_frame, text="端口:").pack(side=tk.LEFT, padx=(0, 5))
self.port_entry = tk.Entry(connection_frame, width=8)
self.port_entry.insert(0, "9999")
self.port_entry.pack(side=tk.LEFT, padx=(0, 10))
self.connect_button = tk.Button(connection_frame, text="连接服务器", command=self.connect_to_server)
self.connect_button.pack(side=tk.LEFT, padx=(0, 10))
self.disconnect_button = tk.Button(connection_frame, text="断开连接", command=self.disconnect_from_server, state=tk.DISABLED)
self.disconnect_button.pack(side=tk.LEFT, padx=(0, 10))
self.clear_button = tk.Button(connection_frame, text="清空日志", command=self.clear_log)
self.clear_button.pack(side=tk.LEFT)
# 状态显示
status_frame = tk.Frame(self.root)
status_frame.pack(pady=5, padx=10, fill=tk.X)
self.status_label = tk.Label(status_frame, text="客户端状态: 未连接", fg="red")
self.status_label.pack(side=tk.LEFT)
# 日志显示区域
log_frame = tk.LabelFrame(self.root, text="通信日志")
log_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=20)
self.log_text.pack(pady=5, padx=5, fill=tk.BOTH, expand=True)
# 消息发送区域
send_frame = tk.LabelFrame(self.root, text="发送消息")
send_frame.pack(pady=10, padx=10, fill=tk.X)
tk.Label(send_frame, text="消息内容:").pack(anchor=tk.W)
self.message_entry = tk.Entry(send_frame)
self.message_entry.pack(pady=5, padx=5, fill=tk.X)
self.send_button = tk.Button(send_frame, text="发送消息", command=self.send_message, state=tk.DISABLED)
self.send_button.pack(pady=5)
def log_message(self, message):
"""在日志区域添加消息"""
timestamp = time.strftime("%H:%M:%S", time.localtime())
self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
self.log_text.see(tk.END)
def connect_to_server(self):
"""连接到UDP服务器"""
try:
host = self.host_entry.get().strip()
if not host:
raise ValueError("服务器地址不能为空")
port = int(self.port_entry.get())
if port < 1 or port > 65535:
raise ValueError("端口号必须在1-65535之间")
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.server_address = (host, port)
self.client_running = True
# 启动接收线程
self.receive_thread = threading.Thread(target=self.receive_messages, daemon=True)
self.receive_thread.start()
self.connect_button.config(state=tk.DISABLED)
self.disconnect_button.config(state=tk.NORMAL)
self.send_button.config(state=tk.NORMAL)
self.host_entry.config(state=tk.DISABLED)
self.port_entry.config(state=tk.DISABLED)
self.status_label.config(text=f"客户端状态: 已连接到 {host}:{port}", fg="green")
#获取本地地址,需要先连接服务器
self.client_socket.connect(self.server_address)
local=self.client_socket.getsockname()
self.log_message(f"{local}已连接到服务器 {host}:{port}")
except ValueError as e:
messagebox.showerror("错误", f"参数无效: {e}")
except Exception as e:
messagebox.showerror("错误", f"连接服务器失败: {e}")
def disconnect_from_server(self):
"""断开与UDP服务器的连接"""
self.client_running = False
if self.client_socket:
self.client_socket.close()
self.connect_button.config(state=tk.NORMAL)
self.disconnect_button.config(state=tk.DISABLED)
self.send_button.config(state=tk.DISABLED)
self.host_entry.config(state=tk.NORMAL)
self.port_entry.config(state=tk.NORMAL)
self.status_label.config(text="客户端状态: 未连接", fg="red")
self.log_message("已断开与服务器的连接")
def send_message(self):
"""发送消息到服务器"""
if not self.client_running:
messagebox.showwarning("警告", "客户端未连接到服务器")
return
message = self.message_entry.get()
if not message:
messagebox.showwarning("警告", "请输入要发送的消息")
return
try:
self.client_socket.sendto(message.encode('utf-8'), self.server_address)
self.log_message(f"发送到服务器: {message}")
self.message_entry.delete(0, tk.END)
except Exception as e:
messagebox.showerror("错误", f"发送消息失败: {e}")
def receive_messages(self):
"""接收来自服务器的消息"""
while self.client_running:
try:
# 设置接收超时,以便能及时响应停止命令
self.client_socket.settimeout(1.0)
data, server = self.client_socket.recvfrom(1024)
response = data.decode('utf-8')
self.log_message(f"服务器回复: {response}")
except socket.timeout:
# 超时继续循环,检查客户端是否仍在运行
continue
except Exception as e:
if self.client_running:
self.log_message(f"接收消息错误: {e}")
break
def clear_log(self):
"""清空日志"""
self.log_text.delete(1.0, tk.END)
def main():
root = tk.Tk()
app = UDPClientGUI(root)
root.mainloop()
if __name__ == "__main__":
main()
1.4 调试结果

2.1 TCP编程模型如图2.1所示

图2.2 TCP编程模型
2.2 TCP服务器端代码
python
import tkinter as tk
from tkinter import scrolledtext, messagebox
import socket
import threading
import datetime
class TCPServerApp:
def __init__(self, root):
self.root = root
self.root.title("TCP服务器")
self.root.geometry("600x600")
# 服务器状态变量
self.server_socket = None
self.client_sockets = {} # 存储客户端连接
self.is_running = False
# 创建界面组件
self.create_widgets()
def getTime(self):
"""获取当前时间"""
now = datetime.datetime.now()
time=now.strftime("%H:%M:%S")
return time
def create_widgets(self):
# 服务器配置区域
config_frame = tk.Frame(self.root, bd=2, relief=tk.GROOVE, padx=10, pady=10)
config_frame.pack(pady=10, padx=10, fill=tk.X)
tk.Label(config_frame, text="监听IP:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.ip_entry = tk.Entry(config_frame, width=20)
self.ip_entry.grid(row=0, column=1, padx=5, pady=5)
self.ip_entry.insert(0, "127.0.0.1")
tk.Label(config_frame, text="监听端口:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
self.port_entry = tk.Entry(config_frame, width=10)
self.port_entry.grid(row=0, column=3, padx=5, pady=5)
self.port_entry.insert(0, "9999")
self.start_btn = tk.Button(config_frame, text="启动服务器", command=self.start_server, width=12)
self.start_btn.grid(row=0, column=4, padx=5, pady=5)
self.stop_btn = tk.Button(config_frame, text="停止服务器", command=self.stop_server, width=12, state=tk.DISABLED)
self.stop_btn.grid(row=0, column=5, padx=5, pady=5)
# 客户端列表区域
client_frame = tk.Frame(self.root, bd=2, relief=tk.GROOVE, padx=10, pady=10)
client_frame.pack(pady=10, padx=10, fill=tk.X)
tk.Label(client_frame, text="已连接客户端:").pack(anchor=tk.W, padx=5, pady=5)
self.client_listbox = tk.Listbox(client_frame, height=4)
self.client_listbox.pack(padx=5, pady=5, fill=tk.X)
# 消息区域
message_frame = tk.Frame(self.root, bd=2, relief=tk.GROOVE, padx=10, pady=10)
message_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
tk.Label(message_frame, text="通信消息:").pack(anchor=tk.W, padx=5, pady=5)
self.message_text = scrolledtext.ScrolledText(message_frame, height=12)
self.message_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
self.message_text.config(state=tk.DISABLED)
# 发送消息区域
send_frame = tk.Frame(self.root, bd=2, relief=tk.GROOVE, padx=10, pady=10)
send_frame.pack(pady=10, padx=10, fill=tk.X)
tk.Label(send_frame, text="广播消息:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.send_entry = tk.Entry(send_frame, width=40)
self.send_entry.grid(row=0, column=1, padx=5, pady=5)
self.send_btn = tk.Button(send_frame, text="发送", command=self.broadcast_message, width=10)
self.send_btn.grid(row=0, column=2, padx=5, pady=5)
def start_server(self):
ip = self.ip_entry.get().strip()
port = self.port_entry.get().strip()
if not ip or not port.isdigit():
messagebox.showerror("错误", "请输入正确的IP地址和端口号")
return
port = int(port)
try:
# 创建服务器套接字
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind((ip, port))
self.server_socket.listen(5)
self.is_running = True
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.NORMAL)
# 更新界面状态
self.append_message(f"{self.getTime()}:服务器已在 {ip}:{port} 上启动,等待客户端连接...\n")
# 启动接受连接的线程
accept_thread = threading.Thread(target=self.accept_connections, daemon=True)
accept_thread.start()
except Exception as e:
messagebox.showerror("错误", f"启动服务器失败:\n{str(e)}")
self.start_btn.config(state=tk.NORMAL)
self.stop_btn.config(state=tk.DISABLED)
def stop_server(self):
self.is_running = False
# 关闭所有客户端连接
for addr, conn in list(self.client_sockets.items()):
try:
conn.close()
except:
pass
self.client_sockets.clear()
# 关闭服务器套接字
if self.server_socket:
try:
self.server_socket.close()
except:
pass
self.server_socket = None
self.start_btn.config(state=tk.NORMAL)
self.stop_btn.config(state=tk.DISABLED)
self.client_listbox.delete(0, tk.END)
self.append_message(f"{self.getTime()}:服务器已停止\n")
def accept_connections(self):
while self.is_running and self.server_socket:
try:
# 设置超时以便能及时退出循环
self.server_socket.settimeout(1.0)
conn, addr = self.server_socket.accept()
conn.settimeout(1.0) # 设置客户端连接超时
# 将新客户端加入字典
self.client_sockets[addr] = conn
self.append_message(f"{self.getTime()}:客户端:{addr} 已连接\n")
# 更新客户端列表
self.root.after(0, self.update_client_list)
# 启动接收消息线程
recv_thread = threading.Thread(
target=self.handle_client,
args=(conn, addr),
daemon=True
)
recv_thread.start()
except socket.timeout:
continue # 正常超时,继续循环
except Exception as e:
if self.is_running:
self.append_message(f"接受连接时出错: {str(e)}\n")
break
def handle_client(self, conn, addr):
while self.is_running:
try:
data = conn.recv(1024)
if not data:
break
message = data.decode('utf-8')
display_msg = f"{self.getTime()}:[{addr}]: {message}\n"
self.append_message(display_msg)
except socket.timeout:
continue
except Exception as e:
break
# 客户端断开连接后的清理工作
if addr in self.client_sockets:
del self.client_sockets[addr]
self.append_message(f"{self.getTime()}:客户端 :{addr} 已断开连接\n")
self.root.after(0, self.update_client_list)
try:
conn.close()
except:
pass
def broadcast_message(self):
if not self.client_sockets:
messagebox.showwarning("警告", "没有已连接的客户端")
return
message = self.send_entry.get().strip()
if not message:
return
disconnected_clients = []
for addr, conn in self.client_sockets.items():
try:
conn.send(message.encode('utf-8'))
except:
disconnected_clients.append(addr)
# 移除已断开的客户端
for addr in disconnected_clients:
if addr in self.client_sockets:
del self.client_sockets[addr]
if disconnected_clients:
self.update_client_list()
self.append_message(f"{self.getTime()}:[服务器广播]: {message}\n")
self.send_entry.delete(0, tk.END)
def append_message(self, message):
# 在主线程中更新UI
self.root.after(0, lambda: self._update_message_display(message))
def _update_message_display(self, message):
self.message_text.config(state=tk.NORMAL)
self.message_text.insert(tk.END, message)
self.message_text.config(state=tk.DISABLED)
self.message_text.see(tk.END)
def update_client_list(self):
self.client_listbox.delete(0, tk.END)
for addr in self.client_sockets.keys():
self.client_listbox.insert(tk.END, f"{addr}:{addr}")
def main():
root = tk.Tk()
app = TCPServerApp(root)
root.protocol("WM_DELETE_WINDOW", lambda: on_closing(root, app))
root.mainloop()
def on_closing(root, app):
if app.is_running:
app.stop_server()
root.destroy()
if __name__ == "__main__":
main()
2.3 TCP客户端代码
python
import tkinter as tk
from tkinter import scrolledtext, messagebox, ttk
import socket
import threading
import datetime
class TCPClientApp:
def __init__(self, root):
self.root = root
self.root.title("TCP客户端")
self.root.geometry("700x600")
self.root.resizable(True, True)
# 客户端状态变量
self.client_socket = None
self.is_connected = False
self.receive_thread = None
self.is_receiving = False
# 创建界面组件
self.create_widgets()
def getTime(self):
"""获取当前时间"""
now = datetime.datetime.now()
time=now.strftime("%H:%M:%S")
return time
def create_widgets(self):
# 连接配置区域
connection_frame = tk.LabelFrame(self.root, text="连接配置", padx=10, pady=10)
connection_frame.pack(pady=10, padx=10, fill=tk.X)
# IP地址输入
tk.Label(connection_frame, text="服务器IP:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.ip_entry = tk.Entry(connection_frame, width=20)
self.ip_entry.grid(row=0, column=1, padx=5, pady=5)
self.ip_entry.insert(0, "127.0.0.1")
# 端口输入
tk.Label(connection_frame, text="端口:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
self.port_entry = tk.Entry(connection_frame, width=10)
self.port_entry.grid(row=0, column=3, padx=5, pady=5)
self.port_entry.insert(0, "9999")
# 连接按钮
self.connect_btn = tk.Button(connection_frame, text="连接服务器", command=self.connect_to_server, width=12)
self.connect_btn.grid(row=0, column=4, padx=5, pady=5)
# 断开按钮
self.disconnect_btn = tk.Button(connection_frame, text="断开连接", command=self.disconnect_from_server, width=12, state=tk.DISABLED)
self.disconnect_btn.grid(row=0, column=5, padx=5, pady=5)
# 连接状态指示
self.status_label = tk.Label(connection_frame, text="未连接", fg="red")
self.status_label.grid(row=0, column=6, padx=10, pady=5)
# 消息显示区域
message_frame = tk.LabelFrame(self.root, text="通信消息", padx=10, pady=10)
message_frame.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
# 消息文本框
self.message_text = scrolledtext.ScrolledText(message_frame, height=15, wrap=tk.WORD)
self.message_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
self.message_text.config(state=tk.DISABLED)
# 发送消息区域
send_frame = tk.LabelFrame(self.root, text="发送消息", padx=10, pady=10)
send_frame.pack(pady=10, padx=10, fill=tk.X)
# 消息输入框
tk.Label(send_frame, text="消息内容:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.send_entry = tk.Entry(send_frame, width=50)
self.send_entry.grid(row=0, column=1, padx=5, pady=5)
self.send_entry.bind("<Return>", lambda event: self.send_message())
# 发送按钮
self.send_btn = tk.Button(send_frame, text="发送", command=self.send_message, width=10, state=tk.DISABLED)
self.send_btn.grid(row=0, column=2, padx=5, pady=5)
# 清空按钮
clear_btn = tk.Button(send_frame, text="清空消息", command=self.clear_messages, width=10)
clear_btn.grid(row=0, column=3, padx=5, pady=5)
# 快捷消息区域
quick_frame = tk.LabelFrame(self.root, text="快捷消息", padx=10, pady=10)
quick_frame.pack(pady=10, padx=10, fill=tk.X)
# 快捷按钮
buttons = [
("Hello Server", "Hello Server"),
("时间查询", "TIME"),
("状态查询", "STATUS"),
("测试消息", "This is a test message")
]
for i, (btn_text, msg) in enumerate(buttons):
btn = tk.Button(quick_frame, text=btn_text,
command=lambda m=msg: self.quick_send(m),
width=12)
btn.grid(row=0, column=i, padx=5, pady=5)
btn.config(state=tk.DISABLED)
setattr(self, f"quick_btn_{i}", btn)
def connect_to_server(self):
"""连接到服务器"""
if self.is_connected:
return
ip = self.ip_entry.get().strip()
port = self.port_entry.get().strip()
if not ip or not port.isdigit():
messagebox.showerror("错误", "请输入正确的IP地址和端口号")
return
port = int(port)
try:
# 创建客户端套接字
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client_socket.settimeout(5) # 设置连接超时
# 连接到服务器
self.client_socket.connect((ip, port))
# 更新连接状态
self.is_connected = True
self.connect_btn.config(state=tk.DISABLED)
self.disconnect_btn.config(state=tk.NORMAL)
self.send_btn.config(state=tk.NORMAL)
self.status_label.config(text="已连接", fg="green")
# 启用快捷按钮
for i in range(4):
getattr(self, f"quick_btn_{i}").config(state=tk.NORMAL)
# 开始接收消息
self.is_receiving = True
self.receive_thread = threading.Thread(target=self.receive_messages, daemon=True)
self.receive_thread.start()
local_address = self.client_socket.getsockname()
self.append_message(f"{self.getTime()}:{local_address}已连接到服务器 {ip}:{port}\n")
except socket.timeout:
messagebox.showerror("错误", "连接超时,请检查服务器地址和端口")
self.cleanup_connection()
except ConnectionRefusedError:
messagebox.showerror("错误", "连接被拒绝,请检查服务器是否正在运行")
self.cleanup_connection()
except Exception as e:
messagebox.showerror("错误", f"连接失败:\n{str(e)}")
self.cleanup_connection()
def disconnect_from_server(self):
"""断开与服务器的连接"""
self.is_receiving = False
if self.client_socket:
try:
self.client_socket.close()
except:
pass
self.client_socket = None
self.cleanup_connection()
self.append_message("{self.getTime()}:已断开与服务器的连接\n")
def cleanup_connection(self):
"""清理连接状态"""
self.is_connected = False
self.connect_btn.config(state=tk.NORMAL)
self.disconnect_btn.config(state=tk.DISABLED)
self.send_btn.config(state=tk.DISABLED)
self.status_label.config(text="未连接", fg="red")
# 禁用快捷按钮
for i in range(4):
getattr(self, f"quick_btn_{i}").config(state=tk.DISABLED)
def receive_messages(self):
"""接收服务器消息的线程函数"""
while self.is_receiving and self.client_socket:
try:
# 设置接收超时
self.client_socket.settimeout(1.0)
data = self.client_socket.recv(1024)
if not data:
# 服务器关闭连接
break
message = data.decode('utf-8')
self.append_message(f"{self.getTime()}:[服务器]: {message}\n")
except socket.timeout:
# 正常超时,继续循环
continue
except Exception as e:
if self.is_receiving:
self.append_message(f"[错误] 接收消息时出错: {str(e)}\n")
break
# 如果仍在连接状态,说明是异常断开
if self.is_connected:
self.root.after(0, self.handle_disconnect)
def handle_disconnect(self):
"""处理意外断开连接"""
self.cleanup_connection()
self.append_message(f"{self.getTime()}:与服务器的连接已断开\n")
messagebox.showinfo("提示", "与服务器的连接已断开")
def send_message(self):
"""发送消息到服务器"""
if not self.is_connected or not self.client_socket:
messagebox.showwarning("警告", "未连接到服务器")
return
message = self.send_entry.get().strip()
if not message:
return
try:
self.client_socket.send(message.encode('utf-8'))
self.append_message(f"{self.getTime()}:[我]: {message}\n")
self.send_entry.delete(0, tk.END)
except Exception as e:
messagebox.showerror("错误", f"发送消息失败:\n{str(e)}")
self.handle_disconnect()
def quick_send(self, message):
"""快速发送预设消息"""
self.send_entry.delete(0, tk.END)
self.send_entry.insert(0, message)
self.send_message()
def append_message(self, message):
"""在消息区域追加消息"""
# 使用after方法确保在主线程中更新UI
self.root.after(0, lambda: self._update_message_display(message))
def _update_message_display(self, message):
"""实际更新消息显示"""
self.message_text.config(state=tk.NORMAL)
self.message_text.insert(tk.END, message)
self.message_text.config(state=tk.DISABLED)
self.message_text.see(tk.END)
def clear_messages(self):
"""清空消息显示区域"""
self.message_text.config(state=tk.NORMAL)
self.message_text.delete(1.0, tk.END)
self.message_text.config(state=tk.DISABLED)
def main():
root = tk.Tk()
app = TCPClientApp(root)
root.protocol("WM_DELETE_WINDOW", lambda: on_closing(root, app))
root.mainloop()
def on_closing(root, app):
"""窗口关闭事件处理"""
if app.is_connected:
app.disconnect_from_server()
root.destroy()
if __name__ == "__main__":
main()
2.4 调试结果
