20241305 2025-2026-2 《Python程序设计》实验三报告
课程:《Python程序设计》
班级: 2413
姓名: 姚航
学号:20241305
实验教师:王志强
实验日期:2026年4月27日
必修/选修: 公选课
1.实验内容
1.1实验内容
创建服务端和客户端,服务端在特定端口监听多个客户请求。客户端和服务端通过Socket套接字(TCP/UDP)进行通信。
1.2实验要求
注意事项:
每人必须做一次客户端和一次服务端,且要和队友(标注学号姓名 本次实验由20241317文彩懿协助完成)互相通信。
要求1:
(1)创建服务端和客户端,选择一个通信端口,用Python语言编程实现通信演示程序;
(2)要求发送方输入内容,加密后并传输;接收方收到密文并解密和显示。要求:发方和收方同时输出明文和明文。
(3)程序代码托管到码云。
(4)添加文件操作,有加分。(可选项)
要求2:使用LLM生成一个带图形界面的程序
(1)分析关键代码的功能和使用方法
(2)分析生成程序的优点
(3)给出运行过程和结果截图
(4)程序代码托管到码云。
注:在华为ECS服务器(OpenOuler系统)和物理机(Windows/Linux系统)上使用VIM、PDB、IDLE、Pycharm等工具编程实现。
2. 实验过程及结果
2.1创建服务端和客户端,选择一个通信端口,用Python语言编程实现通信演示程序
通信端口选择 6666
创建服务端和客户端代码如下:
服务端
这里将服务端设为 0.0.0.0 ,作为“万能监听地址”,能够实现接受来自同一局域网内任意设备(客户端)的连接请求。
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = '0.0.0.0'
PORT = 6666
server.bind((HOST, PORT))
server.listen(1)
print("服务端已经启动,等待客户端连接......")
conn, addr = server.accept()
print(f"已连接客户端:{addr}")
客户端
对应队友的ip配置(学号17收尾),两台电脑全程共同连一个热点,热点ip为10.125.90.xx(根据学号作修改)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = '10.125.90.17'
PORT = 6666
client.connect((HOST, PORT))
print("已连接服务端!输入exit退出聊天!")
2.2要求发送方输入内容,加密后并传输;接收方收到密文并解密和显示。要求:发方和收方同时输出明文和明文
先附上全部代码,本次采用的加密方式为字符转ASCll数字再移3位
服务端全部代码:
import socketoffset = 3
#加密解密算法采用:字符转ASCll数字再移位
def encrypt(msg):res = ""for c in msg:num = ord(c)new_num = num + offsetnew_ch = chr(new_num)res = res + new_chreturn resdef decrypt(msg):res = ""for c in msg:num = ord(c)new_num = num - offsetnew_ch = chr(new_num)res = res + new_chreturn resserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = '0.0.0.0'
PORT = 6666
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("聊天结束")breakprint("客户端密文:",data)text = decrypt(data)print("客户端明文:",text)send_msg = input("我:")secret = encrypt(send_msg)conn.send(secret.encode("utf-8"))print("我方密文:",secret)if send_msg == "exit":breakconn.close()
server.close()
客户端全部代码:
import socketoffset = 3
#加密解密算法采用:字符转ASCll数字再移位
def encrypt(msg):res = ""for c in msg:num = ord(c)new_num = num + offsetnew_ch = chr(new_num)res = res + new_chreturn resdef decrypt(msg):res = ""for c in msg:num = ord(c)new_num = num - offsetnew_ch = chr(new_num)res = res + new_chreturn resclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
HOST = '10.125.90.17'
PORT = 6666
client.connect((HOST, PORT))
print("已连接服务端!输入exit退出聊天!")while True:send_msg = input("我:")secret = encrypt(send_msg)client.send(secret.encode("utf-8"))print("我方密文:",secret)if send_msg == "exit":breakdata = client.recv(1024).decode("utf-8")if not data or data == "exit":print("聊天结束")breakprint("服务端密文:",data)text = decrypt(data)print("服务端明文:",text)client.close()
进行通信并截图:
我作服务端运行

我作客户端运行

这里同时附上队友作服务端和客户端的运行结果截图


2.3程序代码托管到码云
仓库地址
2.4使用LLM生成一个带图形界面的程序
代码附上(选择的加密方式为AES加密,端口选择12345(避免出错),ip为对应学号收尾)
服务端代码
import socket
import threading
import tkinter as tk
from tkinter import scrolledtext, filedialog, messagebox
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import osHOST = '0.0.0.0'
PORT = 12345
KEY = b'1234567890abcdef'class ServerGUI:def __init__(self, root):self.root = rootself.root.title("服务端 - 加密通信+文件传输")self.root.geometry("700x600")self.conn = Noneself.client_addr = Noneself.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("错误", "客户端未连接")returntext = self.msg_entry.get().strip()if not text: returntry: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: returnpath = self.file_path.get()if not os.path.exists(path): returntry: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 = Nonebreakdef 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 socket
import threading
import tkinter as tk
from tkinter import scrolledtext, filedialog, messagebox
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import osHOST = '10.125.90.17' # 服务端电脑的IP
PORT = 12345
KEY = b'1234567890abcdef'
# ======================================================class ClientGUI:def __init__(self, root):self.root = rootself.root.title("客户端 - 加密通信+文件传输")self.root.geometry("700x600")self.sock = Noneself.connected = Falseself.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("错误","未连接服务端")returntext = self.msg_entry.get().strip()if not text: returntry: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: returnpath = self.file_path.get()if not os.path.exists(path): returntry: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 = Falsebreakdef connect_server(self):try:self.sock = socket.socket()self.sock.connect((HOST, PORT))self.connected = Trueself.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()
2.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("错误", "客户端未连接")returntext = self.msg_entry.get().strip()if not text: returntry: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: returnpath = self.file_path.get()if not os.path.exists(path): returntry: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 = Nonebreak
功能
循环监听客户端数据:
类型 0:接收加密文本 → 解密 → 显示明文、密文;
类型 1:接收文件名、文件大小、加密文件数据 → 解密 → 自动保存为 recv_文件名。
使用方法
客户端连接后自动后台运行,无需手动操作,自动接收消息和文件并保存。
2.6分析生成程序的优点
LLM生成代码与我的代码对比:
(1)我的代码
ASCII 码整体偏移 3 位加密,原理简单,行数少,适合新手理解。但保密性差,且只能对普通文本加密,不能处理文件。
没有图形界面,只能在命令行输入消息、查看密文和明文,只能完成基础文字聊天。
(2)LLM生成代码
采用AES-CBC 专业加密,需要固定密钥,加密标准正规,安全程度高。
带有图形可视化界面,配有消息输入框、日志显示区域、文件选择按钮。
文字通信和文件双向传输,能自动识别文本消息和文件消息,接收的文件可自动保存。
总结:AI功能还是过于强大,专业加密方式和图形化界面也让我感到震撼,优点很多,这里就列几条主要的。
2.7运行过程和结果截图
我作服务端

我作客户端

收到的文本文件保存到本地

同时附上队友截图



程序代码托管到码云
仓库地址
3. 实验过程中遇到的问题和解决过程
- 问题1:两台电脑连同一个手机热点,本机自己运行服务端和客户端可以正常通信;换成两台同学电脑互相连接,一直连接失败,提示 10061 目标计算机积极拒绝,连不上服务端。
- 问题1解决方案:其中一台电脑,专用网络和公用网络防火墙忘记关闭;关闭后重新运行服务端和客户端,就能正常连接。
- 问题2:复制代码到 PyCharm 运行,直接报错,提示tk(图形页面)、pycryptodome(AES加密) 模块不存在,程序打不开、运行失败。
- 问题2解决方案:在 PyCharm 终端执行安装命令:pip install pycryptodome 。 tk本来下载安装环境的时候应该自带(但当时下载的是无线版,没有带着tk这个插件),重新下载在线版,重新给pycharm配置了新的解释器;安装完成后重启项目,代码即可正常运行。
- 问题3:着急先点开客户端,再开服务端,一直连不上。
- 问题3解决方案:先启动服务端,等待服务端显示启动成功后,再运行客户端。
其他(感悟、思考等)
通过这次双人网络加密通信实验,收获挺大的,也发现了自己不少问题。
以前上课只听理论,感觉网络通信挺简单,真正自己和队友联机调试,才发现实际操作难点特别多。
环境配置这块,没安装tk模块,直接导致图形界面跑不起来,耽误了不少时间。
然后就是联机通信这块,一开始两台电脑怎么都连不上,后来才知道是电脑防火墙拦着连接。还有一开始对IP地址,还把自己和队友的IP填反了,肯定连不上。
还有一些小问题,都是实际操作中才遇到的。同时我也对比了自己写的代码和LLM生成代码,能明显感觉到AI强的不是一点半点。
这次实验让我明白,python要多动手实操。遇到报错不能瞎改代码,要静下心排查原因,慢慢积累经验,才能成功。
参考资料
-
《Java程序设计与数据结构教程(第二版)》
-
《Java程序设计与数据结构教程(第二版)》学习指导
-
...
