20254106 实验三《Python程序设计》实验报告
20254106 2025-2026-2 《Python程序设计》实验三报告
课程:《Python程序设计》
班级: 2541
姓名: 谢林儒
学号:20254106
实验教师:王志强
实验日期:2026年4月28日
必修/选修: 专选课
1.实验内容
创建服务端和客户端,服务端在特定端口监听多个客户请求。客户端和服务端通过Socket套接字(TCP/UDP)进行通信。
2. 实验过程及结果
A.同队友(20254125祁芸萱)使用课程代码进行通信。
(1)修改电脑IP地址,以本人(20254106)的末位学号“6”作为IP结尾;

(2)创建服务端和客户端,选择一个通信端口,用Python语言编程实现通信演示程序;
a.server端编程
import socket #pip install socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = "172.20.10.6" #"localhost"
PORT = 4444
server.bind((HOST, PORT))
server.listen(1)
print("服务端已经启动,等待客户端连接......")
conn, addr = server.accept()
print(f"已连接客户端:{addr}")
while True:
#接收消息
data = conn.recv(1024).decode("utf-8")
if not data or data == "exit":
print("聊天结束")
break
print(f"客户端:{data}")
send_msg = input("我:")
conn.send(send_msg.encode("utf-8"))
if send_msg == "exit":
break
conn.close()
server.close()
b.client端编程
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = "172.20.10.25"
PORT = 4444
client.connect((HOST, PORT))
print("已连接服务端!输入exit退出聊天!")
while True:
#发送数据
send_msg = input("我:")
client.send(send_msg.encode("utf-8"))
if send_msg == "exit":
break
#接收数据
data = client.recv(1024).decode("utf-8")
if not data or data == "exit":
print("聊天结束")
break
print(f"\n服务端:{data}")
client.close()
(3)要求发送方输入内容,并传输;接收方收到信息并显示。
a.server端视角

b.client端视角

要求2:使用LLM生成一个带图形界面的程序
(1)使用deepseek专家模式,输入实验相关提示词,进而输出代码;
import tkinter as tk
from tkinter import scrolledtext, messagebox
import socket
import threading
import queue
---------------------------- 服务端模块 ----------------------------
class ServerWindow:
def init(self, root):
self.root = root
self.root.title("聊天室 - 服务端(可参与聊天)")
self.root.geometry("600x550")
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
self.server_socket = None
self.is_running = False
self.client_sockets = []
self.client_addresses = {}
self.threads = []
self.message_queue = queue.Queue()
------ 顶部控制栏 ------
frame_top = tk.Frame(root)
frame_top.pack(pady=5, padx=5, fill=tk.X)
tk.Label(frame_top, text="监听端口:").pack(side=tk.LEFT)
self.port_var = tk.StringVar(value="5000")
tk.Entry(frame_top, textvariable=self.port_var, width=8).pack(side=tk.LEFT, padx=5)
self.start_btn = tk.Button(frame_top, text="启动服务", command=self.start_server)
self.start_btn.pack(side=tk.LEFT, padx=5)
self.stop_btn = tk.Button(frame_top, text="停止服务", command=self.stop_server, state=tk.DISABLED)
self.stop_btn.pack(side=tk.LEFT, padx=5)
------ 日志/聊天记录区域 ------
self.log_text = scrolledtext.ScrolledText(root, state='normal', wrap=tk.WORD)
self.log_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
------ 在线客户端列表 ------
frame_mid = tk.LabelFrame(root, text="在线客户端")
frame_mid.pack(padx=5, pady=5, fill=tk.X)
self.client_listbox = tk.Listbox(frame_mid, height=4)
self.client_listbox.pack(fill=tk.X, padx=5, pady=5)
------ 服务端消息输入区域 (NEW) ------
frame_input = tk.Frame(root)
frame_input.pack(padx=5, pady=5, fill=tk.X)
self.server_msg_entry = tk.Entry(frame_input, state=tk.DISABLED)
self.server_msg_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0,5))
self.server_msg_entry.bind("
self.server_send_btn = tk.Button(frame_input, text="发送", command=self.send_server_message, state=tk.DISABLED)
self.server_send_btn.pack(side=tk.RIGHT)
定时检查消息队列,更新UI
self.update_gui_from_queue()
def log(self, msg):
"""线程安全地添加日志"""
self.message_queue.put(("log", msg))
def update_client_list(self):
self.message_queue.put(("update_list", None))
def set_server_input_state(self, state):
self.message_queue.put(("set_input", state))
def update_gui_from_queue(self):
try:
while True:
task, data = self.message_queue.get_nowait()
if task == "log":
self.log_text.insert(tk.END, data + "\n")
self.log_text.see(tk.END)
elif task == "update_list":
self.client_listbox.delete(0, tk.END)
for addr in self.client_addresses.values():
self.client_listbox.insert(tk.END, f"{addr[0]}:{addr[1]}")
elif task == "set_input":
self.server_msg_entry.config(state=data)
self.server_send_btn.config(state=data)
except queue.Empty:
pass
finally:
self.root.after(100, self.update_gui_from_queue)
def start_server(self):
port_str = self.port_var.get()
if not port_str.isdigit():
messagebox.showerror("错误", "请输入有效的端口号")
return
port = int(port_str)
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(("0.0.0.0", port))
self.server_socket.listen(10)
except Exception as e:
messagebox.showerror("启动失败", f"无法启动服务器: {e}")
return
self.is_running = True
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.NORMAL)
self.set_server_input_state(tk.NORMAL) # 启用服务端输入
self.log(f"服务端已启动,监听端口 {port},你可以在下方输入消息参与聊天")
accept_thread = threading.Thread(target=self.accept_clients, daemon=True)
accept_thread.start()
self.threads.append(accept_thread)
def accept_clients(self):
while self.is_running:
try:
client_sock, addr = self.server_socket.accept()
self.client_sockets.append(client_sock)
self.client_addresses[client_sock] = addr
self.log(f"新连接来自 {addr[0]}:{addr[1]}")
self.update_client_list()
t = threading.Thread(target=self.handle_client, args=(client_sock,), daemon=True)
t.start()
self.threads.append(t)
except:
break
def handle_client(self, client_sock):
addr = self.client_addresses.get(client_sock, ("未知", 0))
while self.is_running:
try:
data = client_sock.recv(1024)
if not data:
break
msg = data.decode("utf-8")
self.log(f"{addr[0]}:{addr[1]} 说: {msg}")
# 广播给所有人(包括发送者自己,让其也能通过网络收到回显)
self.broadcast(f"[{addr[0]}:{addr[1]}]: {msg}")
except:
break
self.log(f"客户端 {addr[0]}:{addr[1]} 断开连接")
if client_sock in self.client_sockets:
self.client_sockets.remove(client_sock)
if client_sock in self.client_addresses:
del self.client_addresses[client_sock]
self.update_client_list()
try:
client_sock.close()
except:
pass
def broadcast(self, msg):
"""向所有在线客户端发送消息(包括发送者自己)"""
for sock in self.client_sockets[:]: # 使用副本避免迭代时修改
try:
sock.send(msg.encode("utf-8"))
except:
pass
def send_server_message(self):
"""服务端主动发送消息"""
if not self.is_running:
return
text = self.server_msg_entry.get().strip()
if not text:
return
full_msg = f"[服务端]: {text}"
self.log(f"服务端说: {text}")
self.broadcast(full_msg)
self.server_msg_entry.delete(0, tk.END)
def stop_server(self):
self.is_running = False
# 关闭所有客户端连接
for sock in self.client_sockets:
try:
sock.close()
except:
pass
self.client_sockets.clear()
self.client_addresses.clear()
if self.server_socket:
try:
self.server_socket.close()
except:
pass
self.update_client_list()
self.set_server_input_state(tk.DISABLED)
self.log("服务端已停止")
self.start_btn.config(state=tk.NORMAL)
self.stop_btn.config(state=tk.DISABLED)
def on_closing(self):
self.stop_server()
self.root.destroy()
---------------------------- 客户端模块 ----------------------------
class ClientWindow:
def init(self, root):
self.root = root
self.root.title("聊天室 - 客户端")
self.root.geometry("450x450")
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
self.client_socket = None
self.connected = False
self.message_queue = queue.Queue()
------ 连接设置 ------
frame_top = tk.LabelFrame(root, text="连接设置")
frame_top.pack(padx=5, pady=5, fill=tk.X)
tk.Label(frame_top, text="IP:").grid(row=0, column=0, sticky=tk.E)
self.ip_var = tk.StringVar(value="127.0.0.1")
tk.Entry(frame_top, textvariable=self.ip_var, width=12).grid(row=0, column=1, padx=5)
tk.Label(frame_top, text="端口:").grid(row=0, column=2, sticky=tk.E)
self.port_var = tk.StringVar(value="5000")
tk.Entry(frame_top, textvariable=self.port_var, width=6).grid(row=0, column=3, padx=5)
tk.Label(frame_top, text="昵称:").grid(row=0, column=4, sticky=tk.E)
self.nick_var = tk.StringVar(value="用户")
tk.Entry(frame_top, textvariable=self.nick_var, width=10).grid(row=0, column=5, padx=5)
self.connect_btn = tk.Button(frame_top, text="连接", command=self.toggle_connection)
self.connect_btn.grid(row=0, column=6, padx=10)
------ 聊天显示 ------
self.chat_text = scrolledtext.ScrolledText(root, state='normal', wrap=tk.WORD)
self.chat_text.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
------ 发送区域 ------
frame_bottom = tk.Frame(root)
frame_bottom.pack(padx=5, pady=5, fill=tk.X)
self.msg_entry = tk.Entry(frame_bottom)
self.msg_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0,5))
self.msg_entry.bind("
self.msg_entry.config(state=tk.DISABLED)
self.send_btn = tk.Button(frame_bottom, text="发送", command=self.send_message, state=tk.DISABLED)
self.send_btn.pack(side=tk.RIGHT)
self.update_gui_from_queue()
def update_gui_from_queue(self):
try:
while True:
task, data = self.message_queue.get_nowait()
if task == "show_chat":
self.chat_text.insert(tk.END, data + "\n")
self.chat_text.see(tk.END)
elif task == "set_input_state":
self.msg_entry.config(state=data)
self.send_btn.config(state=data)
elif task == "set_connect_btn":
self.connect_btn.config(text=data[0], command=data[1])
except queue.Empty:
pass
finally:
self.root.after(100, self.update_gui_from_queue)
def show_chat(self, msg):
self.message_queue.put(("show_chat", msg))
def toggle_connection(self):
if not self.connected:
self.connect_server()
else:
self.disconnect()
def connect_server(self):
ip = self.ip_var.get().strip()
port_str = self.port_var.get().strip()
if not port_str.isdigit():
messagebox.showerror("错误", "端口必须是数字")
return
port = int(port_str)
try:
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client_socket.connect((ip, port))
except Exception as e:
messagebox.showerror("连接失败", f"无法连接到服务器: {e}")
return
self.connected = True
self.show_chat("已连接到服务器")
self.message_queue.put(("set_input_state", tk.NORMAL))
self.message_queue.put(("set_connect_btn", ("断开", self.toggle_connection)))
recv_thread = threading.Thread(target=self.receive_loop, daemon=True)
recv_thread.start()
def disconnect(self):
self.connected = False
if self.client_socket:
try:
self.client_socket.close()
except:
pass
self.show_chat("已断开连接")
self.message_queue.put(("set_input_state", tk.DISABLED))
self.message_queue.put(("set_connect_btn", ("连接", self.toggle_connection)))
def receive_loop(self):
while self.connected:
try:
data = self.client_socket.recv(1024)
if not data:
break
self.show_chat(data.decode("utf-8"))
except:
break
if self.connected:
self.show_chat("与服务器的连接已断开")
self.disconnect()
def send_message(self):
if not self.connected:
return
text = self.msg_entry.get().strip()
if not text:
return
nick = self.nick_var.get().strip() or "用户"
full_msg = f"{nick}: {text}"
try:
self.client_socket.send(full_msg.encode("utf-8"))
# 不再本地回显,因为服务端会广播给所有人(包括自己),避免重复
self.msg_entry.delete(0, tk.END)
except Exception as e:
self.show_chat(f"发送失败: {e}")
def on_closing(self):
self.disconnect()
self.root.destroy()
---------------------------- 主入口 ----------------------------
def main():
root = tk.Tk()
root.title("聊天室 - 选择角色")
root.geometry("250x150")
def open_server():
win = tk.Toplevel()
ServerWindow(win)
def open_client():
win = tk.Toplevel()
ClientWindow(win)
tk.Label(root, text="请选择角色", font=("微软雅黑", 12)).pack(pady=10)
tk.Button(root, text="启动服务端", width=15, command=open_server).pack(pady=5)
tk.Button(root, text="启动客户端", width=15, command=open_client).pack(pady=5)
root.mainloop()
if name == "main":
main()
(1)分析关键代码的功能和使用方法(含有AI生成内容)
关键代码功能
服务端模块:
a.启动服务start_server():创建TCP套接字,绑定 0.0.0.0 和指定端口,开始监听。使用 SO_REUSEADDR 避免重启时端口占用。启动后使用新线程循环接受客户端连接 accept_clients()。
b.接收客户端连接 accept_clients():在独立线程中不断 accept(),将新客户端 socket 存入列表,并为每个客户端启动 handle_client() 线程,实现多客户端并发。
c.处理客户端消息 handle_client(client_sock):循环接收客户端数据,解码后记录日志,并调用 broadcast() 转发给所有已连接客户端。当接收空数据或异常时断开连接,清理资源。
d.广播消息 broadcast(msg):遍历客户端 socket 列表(使用副本),向每个连接发送编码后的消息。服务端主动发送的消息也通过此方法发给所有客户端,实现了服务端参与聊天的功能。
e.服务端输入发送 send_server_message():从界面输入框获取文字,加上 [服务端] 前缀后调用 broadcast()。输入框状态由 set_server_input_state() 控制,服务停止时禁用,启动后启用。
f.线程安全 UI 更新:通过 queue.Queue 和 after 机制,将日志输出、客户端列表更新、输入框状态修改等界面操作统一放入队列,由主线程定时取出执行,避免 tkinter 的线程安全问题。
客户端模块:
a.连接服务器 connect_server():根据用户输入的 IP 和端口建立 TCP 连接,成功后启动接收线程 receive_loop(),并将输入框、发送按钮设为可用。
b.接收消息 receive_loop():线程中循环接收服务器广播的数据,解码后通过队列安全地显示在聊天区域。若连接断开,自动更新界面状态。
c.发送消息 send_message():将输入内容加上昵称前缀发送给服务端。为保持界面洁净,发送后清空输入框,不再本地回显(依赖服务端广播回显)。
d.界面状态控制:连接/断开时通过队列更新按钮文本和输入框状态,防止用户在未连接时发送消息。
使用方法
a.运行程序,弹出角色选择窗口。
b.启动服务端:点击“启动服务端”,在新窗口设置端口(默认 5000),点击“启动服务”。此时服务端下方的输入框可用。
c.启动客户端:点击“启动客户端”,填写服务端 IP、端口、昵称,点击“连接”。
d.服务端和客户端均可输入文字,按回车或点“发送”按钮,所有参与者都能看到带前缀的消息。
e.服务端停止会断开所有客户端;客户端点“断开”或关闭窗口也可退出。
(2)分析生成程序的优点(含有部分AI生成内容)
a.界面直观,操作简便
使用 tkinter 提供了友好的图形界面,角色选择窗口清晰,服务端与客户端窗口布局一致,降低了使用门槛;
状态按钮自动切换,如“连接”↔“断开”、“启动服务”↔“停止服务”;输入框随连接状态自动启/禁用,交互逻辑合理。
b.实时多客户端通信能力强
服务端采用多线程架构,每个客户端连接和消息处理都在独立线程中进行,不会相互阻塞,可同时服务多个客户端;
消息通过服务端中转广播,确保所有在线用户都能实时收到信息,实现了简单的聊天室模型。
c.线程安全设计保障稳定性
所有跨线程的 UI 更新都通过 queue.Queue + after() 转发到主线程,避免了 tkinter 常见的崩溃问题;
广播时使用列表副本 self.client_sockets[:] 遍历,减少了迭代过程中列表被修改的风险。
d.资源管理与错误处理较完善
程序关闭时主动停止服务、断开客户端、关闭 socket,防止资源泄露;
捕获连接异常并用消息框提示,不会因单点故障导致整个程序崩溃;
使用 SO_REUSEADDR 允许端口快速复用,便于调试和重启。
e.代码结构清晰,易于扩展
服务端和客户端分别封装为独立的类,职责分明;
日志、用户列表、消息广播等功能模块化,后续可方便地添加数据库记录、加密、用户认证等功能;
AI生成的内容在方法命名和注释上有较好的可读性,便于新手理解和修改。
f.跨平台兼容
基于 Python 标准库(tkinter、socket、threading),只要安装 Python 3 即可在 Windows、macOS、Linux 上运行,不依赖第三方包。
(3)给出运行过程和结果截图
a.server端视角

b.client端视角

3. 实验过程中遇到的问题和解决过程
- 问题1:队友电脑作为server无法接收到信息
- 问题1解决方案:在上课时寻求老师的帮助,具体操作未知,不过老师在近15分钟的严肃探索中解决了问题!
- 问题2:再次连接时发现仍存在无法接收信息的现象
- 问题2解决方案:依据老师的提示与指导,关停了本人及队友电脑中的防火墙、联想电脑管家的运行。
- 问题3:LLM大模型第一次生成的程序服务器没有回复功能
- 问题3解决方案:向LLM再次输入提示词,增加回复功能。
其他(感悟、思考等)
本次实验在难度上较前两次有明显提升,最初我与队友均感到无从下手。配置 IP 地址时,原以为仅需填入地址即可建立连接,但实际操作中反复出现“连接超时”的提示。我们尝试关闭防火墙、切换至同一无线网络、使用 ipconfig 命令核对地址等方法,都没有解决问题,最终在老师的帮助下,客户端与服务端才终于成功互通、接收到对方消息。
通过这次实验,我深刻体会到,课堂上许多概念看似理解,动手实现时才会暴露真正的知识盲区,例如无法接收通信的问题,都是在反复调试中才真正掌握的。同时我也认识到,大语言模型虽是提升效率的工具,但必须结合自身对需求的分析与判断,进行多次修正,不能盲目照搬。与队友分工协作、各自负责服务端与客户端的排查调试,也让我意识到有条理的合作远胜于个人的反复尝试。可以说,这次实验不仅强化了应用编程的能力,更让我在持续试错与调整中,培育了面对复杂问题时从容应对的耐心。
浙公网安备 33010602011771号