20234221 实验三《Python程序设计》实验报告

20234221 2025-2026-2 《Python程序设计》实验三报告

课程:《Python程序设计》
班级: 2342
姓名: 董胡宇
学号: 20234221
实验教师:王志强
实验日期:2026年4月28日
必修/选修: 专选课

1.实验内容

创建服务端和客户端,服务端在特定端口监听多个客户请求。客户端和服务端通过Socket套接字(TCP/UDP)进行通信。
注意事项:
每人必须做一次客户端和一次服务端,且要和队友(标注学号姓名)互相通信。
要求1:
(1)创建服务端和客户端,选择一个通信端口,用Python语言编程实现通信演示程序;
(2)要求发送方输入内容,并传输;接收方收到信息并显示。
要求2:使用LLM生成一个带图形界面的程序
(1)分析关键代码的功能和使用方法
(2)分析生成程序的优点
(3)给出运行过程和结果截图
注:在华为ECS服务器(OpenOuler系统)和物理机(Windows/Linux系统)上使用VIM、PDB、IDLE、Pycharm等工具编程实现。

2. 实验过程及结果

要求1:
(1)创建服务端和客户端,选择一个通信端口,用Python语言编程实现通信演示程序;
(2)要求发送方输入内容,并传输;接收方收到信息并显示。
本实验由我与20234221谢雨峰同学一同完成。
首先按win+r输入cmd再输入ipconfig查询本机IP地址以方便联络,可以看出IP地址为192.168.64.141。
image

下图为我作为服务器端与对方进行通信,成功运行。
image

下图为我作为客户端与对方进行通信,成功运行。
image

要求2:使用LLM生成一个带图形界面的程序
(1)分析关键代码的功能和使用方法
(2)分析生成程序的优点
(3)给出运行过程和结果截图
我们选择使用Deepseek生成程序。因代码行数太多不便截图,故将代码粘贴如下:
一、客户端代码
import socket
import threading
import tkinter as tk
from tkinter import scrolledtext, END, Entry, Button

class SocketClient:
def init(self, root):
# 主窗口配置
self.root = root
self.root.title("TCP 客户端")
self.root.geometry("600x500")

网络参数

self.server_host = "127.0.0.1"
self.server_port = 8888
self.client_socket = None

界面组件

tk.Label(root, text="聊天日志", font=("黑体", 12)).pack(pady=5)
self.log_text = scrolledtext.ScrolledText(root, width=70, height=15)
self.log_text.pack(pady=5)

消息输入框

self.msg_entry = Entry(root, width=50, font=("黑体", 12))
self.msg_entry.pack(pady=5, fill=tk.X)

按钮区域

btn_frame = tk.Frame(root)
btn_frame.pack(pady=5)
Button(btn_frame, text="连接服务端", command=self.connect_server,
bg="blue", fg="white").grid(row=0, column=0, padx=5)
Button(btn_frame, text="发送消息", command=self.send_msg,
bg="green", fg="white").grid(row=0, column=1, padx=5)

self.log("客户端已启动,点击【连接服务端】开始通信")

def log(self, msg):
self.log_text.insert(END, msg + "\n")
self.log_text.see(END)

def receive_msg(self):
# 接收服务端消息(子线程)
while True:
try:
data = self.client_socket.recv(1024).decode("utf-8")
if data:
self.log(data)
except:
self.log("与服务端断开连接")
self.client_socket.close()
break

def connect_server(self):
# 连接服务端
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.client_socket.connect((self.server_host, self.server_port))
self.log(f"成功连接服务端:{self.server_host}:{self.server_port}")
# 启动消息接收线程
recv_thread = threading.Thread(target=self.receive_msg)
recv_thread.daemon = True
recv_thread.start()
except Exception as e:
self.log(f"连接失败:{str(e)}")

def send_msg(self):
# 发送消息到服务端
msg = self.msg_entry.get().strip()
if not msg or not self.client_socket:
return
try:
self.client_socket.send(msg.encode("utf-8"))
self.log(f"我:{msg}")
self.msg_entry.delete(0, END)
except:
self.log("发送失败,服务端未连接")

if name == "main":
window = tk.Tk()
app = SocketClient(window)
window.mainloop()

二、服务器端代码
import socket
import threading
import tkinter as tk
from tkinter import scrolledtext, END

class SocketServer:
def init(self, root):
# 主窗口配置
self.root = root
self.root.title("TCP 服务端 - 多客户端监听")
self.root.geometry("600x500")

网络参数

self.host = "127.0.0.1" # 本地地址
self.port = 8888 # 监听端口
self.server_socket = None
self.clients = [] # 存储所有连接的客户端

界面组件

tk.Label(root, text="服务端日志", font=("黑体", 12)).pack(pady=5)
self.log_text = scrolledtext.ScrolledText(root, width=70, height=20)
self.log_text.pack(pady=5)

tk.Button(root, text="启动服务", command=self.start_server,
bg="green", fg="white", font=("黑体", 11)).pack(pady=5)

日志初始化

self.log("服务端已启动,等待启动服务...")

def log(self, msg):
# 往日志框追加信息
self.log_text.insert(END, msg + "\n")
self.log_text.see(END) # 自动滚动到底部

def handle_client(self, client_socket, client_addr):
# 处理单个客户端通信(子线程)
self.log(f"客户端 {client_addr} 已连接")
self.clients.append(client_socket)

while True:
try:
# 接收客户端消息
data = client_socket.recv(1024).decode("utf-8")
if not data:
break
self.log(f"来自 {client_addr}:{data}")
# 广播消息给所有客户端
self.broadcast(f"{client_addr}:{data}", client_socket)
except:
break

客户端断开连接

self.clients.remove(client_socket)
client_socket.close()
self.log(f"客户端 {client_addr} 已断开")

def broadcast(self, msg, exclude_socket=None):
# 向所有客户端广播消息
for client in self.clients:
if client != exclude_socket:
try:
client.send(msg.encode("utf-8"))
except:
client.close()
self.clients.remove(client)

def start_server(self):
# 启动服务端监听(子线程)
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((self.host, self.port))
self.server_socket.listen(5) # 最大等待连接数

self.log(f"服务已启动:{self.host}:{self.port},等待客户端连接...")

循环接收客户端连接

def accept_connections():
while True:
client_sock, addr = self.server_socket.accept()
# 为每个客户端创建独立线程
thread = threading.Thread(target=self.handle_client, args=(client_sock, addr))
thread.daemon = True
thread.start()

启动连接接收线程

accept_thread = threading.Thread(target=accept_connections)
accept_thread.daemon = True
accept_thread.start()

if name == "main":
window = tk.Tk()
app = SocketServer(window)
window.mainloop()

运行结果展示如下:
下图为我作为客户端与对方通信,成功运行。
image

下图为我作为服务器端与对方通信,成功运行。
image

注:由于此段代码运行于离开软教后,IP地址发生变化,我们使用上文中提到的方法进行了重新获取,此处不再赘述。

代码分析:
一、客户端代码分析(SocketClient)
关键功能
1.GUI界面(tkinter)
标题为“TCP客户端”,包含聊天日志区(ScrolledText)、消息输入框(Entry)和两个按钮(“连接服务端”、“发送消息”)。
日志区自动追加消息并滚动到底部。
2.连接服务器(connect_server)
创建socket.socket(AF_INET,SOCK_STREAM),连接固定的服务器端IP地址(如我的192.168.64.141)。
连接成功后启动一个守护线程专门接收服务端消息(receive_msg),避免阻塞GUI。
3.消息接收(receive_msg)
在循环中调用client_socket.recv(1024),解码后显示在日志区。
若发生异常(如连接断开),关闭socket并退出循环。
4.消息发送(send_msg)
获取输入框内容,编码后通过client_socket.send()发送。
发送成功后清空输入框,并将自己发送的消息显示为“我:xxx”。

使用方法
将self.server_host改为本机实际局域网IP。
运行客户端脚本,弹出窗口。
先点击“连接服务端”按钮,与服务端建立连接。
在输入框输入文字,点击“发送消息”,消息发送至服务端。
接收到的服务端消息会实时显示在聊天日志中。

二、服务端代码分析(SocketServer)
关键功能
1.窗口与日志
标题“TCP服务端多客户端监听”,包含日志区和一个“启动服务”按钮。
2.启动服务(start_server)
创建TCPsocket,设置SO_REUSEADDR选项(允许快速重启)。
绑定到固定IP地址(如我的192.168.64.141),开始监听。
在一个线程中循环接受连接:
server_socket.accept()返回新客户端的socket和地址。
为每个客户端创建独立线程,执行handle_client。
3.客户端处理(handle_client)
将新客户端socket加入self.clients列表。
循环接收该客户端消息,调用broadcast广播给其他客户端(排除发送者自身)。
若连接异常或收到空数据,断开该客户端并从列表中移除。
4.广播消息(broadcast)
遍历self.clients,向除exclude_socket外的所有客户端发送消息。
若发送失败,移除失效的客户端。

使用方法
将self.host改为本机实际局域网IP。
运行服务端脚本,点击“启动服务”按钮。
服务端开始监听,客户端连接后会显示连接日志。
任意客户端发送消息,其他在线客户端及服务端日志都会收到广播。

该聊天程序的优点主要体现在以下几个方面:
1.多客户端并发处理
服务端为每一个新连接的客户端都创建了独立的线程,可以同时与多个用户通信,能实现群聊功能,不会因为一个客户端的处理阻塞其他连接。
2.线程安全的广播机制
向所有客户端广播消息时,会遍历已连接的客户端列表,并捕获发送过程中可能出现的异常。一旦发现某个客户端已断开,会自动将其关闭并从列表中移除,保证广播过程的稳定,避免因个别客户端掉线而导致程序崩溃。
3.界面实时反馈,操作流畅
利用tkinter与多线程结合,消息的接收和网络监听都放在子线程中运行,主线程只负责界面更新。这样收发数据时图形界面不会卡顿,日志能够即时追加并自动滚动到底部,用户体验良好。
4.合理的守护线程设计
接收消息的线程、服务端监听连接的线程都被设置为守护线程(daemon)。主程序窗口关闭时,这些后台线程会自动结束,不会残留后台进程,程序退出干净利落。
5.清晰的模块化结构
客户端和服务端分别封装为独立的类,网络逻辑与界面逻辑分离,功能划分明确。这种结构便于阅读、修改和扩展,例如可以方便地添加私聊、昵称等功能。
6.自动的断线检测与资源清理
无论是客户端还是服务端,都能在接收或发送时感知连接断开,并立即在日志中给出提示,同时清理对应的socket资源、从客户端列表中移除。整个过程无需用户手动干预,保证了资源的及时释放。
7.简洁高效的群聊通信模型
服务端接收到一条消息后,会将其广播给除发送者外的所有其他客户端,并在服务端日志中同步显示,这正是一个典型的群聊转发逻辑。实现方式简单直接,非常适合作为局域网聊天工具的基础框架。

3. 实验过程中遇到的问题和解决过程

  • 问题1:客户端无法与服务器端连接。
  • 问题1解决方案:排查问题,发现代码中ip地址对应错误,修改后重新连接。
  • 问题2:分不清TCP和UDP。随便把 SOCK_STREAM 和 SOCK_DGRAM 乱改,不知道 TCP 是面向连接、UDP 是无连接。
    问题2解决方案:TCP:socket.AF_INET,socket.SOCK_STREAM,必须bind→listen→accept→connect;UDP:socket.SOCK_DGRAM,不需要连接,直接收发。

其他(感悟、思考等)

本次实验展示了基于Socket的服务端与客户端通信,让我学到了数据传输、端口等相关网络知识,深入学习了多线程、TCP协议、tkinter与子线程协作等专业知识,了解了服务器与客户端进行通信交流的流程与逻辑,受益匪浅。
王老师带领我们在课堂上编写的只是一段不长的程序,但我们生活中具有重要地位的网络通信正是在这样一段看起来甚至能说是“简单”的程序上发展起来的。许多看似复杂的系统,往往就起源于这样一段百来行的代码。

参考资料

posted @ 2026-05-12 16:56  雒风  阅读(7)  评论(3)    收藏  举报