20243408 2025-2026-2 《Python程序设计》实验3报告
课程 :《Python程序设计》
班级 : 2434
姓名 : 梁灏
学号 :20243408
实验教师 :王志强
实验日期 :2026年4月28日
必修/选修: 公选课
1. 实验内容
本次实验围绕Socket网络编程技术展开,要求如下:
- 创建服务端和客户端: 服务端在特定端口监听多个客户请求。客户端和服务端通过Socket套接字(TCP/UDP)进行通信。
- 实现加密传输以及优化: 发送方输入内容,加密后并传输;接收方收到密文并解密和显示。同时实现代码优化。
此外,实验还涉及代码版本管理(托管至码云)以及在华为OpenEuler系统上的部署验证。
2. 实验过程及结果
2.1 创建服务端和用户端
2.1.1 代码验证
基于老师提供的SocketServer.py和SocketClient.py基础代码,进行了如下操作:
- 通信基础: 沿用文档中的TCP Socket通信模型,服务端绑定IP与端口并监听,客户端发起连接。
- 更改IP地址: 将两台计算机连接至同一网络下,分别修改IP地址为:
192.168.43.8和192.168.43.29。如图2-1、图2-2所示。
图2-1 计算机1(192.168.43.8)
图2-2 计算机2(192.168.43.29)
- 队友互测: 我与队友
20243429付家祺分别运行服务端和客户端程序,成功进行了加密通信测试。
如图2-3所示。
图2-3 两台计算机互相传送信息
2.1.2 关键代码与功能分析
这SocketClient.py和SocketServer.py分别实现了服务端和客户端的功能。
-
启动与连接
- 服务端 先行启动,在
localhost(本机)的4444端口上"守候"(listen)。 - 客户端 随后启动,主动向服务端的地址和端口发起连接(
connect)。 - 连接建立后,双方进入对话循环。
- 服务端 先行启动,在
-
对话循环
这是代码最核心的特点,双方收发消息的顺序是固定且交替的:
- 服务端循环 :
接收 -> (处理/显示) -> 等待用户输入 -> 发送 - 客户端循环 :
等待用户输入 -> 发送 -> 接收 -> (处理/显示)
具体到一次完整的消息往返:
- 客户端 必须首先在控制台输入一句话,然后发送。
- 服务端 接收到这句话并显示,然后才能在控制台输入回复,发送回去。
- 客户端 收到回复并显示,之后才能输入下一条消息。
如此循环往复。任何一方试图"抢话"(在不该自己发言的轮次输入)都会导致程序在recv()或input()处阻塞等待。
- 服务端循环 :
-
退出机制
- 任何一方在输入时键入
"exit"并发送,都会在发送完该消息后终止自己的循环。 - 接收方收到
"exit"消息或空数据时,也会终止循环。 - 双方关闭连接(
close())。
- 任何一方在输入时键入
-
网络连接建立
-
服务端 :
pythonserver.bind((HOST, PORT)) # 绑定地址端口,如同设定电话号码 server.listen(1) # 开始监听,允许1个连接排队 conn, addr = server.accept() # 等待并接受连接,conn是专属通话管道 -
客户端 :
pythonclient.connect((HOST, PORT)) # 主动拨打服务端的"电话号码"
-
-
数据的收发与编解码
- 发送 :
conn.send(send_msg.encode("utf-8"))
将字符串(str)使用UTF-8编码转换为字节序列(bytes)后通过网络发送。 - 接收 :
data = conn.recv(1024).decode("utf-8")
从网络接收字节数据(最多1024字节),然后使用UTF-8解码为字符串。
- 发送 :
-
同步控制
- 控制逻辑由双方代码中
send和recv的固定顺序 天然实现,形成了上述的"锁步"效果。input()和recv()都是阻塞调用,强制了这种顺序。
- 控制逻辑由双方代码中
-
程序特点与局限性
-
优点:
- 结构清晰:非常直观地展示了TCP Socket编程的基本步骤(创建、绑定/连接、收发、关闭)。
- 逻辑简单:适合初学者理解网络通信的基础流程。
-
局限性:
- 严格同步:聊天体验不自然,双方不能自由地随时发送消息。
- 单线程阻塞:在等待输入或接收消息时,程序无法做任何其他事情。
- 仅支持单客户端 :服务端的
accept()只调用一次,处理完一个客户端的整个会话后才会结束,期间无法服务其他客户端。 - 脆弱的数据边界 :使用
recv(1024),如果单条消息长度超过1024字节会被截断,如果小于1024字节则可能一次收到多条消息的片段,导致解码或逻辑混乱。这在当前简单字符串聊天中可能不显现,但是一个潜在问题。 - 数据明文传输:未采用任何形式加密,安全性存疑。
2.1.3 程序运行与结果分析
-
运行过程 :
- 首先在主机A运行
SocketServer.py启动服务端。 - 随后在主机B运行
SocketClient.py,连接至主机A的IP和端口4444。 - 双方交替输入文字,程序自动完成发送、接收的过程。
- 首先在主机A运行
-
运行结果 :
(以localhost形式测试)-
服务端控制台输出示例 :
D:\Python_Class\Python_Class\.venv\Scripts\python.exe D:\Python_Class\socket\SocketServer.py 服务端已经启动,等待客户端连接...... 已连接客户端:('127.0.0.1', 10683) 客户端:From Client 我:From Server ...... -
客户端控制台输出示例 :
D:\Python_Class\Python_Class\.venv\Scripts\python.exe D:\Python_Class\socket\SocketClient.py 已连接服务端!输入exit退出聊天! 我:From CLient 服务端:From Server 我: ......
-
-
结果分析 :
- 成功实现了通信基本功能,且经实操验证。(见
2.1.1)
- 成功实现了通信基本功能,且经实操验证。(见
2.2 实现加密传输以及优化
2.2.1 基于LLM生成带有加密传输功能的代码
-
生成优化后代码
针对
2.1.2提及的局限性,向Deepseek输入提示词:目前存在以下问题,请解决: 1、严格同步:聊天体验不自然,双方不能自由地随时发送消息。 2、单线程阻塞:在等待输入或接收消息时,程序无法做任何其他事情。 3、仅支持单客户端:服务端的 accept()只调用一次,处理完一个客户端的整个会话后才会结束,期间无法服务其他客户端。 4、脆弱的数据边界:使用recv(1024),如果单条消息长度超过1024字节会被截断,如果小于1024字节则可能一次收到多条消息的片段,导致解码或逻辑混乱。这在当前简单字符串聊天中可能不显现,但是一个潜在问题。 5、数据明文传输:未采用任何形式加密。 帮我完成问题解决后,提供给我SocketClient_enhanced.py、SocketServer_enhanced.py。Deepseek生成代码如下:
SocketServer_enhanced.pypython# -*- coding: utf-8 -*- import socket import struct import threading from queue import Queue import hashlib import os from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Random import get_random_bytes import base64 class AESCipher: """AES加密解密类""" def __init__(self, key=None): if key is None: key = get_random_bytes(32) # 256位密钥 self.key = hashlib.sha256(key).digest()[:32] # 确保32字节 def encrypt(self, plaintext): """加密明文""" iv = get_random_bytes(16) # 随机生成IV cipher = AES.new(self.key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(pad(plaintext.encode('utf-8'), AES.block_size)) return iv + ciphertext def decrypt(self, ciphertext_with_iv): """解密密文""" iv = ciphertext_with_iv[:16] ciphertext = ciphertext_with_iv[16:] cipher = AES.new(self.key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) return plaintext.decode('utf-8') def send_encrypted_message(sock, aes_cipher, message): """发送加密消息""" try: # 加密消息 encrypted_data = aes_cipher.encrypt(message) # 发送加密后的数据长度和内容 header = struct.pack('!I', len(encrypted_data)) sock.sendall(header + encrypted_data) return True except (BrokenPipeError, ConnectionResetError, OSError): return False def recv_encrypted_message(sock, aes_cipher): """接收并解密消息""" try: # 读取消息长度 header = sock.recv(4) if not header: return None, None msg_len = struct.unpack('!I', header)[0] # 读取加密消息 data = b'' while len(data) < msg_len: packet = sock.recv(min(msg_len - len(data), 4096)) if not packet: return None, None data += packet # 解密消息 plaintext = aes_cipher.decrypt(data) return plaintext, data except (ConnectionResetError, struct.error, OSError, ValueError): return None, None def handle_client(conn, addr, client_id, shared_key): """处理单个客户端连接""" print(f"[服务器] 客户端 #{client_id} {addr} 已连接") print(f"[服务器] 与客户端 #{client_id} 的共享密钥: {shared_key.hex()[:16]}...") # 创建AES加密器 aes_cipher = AESCipher(shared_key) # 创建接收消息的线程 recv_thread = threading.Thread(target=receive_from_client, args=(conn, addr, client_id, aes_cipher)) recv_thread.daemon = True recv_thread.start() # 主线程用于发送消息 try: while True: # 获取用户输入 plaintext = input(f"[向客户端#{client_id}发送] ") if plaintext.lower() == 'exit': print(f"[服务器] 终止与客户端 #{client_id} 的会话") break # 加密并发送消息 if send_encrypted_message(conn, aes_cipher, plaintext): # 显示明文和密文 encrypted_data = aes_cipher.encrypt(plaintext) ciphertext_hex = base64.b64encode(encrypted_data[16:]).decode() # 不包含IV print(f" [明文] {plaintext}") print(f" [密文] {ciphertext_hex[:64]}...") else: print(f"[服务器] 客户端 #{client_id} 连接已断开") break except (EOFError, KeyboardInterrupt): pass finally: conn.close() print(f"[服务器] 与客户端 #{client_id} 的连接已关闭") def receive_from_client(conn, addr, client_id, aes_cipher): """接收来自客户端的消息""" while True: plaintext, ciphertext_with_iv = recv_encrypted_message(conn, aes_cipher) if plaintext is None: print(f"[服务器] 客户端 #{client_id} 断开连接") break # 显示接收到的密文和明文 ciphertext_hex = base64.b64encode(ciphertext_with_iv[16:]).decode() # 不包含IV print(f"\n[客户端#{client_id} 说]") print(f" [密文] {ciphertext_hex[:64]}...") print(f" [明文] {plaintext}") def main(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) HOST = "localhost" PORT = 4444 server.bind((HOST, PORT)) server.listen(5) print(f"[服务器] 启动在 {HOST}:{PORT},等待客户端连接...") print("[服务器] 生成共享密钥中...") client_counter = 0 try: while True: conn, addr = server.accept() client_counter += 1 # 生成并发送共享密钥 shared_key = get_random_bytes(32) conn.sendall(shared_key) # 为每个客户端创建处理线程 client_thread = threading.Thread(target=handle_client, args=(conn, addr, client_counter, shared_key)) client_thread.daemon = True client_thread.start() except KeyboardInterrupt: print("\n[服务器] 正在关闭...") finally: server.close() if __name__ == "__main__": main()SocketClient_enhanced.py
python# -*- coding: utf-8 -*- import socket import struct import threading import sys import hashlib from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Random import get_random_bytes import base64 class AESCipher: """AES加密解密类""" def __init__(self, key=None): if key is None: key = get_random_bytes(32) # 256位密钥 self.key = hashlib.sha256(key).digest()[:32] # 确保32字节 def encrypt(self, plaintext): """加密明文""" iv = get_random_bytes(16) # 随机生成IV cipher = AES.new(self.key, AES.MODE_CBC, iv) ciphertext = cipher.encrypt(pad(plaintext.encode('utf-8'), AES.block_size)) return iv + ciphertext def decrypt(self, ciphertext_with_iv): """解密密文""" iv = ciphertext_with_iv[:16] ciphertext = ciphertext_with_iv[16:] cipher = AES.new(self.key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) return plaintext.decode('utf-8') def send_encrypted_message(sock, aes_cipher, message): """发送加密消息""" try: # 加密消息 encrypted_data = aes_cipher.encrypt(message) # 发送加密后的数据长度和内容 header = struct.pack('!I', len(encrypted_data)) sock.sendall(header + encrypted_data) return True except (BrokenPipeError, ConnectionResetError, OSError): return False def recv_encrypted_message(sock, aes_cipher): """接收并解密消息""" try: # 读取消息长度 header = sock.recv(4) if not header: return None, None msg_len = struct.unpack('!I', header)[0] # 读取加密消息 data = b'' while len(data) < msg_len: packet = sock.recv(min(msg_len - len(data), 4096)) if not packet: return None, None data += packet # 解密消息 plaintext = aes_cipher.decrypt(data) return plaintext, data except (ConnectionResetError, struct.error, OSError, ValueError): return None, None def receive_thread_func(sock, aes_cipher): """接收服务器消息的线程""" while True: plaintext, ciphertext_with_iv = recv_encrypted_message(sock, aes_cipher) if plaintext is None: print("\n[连接] 与服务器的连接已断开") sys.exit(0) # 显示接收到的密文和明文 ciphertext_hex = base64.b64encode(ciphertext_with_iv[16:]).decode() # 不包含IV print(f"\n[服务器]") print(f" [密文] {ciphertext_hex[:64]}...") print(f" [明文] {plaintext}") sys.stdout.write("我: ") sys.stdout.flush() def main(): client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) HOST = "localhost" PORT = 4444 try: client.connect((HOST, PORT)) except ConnectionRefusedError: print("无法连接到服务器。请确保服务器已启动。") return print("已连接至服务器。") # 接收共享密钥 shared_key = client.recv(32) print(f"从服务器获取共享密钥: {shared_key.hex()[:16]}...") # 创建AES加密器 aes_cipher = AESCipher(shared_key) print("加密通道已建立。您可以随时输入消息,输入 'exit' 退出。\n") # 启动接收消息的线程 recv_thread = threading.Thread(target=receive_thread_func, args=(client, aes_cipher)) recv_thread.daemon = True recv_thread.start() # 主线程用于发送消息 try: while True: # 获取用户输入 plaintext = input("我: ") if plaintext.lower() == 'exit': print("正在退出...") break # 加密并发送消息 if send_encrypted_message(client, aes_cipher, plaintext): # 显示明文和密文 encrypted_data = aes_cipher.encrypt(plaintext) ciphertext_hex = base64.b64encode(encrypted_data[16:]).decode() # 不包含IV print(f" [明文] {plaintext}") print(f" [密文] {ciphertext_hex[:64]}...") else: print("发送失败,连接可能已断开") break except (EOFError, KeyboardInterrupt): print("\n正在退出...") finally: client.close() if __name__ == "__main__": # 检查依赖库 try: from Crypto.Cipher import AES except ImportError: print("需要安装pycryptodome库,请运行: pip install pycryptodome") sys.exit(1) main() -
运行测试:
- 首先在主机A运行
SocketServer_enhanced.py启动服务端。 - 随后在主机B运行
SocketClient_enhanced.py,连接至主机A的IP和端口4444。 - 双方交替输入文字,程序自动完成发送、接收的过程。
- 首先在主机A运行
-
运行结果 :
(以localhost形式测试)-
服务端控制台输出示例 :
D:\Python_Class\Python_Class\.venv\Scripts\python.exe D:\Python_Class\socket\SocketServer_enhanced.py [服务器] 启动在 localhost:4444,等待客户端连接... [服务器] 生成共享密钥中... [服务器] 客户端 #1 ('127.0.0.1', 4354) 已连接 [服务器] 与客户端 #1 的共享密钥: 4003e30900aae59c... [向客户端#1发送] [客户端#1 说] [密文] HTOnuncWcPvvIGyiyKdGMQ==... [明文] From Client [明文] [密文] m4OxyLG8My5YH1hVVPzd7w==... [向客户端#1发送] From Server [明文] From Server [密文] hjcLJucmzxTT621rrycfrA==... [向客户端#1发送] ...... -
客户端控制台输出示例 :
D:\Python_Class\Python_Class\.venv\Scripts\python.exe D:\Python_Class\socket\SocketClient_enhanced.py 已连接至服务器。 从服务器获取共享密钥: 4003e30900aae59c... 加密通道已建立。您可以随时输入消息,输入 'exit' 退出。 我: From Client [明文] From Client [密文] qcIt0K/CLeSOVB7WFPoyTA==... 我: [服务器] [密文] +mpGQE84OzoQKBelQIuAQA==... [明文] 我: [服务器] [密文] +pYq0gnwx8a/0NW06SkQyQ==... [明文] From Server 我: ......
-
-
结果分析:
- 成功实现了通信基本功能,且经实操验证。(如图2-4、图2-5所示)
图2-4 Server
图2-5 Client
2.2.2 基于LLM生成带有GUI的代码
- 运行结果:如图2-6所示。
图2-5 带有图形界面的版本
3. 实验过程中遇到的问题和解决过程
- 问题1 :两台计算机无法链接
- 解决方案:链接至手机热点内,有可能是因为教室有人学号相同,使用了同一个IP地址。
- 问题2 :Pycharm无法推送Git
- 解决方案:重新安装。
其他(感悟、思考等)
- 理论与实践结合:本次实验让我对TCP Socket编程的"三次握手"、"流式传输"等概念有了更直观的认识。特别是处理消息边界问题,是理论学习中容易忽略的实践关键点。
- 安全无小事:实现加密功能让我体会到,即使是一个简单的聊天程序,要实现基本的数据保密性也需要仔细选择算法、处理密钥。
- AI辅助开发的利与弊 :LLM能极大提升原型开发速度,尤其是在生成GUI布局、模板代码方面。但其生成的代码可能不够健壮(如未处理多线程、异常),或不符合特定需求。因此,理解其生成的代码并具备调试、优化和整合的能力,比单纯会"提问"更为重要。
- 工具链的重要性:本次实验综合运用了Git(码云)、PyCharm等多种工具,让我感受到熟练掌握开发、调试、部署、版本管理的全流程工具,对高效完成项目至关重要。
参考资料
- Python官方文档 -
socket模块 - LLM (ChatGPT/DeepSeek) 在GUI代码生成和问题调试过程中的对话记录。
- 代码仓库 :本实验所有代码(基础版、增强版、GUI版)均已托管至码云(Gitee),仓库地址:
https://gitee.com/lucaslianghao/PythonClass