20243405 实验三《Python程序设计》实验报告
课程:《Python程序设计》
班级: 2434
姓名: 付鸿睿
学号:20243405
实验教师:王志强
实验日期:2026年4月27日
必修/选修: 公选课
一、实验内容
1.实验内容
创建服务端和客户端,服务端在特定端口监听多个客户请求。客户端和服务端通过Socket套接字(TCP/UDP)进行通信。
2.实验要求
注意事项:每人必须做一次客户端和一次服务端,且要和队友互相通信。
要求1:
(1)创建服务端和客户端,选择一个通信端口,用Python语言编程实现通信演示程序;
(2)要求发送方输入内容,加密后并传输;接收方收到密文并解密和显示。要求:发方和收方同时输出明文和明文。
(3)程序代码托管到码云。
(4)添加文件操作。(可选项)
要求2:使用LLM生成一个带图形界面的程序
(1)分析关键代码的功能和使用方法
(2)分析生成程序的优点
(3)给出运行过程和结果截图
(4)程序代码托管到码云。
注:在华为ECS服务器(OpenOuler系统)和物理机(Windows/Linux系统)上使用VIM、PDB、IDLE、Pycharm等工具编程实现。
二、实验过程及结果
1.创建服务端和客户端,选择一个通信端口,用Python语言编程实现通信演示程序
通信端口选择 4444
创建服务端和客户端代码如下:
服务端
这里将服务端设为 0.0.0.0 ,作为“万能监听地址”,能够实现接受来自同一局域网内任意设备(客户端)的连接请求。
HOST = "0.0.0.0" #"localhost"
PORT = 4444
server.bind((HOST, PORT))
server.listen(1)
print("服务端已经启动,等待客户端连接……")
conn, addr = server.accept()
print(f"已连接客户端:{addr}")
客户端
对应队友的ip配置,两台电脑全程共同连一个热点,热点ip为10.125.90.xx(根据学号作修改,队友为20241305姚航)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = "10.125.90.5"
PORT = 4444
client.connect((HOST, PORT))
print("已连接服务端!输入exit退出聊天!")
2.要求发送方输入内容,加密后并传输;接收方收到密文并解密和显示。
要求:发方和收方同时输出明文和明文
服务端代码:
import socket #pip install socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = "0.0.0.0" #"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()
客户端代码:
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = "10.125.90.5"
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.程序代码托管到码云
4.使用LLM生成一个带图形界面的程序
代码附上(选择的加密方式为AES加密,端口选择12345(避免出错),ip为对应学号收尾)
服务端代码:
import socketimport threadingimport tkinter as tkfrom tkinter import scrolledtext, filedialog, messageboxfrom Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadfrom Crypto.Random import get_random_bytesimport os
HOST = '0.0.0.0'
PORT = 12345
KEY = b'1234567890abcdef'
class ServerGUI:
def init(self, root):
self.root = root
self.root.title("服务端 - 加密通信+文件传输")
self.root.geometry("700x600")
self.conn = None
self.client_addr = None
self.setup_ui()
self.server_thread = threading.Thread(target=self.start_server, daemon=True)
self.server_thread.start()
def setup_ui(self):
self.log_area = scrolledtext.ScrolledText(self.root, wrap=tk.WORD, width=80, height=20)
self.log_area.pack(pady=10, padx=10)
self.msg_frame = tk.Frame(self.root)
self.msg_frame.pack(pady=5, padx=10, fill=tk.X)
tk.Label(self.msg_frame, text="发送消息:").pack(side=tk.LEFT, padx=5)
self.msg_entry = tk.Entry(self.msg_frame, width=50)
self.msg_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
self.send_btn = tk.Button(self.msg_frame, text="发送文本", command=self.send_text)
self.send_btn.pack(side=tk.LEFT, padx=5)
self.file_frame = tk.Frame(self.root)
self.file_frame.pack(pady=5, padx=10, fill=tk.X)
self.file_path = tk.StringVar()
tk.Button(self.file_frame, text="选择文件", command=self.select_file).pack(side=tk.LEFT, padx=5)
self.file_label = tk.Label(self.file_frame, textvariable=self.file_path, width=40)
self.file_label.pack(side=tk.LEFT, padx=5)
self.send_file_btn = tk.Button(self.file_frame, text="发送文件", command=self.send_file, state=tk.DISABLED)
self.send_file_btn.pack(side=tk.LEFT, padx=5)
def log(self, text):
self.log_area.insert(tk.END, text + "\n")
self.log_area.yview(tk.END)
def select_file(self):
path = filedialog.askopenfilename()
if path:
self.file_path.set(path)
self.send_file_btn.config(state=tk.NORMAL)
def encrypt_data(self, data):
iv = get_random_bytes(16)
cipher = AES.new(KEY, AES.MODE_CBC, iv)
return iv + cipher.encrypt(pad(data, AES.block_size))
def decrypt_data(self, data):
iv = data[:16]
cipher = AES.new(KEY, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(data[16:]), AES.block_size)
def send_text(self):
if not self.conn:
messagebox.showerror("错误", "客户端未连接")
return
text = self.msg_entry.get().strip()
if not text: return
try:
plain = text.encode()
enc = self.encrypt_data(plain)
self.conn.sendall((0).to_bytes(4, 'big'))
self.conn.sendall(len(enc).to_bytes(4, 'big'))
self.conn.sendall(enc)
self.log(f"【发送明文】{text}")
self.log(f"【发送密文】{enc.hex()}\n")
self.msg_entry.delete(0, tk.END)
except:
self.log("发送失败")
def send_file(self):
if not self.conn: return
path = self.file_path.get()
if not os.path.exists(path): return
try:
fn = os.path.basename(path)
fsize = os.path.getsize(path)
with open(path, 'rb') as f:
data = f.read()
enc = self.encrypt_data(data)
self.conn.sendall((1).to_bytes(4, 'big'))
self.conn.sendall(len(fn.encode()).to_bytes(4, 'big'))
self.conn.sendall(fn.encode())
self.conn.sendall(fsize.to_bytes(8, 'big'))
self.conn.sendall(len(enc).to_bytes(4, 'big'))
self.conn.sendall(enc)
self.log(f"【文件发送成功】{fn}")
except:
self.log("文件发送失败")
def handle_client(self):
while True:
try:
typ = int.from_bytes(self.conn.recv(4), 'big')
if typ == 0:
l = int.from_bytes(self.conn.recv(4), 'big')
d = self.conn.recv(l)
txt = self.decrypt_data(d).decode()
self.log(f"【接收明文】{txt}")
self.log(f"【接收密文】{d.hex()}\n")
elif typ == 1:
fnl = int.from_bytes(self.conn.recv(4), 'big')
fn = self.conn.recv(fnl).decode()
fsz = int.from_bytes(self.conn.recv(8), 'big')
l = int.from_bytes(self.conn.recv(4), 'big')
d = self.conn.recv(l)
raw = self.decrypt_data(d)
with open(f"recv_{fn}", 'wb') as f:
f.write(raw)
self.log(f"【收到文件】{fn} 已保存")
self.log(f"【文件密文】{d.hex()}\n")
except:
self.log("客户端断开")
self.conn = None
break
def start_server(self):
with socket.socket() as s:
s.bind((HOST, PORT))
s.listen(1)
self.log(f"服务端已启动,端口 {PORT}")
while True:
self.conn, addr = s.accept()
self.log(f"客户端已连接:{addr}")
threading.Thread(target=self.handle_client, daemon=True).start()
if name == "main":
root = tk.Tk()
app = ServerGUI(root)
root.mainloop()
客户端代码:
import socketimport threadingimport tkinter as tkfrom tkinter import scrolledtext, filedialog, messageboxfrom Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadfrom Crypto.Random import get_random_bytesimport os
HOST = '10.125.90.5' # 服务端电脑的IP
PORT = 12345
KEY = b'1234567890abcdef'# ======================================================
class ClientGUI:
def init(self, root):
self.root = root
self.root.title("客户端 - 加密通信+文件传输")
self.root.geometry("700x600")
self.sock = None
self.connected = False
self.setup_ui()
threading.Thread(target=self.connect_server, daemon=True).start()
def setup_ui(self):
self.log_area = scrolledtext.ScrolledText(self.root, wrap=tk.WORD, width=80, height=20)
self.log_area.pack(pady=10, padx=10)
self.msg_frame = tk.Frame(self.root)
self.msg_frame.pack(pady=5, padx=10, fill=tk.X)
tk.Label(self.msg_frame, text="发送消息:").pack(side=tk.LEFT, padx=5)
self.msg_entry = tk.Entry(self.msg_frame, width=50)
self.msg_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
self.send_btn = tk.Button(self.msg_frame, text="发送文本", command=self.send_text)
self.send_btn.pack(side=tk.LEFT, padx=5)
self.file_frame = tk.Frame(self.root)
self.file_frame.pack(pady=5, padx=10, fill=tk.X)
self.file_path = tk.StringVar()
tk.Button(self.file_frame, text="选择文件", command=self.select_file).pack(side=tk.LEFT, padx=5)
self.file_label = tk.Label(self.file_frame, textvariable=self.file_path, width=40)
self.file_label.pack(side=tk.LEFT, padx=5)
self.send_file_btn = tk.Button(self.file_frame, text="发送文件", command=self.send_file, state=tk.DISABLED)
self.send_file_btn.pack(side=tk.LEFT, padx=5)
def log(self, text):
self.log_area.insert(tk.END, text + "\n")
self.log_area.yview(tk.END)
def encrypt_data(self, data):
iv = get_random_bytes(16)
cipher = AES.new(KEY, AES.MODE_CBC, iv)
return iv + cipher.encrypt(pad(data, AES.block_size))
def decrypt_data(self, data):
iv = data[:16]
cipher = AES.new(KEY, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(data[16:]), AES.block_size)
def select_file(self):
path = filedialog.askopenfilename()
if path:
self.file_path.set(path)
self.send_file_btn.config(state=tk.NORMAL)
def send_text(self):
if not self.connected:
messagebox.showerror("错误","未连接服务端")
return
text = self.msg_entry.get().strip()
if not text: return
try:
plain = text.encode()
enc = self.encrypt_data(plain)
self.sock.sendall((0).to_bytes(4,'big'))
self.sock.sendall(len(enc).to_bytes(4,'big'))
self.sock.sendall(enc)
self.log(f"【发送明文】{text}")
self.log(f"【发送密文】{enc.hex()}\n")
self.msg_entry.delete(0,tk.END)
except:
self.log("发送失败")
def send_file(self):
if not self.connected: return
path = self.file_path.get()
if not os.path.exists(path): return
try:
fn = os.path.basename(path)
fsz = os.path.getsize(path)
with open(path,'rb') as f:
data = f.read()
enc = self.encrypt_data(data)
self.sock.sendall((1).to_bytes(4,'big'))
self.sock.sendall(len(fn.encode()).to_bytes(4,'big'))
self.sock.sendall(fn.encode())
self.sock.sendall(fsz.to_bytes(8,'big'))
self.sock.sendall(len(enc).to_bytes(4,'big'))
self.sock.sendall(enc)
self.log(f"【文件发送成功】{fn}")
except:
self.log("文件发送失败")
def receive_loop(self):
while self.connected:
try:
typ = int.from_bytes(self.sock.recv(4),'big')
if typ == 0:
l = int.from_bytes(self.sock.recv(4),'big')
d = self.sock.recv(l)
txt = self.decrypt_data(d).decode()
self.log(f"【收到明文】{txt}")
self.log(f"【收到密文】{d.hex()}\n")
elif typ == 1:
fnl = int.from_bytes(self.sock.recv(4),'big')
fn = self.sock.recv(fnl).decode()
fsz = int.from_bytes(self.sock.recv(8),'big')
l = int.from_bytes(self.sock.recv(4),'big')
d = self.sock.recv(l)
raw = self.decrypt_data(d)
with open(f"recv_{fn}",'wb') as f:
f.write(raw)
self.log(f"【收到文件】{fn} 已保存")
self.log(f"【文件密文】{d.hex()}\n")
except:
self.log("服务端断开")
self.connected = False
break
def connect_server(self):
try:
self.sock = socket.socket()
self.sock.connect((HOST, PORT))
self.connected = True
self.log(f"成功连接服务端 {HOST}")
threading.Thread(target=self.receive_loop, daemon=True).start()
except Exception as e:
self.log(f"连接失败:{e}")
messagebox.showerror("错误",str(e))
if name == "main":
root = tk.Tk()
app = ClientGUI(root)
root.mainloop()
5.分析关键代码的功能和使用方法
(1)日志显示函数
def log(self, text):
self.log_area.insert(tk.END, text + "\n")**
self.log_area.yview(tk.END)
功能:在界面滚动文本框中追加输出文字,自动换行并自动到最新消息位置。记录服务启动、客户端连接、收发明文密文、文件状态、报错信息。
使用方法:自动调用,不需要手动操作,所有运行信息都会自动打印在界面日志区。
(2)加解密操作
AES 加密函数
def encrypt_data(self, data):
iv = get_random_bytes(16)
cipher = AES.new(KEY, AES.MODE_CBC, iv)
return iv + cipher.encrypt(pad(data, AES.block_size))
AES 解密函数
def decrypt_data(self, data):
iv = data[:16]
cipher = AES.new(KEY, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(data[16:]), AES.block_size)
功能:采用 AES-CBC 模式加密数据:解密后,还原原始文本或文件二进制数据。
使用方法:程序自动调用,无需手动修改。
(3)发送文本功能函数
def send_text(self):
if not self.conn:
messagebox.showerror("错误", "客户端未连接")
return
text = self.msg_entry.get().strip()
if not text: return
try:
plain = text.encode()
enc = self.encrypt_data(plain)
self.conn.sendall((0).to_bytes(4, 'big'))
self.conn.sendall(len(enc).to_bytes(4, 'big'))
self.conn.sendall(enc)
self.log(f"【发送明文】{text}")
self.log(f"【发送密文】{enc.hex()}\n")
self.msg_entry.delete(0, tk.END)
except:
self.log("发送失败")
功能:获取输入框文本 → 编码 → AES 加密 → 按格式发送给客户端 → 日志打印明文和密文。
使用方法:在界面输入文字,点击即可自动完成加密发送。
(4)发送文件功能函数
def send_file(self):
if not self.conn: return
path = self.file_path.get()
if not os.path.exists(path): return
try:
fn = os.path.basename(path)
fsize = os.path.getsize(path)
with open(path, 'rb') as f:
data = f.read()
enc = self.encrypt_data(data)
self.conn.sendall((1).to_bytes(4, 'big'))
self.conn.sendall(len(fn.encode()).to_bytes(4, 'big'))
self.conn.sendall(fn.encode())
self.conn.sendall(fsize.to_bytes(8, 'big'))
self.conn.sendall(len(enc).to_bytes(4, 'big'))
self.conn.sendall(enc)
self.log(f"【文件发送成功】{fn}")
except:
self.log("文件发送失败")
功能:读取本地文件 → 获取文件名和大小 → 二进制加密 → 按协议依次发送类型、文件名、文件大小、加密数据。
使用方法:点击选择文件 → 选中文件 → 点击发送文件,自动加密传输。(接收方收到的文件在工程目录下)
(5)接收消息与文件处理
def handle_client(self):
while True:
try:
typ = int.from_bytes(self.conn.recv(4), 'big')
if typ == 0:
l = int.from_bytes(self.conn.recv(4), 'big')
d = self.conn.recv(l)
txt = self.decrypt_data(d).decode()
self.log(f"【接收明文】{txt}")
self.log(f"【接收密文】{d.hex()}\n")
elif typ == 1:
fnl = int.from_bytes(self.conn.recv(4), 'big')
fn = self.conn.recv(fnl).decode()
fsz = int.from_bytes(self.conn.recv(8), 'big')
l = int.from_bytes(self.conn.recv(4), 'big')
d = self.conn.recv(l)
raw = self.decrypt_data(d)
with open(f"recv_{fn}", 'wb') as f:
f.write(raw)
self.log(f"【收到文件】{fn} 已保存")
self.log(f"【文件密文】{d.hex()}\n")
except:
self.log("客户端断开")
self.conn = None
break
功能:循环监听客户端数据:
类型 0:接收加密文本 → 解密 → 显示明文、密文;
类型 1:接收文件名、文件大小、加密文件数据 → 解密 → 自动保存为 recv_文件名。
使用方法:客户端连接后自动后台运行,无需手动操作,自动接收消息和文件并保存。
6.分析生成程序的优点
LLM生成代码与我的代码对比:
(1)我的代码
原理简单,行数少,适合新手理解。但保密性差,且只能对普通文本加密,不能处理文件。
没有图形界面,只能在命令行输入消息、查看密文和明文,只能完成基础文字聊天。
(2)LLM生成代码
采用AES-CBC 专业加密,需要固定密钥,加密标准正规,安全程度高。
带有图形可视化界面,配有消息输入框、日志显示区域、文件选择按钮。
文字通信和文件双向传输,能自动识别文本消息和文件消息,接收的文件可自动保存。
总结:LLM生成的代码专业度更高,更复杂,能满足更加严格的加密要求,反之我的代码理解与使用简便,却因较为简单而保密性较差。
7.成果截图

程序代码托管到码云

三、实验过程中遇到的问题和解决过程
1.两台电脑连同一个手机热点,本机服务端与队友客户端可以正常通信;交换角色后,一直连接失败,提示 10061 目标计算机积极拒绝,连不上服务端。
解决方案:我的电脑中,专用网络和公用网络防火墙忘记关闭;关闭后重新运行服务端和客户端,就能正常连接。
2.复制代码到 PyCharm 运行,直接报错,提示tk(图形页面)、pycryptodome(AES加密) 模块不存在,程序打不开、运行失败。
解决方案:在 PyCharm 终端执行安装命令:pip install pycryptodome 。 tk本来下载安装环境的时候应该自带(但当时下载的是无线版,没有带着tk这个插件),重新下载在线版,重新给pycharm配置了新的解释器;安装完成后重启项目,代码即可正常运行。
3.先点开客户端,再开服务端导致无法连接。
解决方案:先启动服务端,等待服务端显示启动成功后,再运行客户端。
4.其他(感悟、思考等)
在这之前我已经很久没有做过组队实验了,按以往的“经验”来看,这种时候我一般都是拖后腿那个(实验不悦体质是这样的)因此遭到了不少人的“嫌弃”。
这次与一位我并不是很熟的同学组队,他因为本专业只有他一个人选了这节课,除了我以外似乎没有认识的人,只能找到我了(没错互相拣剩下的),不过该说不说这雀食是一个互相了解的机会。
起初在我服务端他客户端的时候还算顺利,不过反过来的时候就频频出现问题,尤其是软教的网络经常波动,我们的热点经常断掉,非常让人火大···最后查了一圈,发现是我电脑防火墙的问题,将之一并关掉,问题就有所缓解了。
关于防火墙的问题,在近期计网实验中也有出现过(那次是队友防火墙的问题),两台电脑之间无法Ping通,最后是靠学长帮忙才得以解决。总的来说,在专业课以及专业课衍生学科的学习上,对于我一个本来以文科与化学生物见长的人来说,还有很长一段路要走。
马克思主义哲学有言,实践是检验真理的唯一标准,而探索真理的过程,不断实践也是必不可少的。
5.参考资料:
《程序设计与数据结构教程(第二版)》https://book.douban.com/subject/26851579/
《Python官方文档》https://docs.python.org/zh-cn/3/
《PyCharm调试指南》https://www.jetbrains.com/help/pycharm/debugging-code.html
《Git入门教程》https://gitee.com/help/categories/5
···

浙公网安备 33010602011771号