20241207 实验三《Python程序设计》实验报告
20241207 2025-2026-2 《Python程序设计》实验三报告
课程:《Python程序设计》
班级: 2412
姓名: 陈琪雅
学号:20241207
实验教师:王志强
实验日期:2026年5月10日
必修/选修: 公选课
1.实验内容
本次实验要求使用Python创建加密通信程序,包括服务端和客户端。服务端在指定端口监听。双方能够实现文本消息的加密传输,同时输出明文和密文。
第一部分(命令行版):
编写TCP服务端和客户端程序,使用AES-256-GCM对称加密,实现加密通信。
发送方输入内容加密后传输,接收方解密并显示明文。
双方同时输出明文和对应的密文(十六进制形式)。
与队友互相通信,并将代码托管到码云。
第二部分(图形界面版):
使用LLM生成一个带图形界面的加密通信程序(tkinter实现),补充文件操作。
分析关键代码的功能和使用方法,分析生成程序的优点。
给出运行过程和结果截图,代码托管到码云。
2. 实验过程及结果
2.1撰写代码
2.1.1 加密实现思路
采用AES-256-GCM对称加密算法。GCM模式是流式加密,无需手动处理填充,可直接加密任意长度的数据,实现较为简单
密钥固定为32字节(随机设定为b'1234567890abcdef12345678'),nonce固定为12字节(随机设定为b'123456789012')。
实验中为调用函数需使用cryptography库,预先进行了“pip install cryptography”安装操作。

2.1.2 服务端代码
import socket
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = b'1234567890abcdef12345678'
def encrypt(plaintext):
nonce = b'123456789012'
return nonce + AESGCM(key).encrypt(nonce, plaintext.encode('utf-8'), None)
def decrypt(ciphertext):
nonce = ciphertext[:12]
data = ciphertext[12:]
return AESGCM(key).decrypt(nonce, data, None).decode('utf-8')
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = "172.20.10.7"
port = 8080
server.bind((host, port))
server.listen(1)
print("加密通信服务端")
print(f"我的学号:20241207")
print(f"队友学号:20243222")
print(f"监听IP:{host}")
print(f"端口:8080")
print()
print("服务器已启动,等待客户端连接......")
conn, addr = server.accept()
print(f"已连接客户端: {addr}")
while True:
encrypted_data = conn.recv(1024)
if not encrypted_data:
break
plaintext = decrypt(encrypted_data)
if plaintext == "exit":
print("聊天结束")
break
print(f"接收密文: {encrypted_data.hex()}")
print(f"客户端: {plaintext}")
reply = input("我: ")
encrypted_reply = encrypt(reply)
conn.send(encrypted_reply)
if reply == "exit":
break
conn.close()
server.close()
2.1.3 客户端代码
import socket
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = b'1234567890abcdef12345678'
def encrypt(plaintext):
nonce = b'123456789012'
return nonce + AESGCM(key).encrypt(nonce, plaintext.encode('utf-8'), None)
def decrypt(ciphertext):
nonce = ciphertext[:12]
data = ciphertext[12:]
return AESGCM(key).decrypt(nonce, data, None).decode('utf-8')
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_host = "172.20.10.22"
port = 8080
client.connect((server_host, port))
print("加密通信客户端")
print(f"我的学号:20241207")
print(f"队友学号:20243222")
print(f"连接服务端IP:172.20.20.22")
print(f"端口:8080")
print()
print("已连接服务端!输入exit退出聊天!")
while True:
msg = input("我: ")
encrypted_msg = encrypt(msg)
client.send(encrypted_msg)
if msg == "exit":
break
encrypted_reply = client.recv(1024)
if not encrypted_reply:
break
reply = decrypt(encrypted_reply)
if reply == "exit":
print("聊天结束")
break
print(f"接收密文: {encrypted_reply.hex()}")
print(f"服务端: {reply}")
client.close()
2.1.4 运行结果
与队友连接同一局域网,将IP手动修改为学号。
使用ipconfig确认IP,再用ping测试连通性。
两电脑进行连接。(队友连我电脑截图)

启动服务端,再启动客户端连接:
我为服务端(IP为172.20.10.7)

我为客户端(连接服务端IP172.20.10.22)

2.2LLM撰写的图形代码
(代码来自AI:Deepseek)
使用tkinter构建GUI版本,同样采用AES-256-GCM加密,并增加文件传输功能(加分项)和多线程处理。
加密模块:
encrypt和decrypt函数直接使用命令行版本,采用AES-256-GCM,保证数据机密性。
网络模块:
服务端使用 threading.Thread分别启动_accept_client(接受连接)和 _receive_messages(接收消息)两个独立线程,避免阻塞GUI主循环;客户端同样使用独立线程 _receive_messages监听服务端消息。客户端可指定 IP 连接。
GUI模块:
tkinter创建主窗口,LabelFrame分组显示设置区域,ScrolledText作为日志滚动框。
按钮绑定回调函数(start_server、stop_server、connect_server、disconnect),实现状态控制。
消息输入框绑定 Return事件,支持回车发送。
文件传输预留:
使用send_file 和 receive_file搭建框架,通过 [FILE] 标记区分文件传输和普通消息,实际分块加密传输逻辑未完整实现。
2.2.1服务端代码
import socket #提供网络通信功能
import threading #实现多线程,避免界面卡顿
import tkinter as tk #图形界面基础库
from tkinter import scrolledtext, filedialog #滚动文本框和文件对话框
from cryptography.hazmat.primitives.ciphers.aead import AESGCM #AES-GCM加密
#AES-256需要32字节密钥
KEY = b'1234567890abcdef12345678'
#GCM模式需要12字节nonce
NONCE = b'123456789012'
def encrypt(plaintext: str) -> bytes:
# 加密:将明文转换为密文,返回nonce+密文字节串
return NONCE + AESGCM(KEY).encrypt(NONCE, plaintext.encode('utf-8'), None)
def decrypt(ciphertext: bytes) -> str:
# 解密:从密文中提取nonce,解密并返回明文字符串
nonce = ciphertext[:12] #前12字节是nonce
data = ciphertext[12:] #剩余部分是密文
# 解密后解码为UTF-8字符串
return AESGCM(KEY).decrypt(nonce, data, None).decode('utf-8')
class ServerGUI:
def __init__(self):
# 创建主窗口
self.root = tk.Tk()
self.root.title("加密通信服务端 - 学号:20241207")
self.root.geometry("600x500") # 窗口大小
# 网络相关变量
self.server_socket = None # 监听socket(用于接受新连接)
self.client_conn = None # 与客户端通信的socket
self.running = False # 控制服务端运行状态
# 构建界面
self._create_widgets()
def _create_widgets(self):
"""创建所有图形界面组件"""
# 控制区域(端口设置、启动/停止按钮)
frame = tk.LabelFrame(self.root, text="服务端设置", padx=5, pady=5)
frame.pack(fill=tk.X, padx=10, pady=5)
# 端口标签和输入框
tk.Label(frame, text="端口:").grid(row=0, column=0)
self.port_entry = tk.Entry(frame, width=8)
self.port_entry.insert(0, "8080") # 默认端口
self.port_entry.grid(row=0, column=1)
# 启动按钮:绑定 start_server 方法
self.start_btn = tk.Button(frame, text="启动服务端", command=self.start_server)
self.start_btn.grid(row=0, column=2, padx=10)
# 停止按钮:初始禁用,等启动后启用
self.stop_btn = tk.Button(frame, text="停止服务端", command=self.stop_server, state=tk.DISABLED)
self.stop_btn.grid(row=0, column=3)
# 日志显示区域(滚动文本框)
self.log_area = scrolledtext.ScrolledText(self.root, state=tk.DISABLED, wrap=tk.WORD)
self.log_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# 消息发送区域
msg_frame = tk.Frame(self.root)
msg_frame.pack(fill=tk.X, padx=10, pady=5)
self.msg_entry = tk.Entry(msg_frame)
self.msg_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0,5))
# 绑定回车键,按下回车直接发送消息
self.msg_entry.bind("<Return>", self.send_message)
self.send_btn = tk.Button(msg_frame, text="发送", command=self.send_message)
self.send_btn.pack(side=tk.RIGHT)
# 文件发送按钮(扩展功能)
self.file_btn = tk.Button(self.root, text="发送文件", command=self.send_file)
self.file_btn.pack(pady=5)
def log(self, msg):
"""在日志区域追加一条消息(线程安全,直接调用)"""
self.log_area.config(state=tk.NORMAL) # 临时解锁文本框
self.log_area.insert(tk.END, msg + "\n")
self.log_area.see(tk.END) # 自动滚动到底部
self.log_area.config(state=tk.DISABLED) # 重新锁定,防止用户编辑
# ---------- 服务端核心逻辑 ----------
def start_server(self):
"""启动服务端:创建socket、绑定、监听,并启动接受客户端线程"""
port = int(self.port_entry.get())
try:
# 创建TCP socket
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定到所有网络接口(0.0.0.0),允许其他电脑连接
self.server_socket.bind(('0.0.0.0', port))
# 开始监听,最多1个等待连接
self.server_socket.listen(1)
self.log(f"[系统] 服务端启动,监听端口 {port}")
# 更新按钮状态
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.NORMAL)
self.running = True
# 启动一个线程专门接受客户端连接,避免阻塞主界面
threading.Thread(target=self._accept_client, daemon=True).start()
except Exception as e:
self.log(f"[错误] 启动失败: {e}")
def _accept_client(self):
"""等待客户端连接(在独立线程中运行)"""
try:
# accept() 会阻塞直到有客户端连接
self.client_conn, addr = self.server_socket.accept()
self.log(f"[系统] 客户端 {addr} 已连接")
# 启动另一个线程接收该客户端发来的消息
threading.Thread(target=self._receive_messages, daemon=True).start()
except Exception:
# 如果服务端已关闭,accept可能抛出异常,忽略即可
pass
def _receive_messages(self):
"""持续接收客户端消息(在独立线程中运行)"""
while self.running and self.client_conn:
try:
# 接收密文数据(最多4096字节)
enc_data = self.client_conn.recv(4096)
if not enc_data: # 连接关闭
break
# 解密得到明文
plain = decrypt(enc_data)
if plain == "exit":
self.log("[系统] 客户端请求退出")
break
# 判断是否为文件传输指令(扩展功能)
if plain.startswith("[FILE]"):
self.receive_file(enc_data)
else:
# 显示明文和密文的十六进制形式
self.log(f"[客户端] 明文: {plain}")
self.log(f"[客户端] 密文: {enc_data.hex()}")
except Exception as e:
self.log(f"[错误] 接收消息异常: {e}")
break
# 退出循环后清理连接
self.stop_server()
def send_message(self, event=None):
"""发送消息:获取输入框文本,加密后发送"""
if not self.client_conn:
self.log("[系统] 没有客户端连接,无法发送")
return
msg = self.msg_entry.get().strip()
if not msg:
return
try:
# 加密消息
enc_msg = encrypt(msg)
self.client_conn.send(enc_msg)
# 在本地日志中显示发送的明文和密文
self.log(f"[我] 明文: {msg}")
self.log(f"[我] 密文: {enc_msg.hex()}")
self.msg_entry.delete(0, tk.END) # 清空输入框
if msg == "exit":
self.stop_server()
except Exception as e:
self.log(f"[错误] 发送失败: {e}")
def send_file(self):
"""发送文件(加分项框架)"""
if not self.client_conn:
self.log("[系统] 没有客户端连接,无法发送文件")
return
# 弹出文件选择对话框
file_path = filedialog.askopenfilename()
if not file_path:
return
# 这里仅演示文件选择,实际文件传输需要分块读取、加密并发送
self.log(f"[系统] 准备发送文件: {file_path}")
# 可在此处实现真正的文件传输逻辑(略)
def receive_file(self, first_packet):
"""接收文件(加分项框架)"""
# 本实验未完整实现,仅提示
self.log("[系统] 收到文件,请实现完整接收逻辑(可扩展)")
def stop_server(self):
"""停止服务端:关闭socket,重置状态"""
self.running = False
if self.client_conn:
self.client_conn.close()
if self.server_socket:
self.server_socket.close()
self.log("[系统] 服务端已停止")
# 恢复按钮状态
self.start_btn.config(state=tk.NORMAL)
self.stop_btn.config(state=tk.DISABLED)
def run(self):
"""启动GUI主循环"""
self.root.mainloop()
# ---------- 程序入口 ----------
if __name__ == "__main__":
app = ServerGUI()
app.run()
2.2.2客户端代码
import socket
import threading
import tkinter as tk
from tkinter import scrolledtext, filedialog
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
# ---------- 加密配置----------
KEY = b'1234567890abcdef12345678'
NONCE = b'123456789012'
def encrypt(plaintext: str) -> bytes:
return NONCE + AESGCM(KEY).encrypt(NONCE, plaintext.encode('utf-8'), None)
def decrypt(ciphertext: bytes) -> str:
nonce = ciphertext[:12]
data = ciphertext[12:]
return AESGCM(KEY).decrypt(nonce, data, None).decode('utf-8')
# ---------- 客户端图形界面类 ----------
class ClientGUI:
def __init__(self):
self.root = tk.Tk()
self.root.title("加密通信客户端 - 学号:20241207")
self.root.geometry("600x500")
self.client_socket = None # 与服务端通信的socket
self.connected = False # 是否已连接
self._create_widgets()
def _create_widgets(self):
"""构建界面组件"""
# ----- 连接设置区域 -----
frame = tk.LabelFrame(self.root, text="连接设置", padx=5, pady=5)
frame.pack(fill=tk.X, padx=10, pady=5)
tk.Label(frame, text="服务端IP:").grid(row=0, column=0)
self.ip_entry = tk.Entry(frame, width=12)
self.ip_entry.insert(0, "127.0.0.1")
self.ip_entry.grid(row=0, column=1)
tk.Label(frame, text="端口:").grid(row=0, column=2)
self.port_entry = tk.Entry(frame, width=6)
self.port_entry.insert(0, "8080")
self.port_entry.grid(row=0, column=3)
self.connect_btn = tk.Button(frame, text="连接", command=self.connect_server)
self.connect_btn.grid(row=0, column=4, padx=5)
self.disconnect_btn = tk.Button(frame, text="断开", command=self.disconnect, state=tk.DISABLED)
self.disconnect_btn.grid(row=0, column=5)
# ----- 对话日志区域 -----
self.log_area = scrolledtext.ScrolledText(self.root, state=tk.DISABLED, wrap=tk.WORD)
self.log_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# ----- 消息输入区域 -----
msg_frame = tk.Frame(self.root)
msg_frame.pack(fill=tk.X, padx=10, pady=5)
self.msg_entry = tk.Entry(msg_frame)
self.msg_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0,5))
self.msg_entry.bind("<Return>", self.send_message)
self.send_btn = tk.Button(msg_frame, text="发送", command=self.send_message)
self.send_btn.pack(side=tk.RIGHT)
# ----- 文件发送按钮(扩展)-----
self.file_btn = tk.Button(self.root, text="发送文件", command=self.send_file)
self.file_btn.pack(pady=5)
def log(self, msg):
"""在日志区域添加一行"""
self.log_area.config(state=tk.NORMAL)
self.log_area.insert(tk.END, msg + "\n")
self.log_area.see(tk.END)
self.log_area.config(state=tk.DISABLED)
# ---------- 客户端核心网络操作 ----------
def connect_server(self):
"""连接到服务端"""
ip = self.ip_entry.get()
port = int(self.port_entry.get())
try:
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client_socket.connect((ip, port))
self.connected = True
self.log(f"[系统] 已连接服务端 {ip}:{port}")
# 更新按钮状态
self.connect_btn.config(state=tk.DISABLED)
self.disconnect_btn.config(state=tk.NORMAL)
# 启动接收线程,持续监听服务端发来的消息
threading.Thread(target=self._receive_messages, daemon=True).start()
except Exception as e:
self.log(f"[错误] 连接失败: {e}")
def disconnect(self):
"""主动断开连接"""
if self.client_socket:
self.client_socket.close()
self.connected = False
self.log("[系统] 已断开连接")
self.connect_btn.config(state=tk.NORMAL)
self.disconnect_btn.config(state=tk.DISABLED)
def _receive_messages(self):
"""接收服务端消息(运行在独立线程)"""
while self.connected:
try:
enc_data = self.client_socket.recv(4096)
if not enc_data:
break
plain = decrypt(enc_data)
if plain == "exit":
self.log("[系统] 服务端退出")
break
# 显示收到的明文和密文(十六进制)
self.log(f"[服务端] 明文: {plain}")
self.log(f"[服务端] 密文: {enc_data.hex()}")
except Exception as e:
self.log(f"[错误] 接收消息失败: {e}")
break
# 接收循环结束,自动断开连接
self.disconnect()
def send_message(self, event=None):
"""发送消息给服务端"""
if not self.connected:
self.log("[系统] 未连接服务端")
return
msg = self.msg_entry.get().strip()
if not msg:
return
try:
enc_msg = encrypt(msg)
self.client_socket.send(enc_msg)
self.log(f"[我] 明文: {msg}")
self.log(f"[我] 密文: {enc_msg.hex()}")
self.msg_entry.delete(0, tk.END)
if msg == "exit":
self.disconnect()
except Exception as e:
self.log(f"[错误] 发送失败: {e}")
def send_file(self):
"""发送文件(加分项框架)"""
if not self.connected:
self.log("[系统] 未连接服务端")
return
file_path = filedialog.askopenfilename()
if not file_path:
return
self.log(f"[系统] 准备发送文件: {file_path}")
# 实际文件传输需要分块读取、加密并发送,此处仅演示
def run(self):
"""启动GUI主循环"""
self.root.mainloop()
if __name__ == "__main__":
app = ClientGUI()
app.run()
2.2.3运行截图






2.3两代码对比(AI生成代码优点纯享版)
1.交互性强:图形界面程序通过按钮和输入框实现直观操作
2.信息展示更清晰:使用滚动区域分类显示,每条消息都加上了“我”或“服务端/客户端”的明确标签,可读性更强
4.原程序采用单线程模型,收发消息交替进行,一方发送时无法同时接收消息,而图形界面程序引入多线程,可以实现收发并行。
5.实现文件传输的功能扩展。
3. 实验过程中遇到的问题和解决过程
-
问题1:实验中我首先尝试了国密SM4算法,向AI学习了加解密函数使用后发现,ECB/CBC模式要求特定长度的明文输入(明文长度为16字节的倍数),如果明文不符合要求,需要手动实现PKCS7填充字符长度,由于未完全理解逻辑,尝试撰写时反复出错多次。
-
问题1解决方案:经AI建议,改用Python的cryptography库实现AES-256-GCM。该模式为流式加密,无需手动填充,且自带完整性校验,更加适合流式通信场景。
-
问题2:开始时无法和队友的电脑实现网络连通(ping)
-
问题2解决方案:在老师的指导下将子网掩码都改为255.255.255.0
其他(感悟、思考等)
选择国密算法进行编写程序时,对sm4等不同算法的具体实现逻辑有了了解