20242218 2025-2026-2 《Python程序设计》实验三报告
20242218 2025-2026-2 《Python程序设计》实验三报告
课程:《Python程序设计》
班级: 2422
姓名: 段锦坤
学号:20242218
实验教师:王志强
实验日期:2026年4月27日
必修/选修: 公选课
1.实验内容
创建服务端和客户端,服务端在特定端口监听多个客户请求。客户端和服务端通过Socket套接字(TCP/UDP)进行通信。
2. 实验过程及结果
本次通信实验双方配置信息:
| 学号 | 姓名 | ip地址 | 主通信通道端口 | 服务器端文件端口 | 客户端文件端口 |
|---|---|---|---|---|---|
| 20242218 | 段锦坤 | 192.168.43.18 | 8888 | 8889 | 8899 |
| 20242219 | 陈儒俊 | 192.168.43.19 | 8888 | 8889 | 8899 |
2.1 不借助AI实现socket通信
首先,通过课堂所学的socket技术,进行编码,分别搭建客户端与服务端的程序。
接下来,进行加密层的设计。我设计的加密层是基于预共享口令派生共享对称密钥的AES-ECB加密,加密后将密文进行Base64编码。程序流程为:
- 用户输入预共享口令。
- 客户端和服务端根据预共享口令派生共享对称密钥。
- 发信方将明文进行AES-ECB加密,加密后将密文进行Base64编码。
- 接收方收到密文后,使用共享对称密钥进行解密,将解密后的明文进行Base64解码。
在编写完加密对话程序后,我设计了一套基于原加密通信管道的文件传输协议。考虑了文件传输的发起、接受、拒绝、暂停与续传功能。
将文件传输协议与通信过程、加密认证过程综合起来,形成了加密聊天室的通信协议内容如下:
| 编号 | 控制码 | 方向 | 说明 |
|---|---|---|---|
| 1 | SYS | 双向 | 系统消息 |
| 2 | AUTH_CHALLENGE | 服务器→客户端 | 认证挑战 |
| 3 | AUTH_RESPONSE | 客户端→服务器 | 认证响应 |
| 4 | AUTH_SUCCESS | 服务器→客户端 | 认证成功 |
| 5 | AUTH_FAILED | 服务器→客户端 | 认证失败 |
| 6 | NICKNAME | 双向 | 昵称交换 |
| 7 | MSG | 双向 | 聊天消息 |
| 8 | FILE_REQUEST | 发送方→接收方 | 文件传输请求 |
| 9 | FILE_ACCEPT | 接收方→发送方 | 接受文件 |
| 10 | FILE_REJECT | 接收方→发送方 | 拒绝文件 |
| 11 | FILE_PAUSE | 发送方→接收方 | 暂停传输 |
| 12 | FILE_PAUSE_ACK | 接收方→发送方 | 暂停确认 |
| 13 | FILE_RESUME | 发送方→接收方 | 恢复传输 |
| 14 | FILE_RESUME_ACK | 接收方→发送方 | 恢复确认 |
| 15 | FILE_CANCEL | 发送方→接收方 | 取消传输 |
| 16 | FILE_CANCEL_ACK | 接收方→发送方 | 取消确认 |
| 17 | FILE_PROGRESS | 发送方→接收方 | 文件传输进度 |
| 18 | FILE_DONE | 发送方→接收方 | 文件传输完成 |
| 19 | HB | 双向 | 心跳,每10秒发送1次,当30秒未收到对方心跳,则断开与对方通信 |
以下是实验结果:
-
作为服务器与同伴进行通信
![屏幕截图 2026-05-07 182914]()
-
作为客户端连接到服务器进行通信
![屏幕截图 2026-05-07 183506]()
程序源码如下:
#服务器端代码
import socket
import threading
import os
import time
import sys
import base64
import hashlib
# pip install pycryptodome
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
def derive_key(password):
key = hashlib.sha256(password.encode()).digest()
return key
def encrypt_message(message, password):
key = derive_key(password)
cipher = AES.new(key, AES.MODE_CBC)
ct_bytes = cipher.encrypt(pad(message.encode('utf-8'), AES.block_size))
iv = base64.b64encode(cipher.iv).decode('utf-8')
ct = base64.b64encode(ct_bytes).decode('utf-8')
return iv + ":" + ct
def decrypt_message(encrypted_data, password):
try:
iv, ct = encrypted_data.split(":")
key = derive_key(password)
iv_bytes = base64.b64decode(iv)
ct_bytes = base64.b64decode(ct)
cipher = AES.new(key, AES.MODE_CBC, iv_bytes)
pt = unpad(cipher.decrypt(ct_bytes), AES.block_size)
return pt.decode('utf-8')
except:
return None
class ChatServer:
def __init__(self):
self.server = socket.socket()
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server.bind(("0.0.0.0", 8888))
self.server.listen(1)
self.conn = None
self.addr = None
self.running = True
self.password = input("请设置加密口令: ")
self.messages = []
print("服务器启动成功,等待客户端连接...")
self.server_thread = threading.Thread(target=self.accept_connection)
self.server_thread.daemon = True
self.server_thread.start()
self.receive_thread = threading.Thread(target=self.receive_messages)
self.receive_thread.daemon = True
self.receive_thread.start()
self.command_thread = threading.Thread(target=self.command_loop)
self.command_thread.daemon = True
self.command_thread.start()
while self.running:
time.sleep(0.1)
def accept_connection(self):
try:
self.conn, self.addr = self.server.accept()
self.conn.setblocking(False)
print(f"[服务器] 客户端连接成功: {self.addr}")
except Exception as e:
if not self.running:
return
print(f"[服务器] 错误: {str(e)}")
def receive_messages(self):
while self.running:
if not self.conn:
time.sleep(0.5)
continue
try:
data = self.conn.recv(1024)
if not data:
print(f"[服务器] 客户端已断开连接")
self.conn = None
continue
encrypted_message = data.decode()
self.messages.append(f"[客户端] 收到加密消息: {encrypted_message}")
decrypted = decrypt_message(encrypted_message, self.password)
if decrypted:
self.messages.append(f"[客户端] 解密消息: {decrypted}")
else:
self.messages.append(f"[客户端] 解密失败")
self.refresh_screen()
except socket.error as e:
if e.errno != 10035: # 非阻塞模式的正常错误
print(f"[服务器] 接收错误: {str(e)}")
except Exception as e:
print(f"[服务器] 接收错误: {str(e)}")
time.sleep(0.1)
def send_message(self, message=None):
if not self.conn:
self.messages.append("[服务器] 没有客户端连接")
self.refresh_screen()
return
if message is None:
message = input("")
if message:
encrypted = encrypt_message(message, self.password)
self.messages.append(f"[服务器] 原文: {message}")
self.messages.append(f"[服务器] 加密消息: {encrypted}")
try:
self.conn.send(encrypted.encode())
except Exception as e:
self.messages.append(f"[服务器] 发送失败: {str(e)}")
self.refresh_screen()
def refresh_screen(self):
os.system('cls' if os.name == 'nt' else 'clear')
print("加密聊天室 - 服务器端")
print("=" * 60)
# 显示最近的20条消息
for msg in self.messages[-20:]:
print(msg)
print("-" * 60)
print("输入消息,输入exit退出聊天室")
sys.stdout.flush()
def command_loop(self):
while self.running:
self.refresh_screen()
message = input()
if message == "exit":
self.exit_chat()
continue
self.send_message(message)
def exit_chat(self):
confirm = input("确定要退出聊天室吗?(y/n): ")
if confirm.lower() == 'y':
self.send_message("我已退出聊天室,再见!")
print("[服务器] 正在关闭服务器...")
self.running = False
if self.conn:
try:
self.conn.close()
except:
pass
try:
self.server.close()
except:
pass
print("[服务器] 服务器已关闭")
exit()
if __name__ == "__main__":
ChatServer()
#客户端代码
import socket
import threading
import time
import base64
import hashlib
import os
import sys
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
def derive_key(password):
key = hashlib.sha256(password.encode()).digest()
return key
def encrypt_message(message, password):
key = derive_key(password)
cipher = AES.new(key, AES.MODE_CBC)
ct_bytes = cipher.encrypt(pad(message.encode('utf-8'), AES.block_size))
iv = base64.b64encode(cipher.iv).decode('utf-8')
ct = base64.b64encode(ct_bytes).decode('utf-8')
return iv + ":" + ct
def decrypt_message(encrypted_data, password):
try:
iv, ct = encrypted_data.split(":")
key = derive_key(password)
iv_bytes = base64.b64decode(iv)
ct_bytes = base64.b64decode(ct)
cipher = AES.new(key, AES.MODE_CBC, iv_bytes)
pt = unpad(cipher.decrypt(ct_bytes), AES.block_size)
return pt.decode('utf-8')
except:
return None
class ChatClient:
def __init__(self):
self.client = socket.socket()
self.running = True
self.password = input("请输入加密口令: ")
self.messages = []
self.connect_to_server()
self.receive_thread = threading.Thread(target=self.receive_messages)
self.receive_thread.daemon = True
self.receive_thread.start()
self.command_loop()
def connect_to_server(self):
try:
self.client.connect(("192.168.43.19", 8888))
self.client.setblocking(False)
self.messages.append("连接服务器成功!")
self.refresh_screen()
except Exception as e:
print(f"连接服务器失败: {str(e)}")
self.running = False
exit()
def receive_messages(self):
while self.running:
try:
data = self.client.recv(1024)
if not data:
self.messages.append("服务器已断开连接")
self.running = False
self.refresh_screen()
break
encrypted_message = data.decode()
self.messages.append(f"[服务器] 收到加密消息: {encrypted_message}")
decrypted = decrypt_message(encrypted_message, self.password)
if decrypted:
self.messages.append(f"[服务器] 解密消息: {decrypted}")
else:
self.messages.append(f"[服务器] 解密失败")
self.refresh_screen()
except socket.error as e:
if e.errno != 10035: # 非阻塞模式的正常错误
self.messages.append(f"接收错误: {str(e)}")
self.refresh_screen()
except Exception as e:
self.messages.append(f"接收错误: {str(e)}")
self.refresh_screen()
time.sleep(0.1)
def send_message(self, message=None):
if message is None:
message = input("")
if message:
encrypted = encrypt_message(message, self.password)
self.messages.append(f"[客户端] 原文: {message}")
self.messages.append(f"[客户端] 加密消息: {encrypted}")
try:
self.client.send(encrypted.encode())
except Exception as e:
self.messages.append(f"发送消息失败: {str(e)}")
self.refresh_screen()
def refresh_screen(self):
os.system('cls' if os.name == 'nt' else 'clear')
print("加密聊天室 - 客户端")
print("=" * 60)
# 显示最近的20条消息
for msg in self.messages[-20:]:
print(msg)
print("-" * 60)
print("输入消息,输入exit退出聊天室")
sys.stdout.flush()
def command_loop(self):
while self.running:
self.refresh_screen()
message = input()
if message == "exit":
self.exit_chat()
continue
self.send_message(message)
def exit_chat(self):
confirm = input("确定要退出聊天室吗?(y/n): ")
if confirm.lower() == 'y':
self.send_message("我已退出聊天室,再见!")
print("正在退出聊天室...")
self.running = False
try:
self.client.close()
except:
pass
print("已退出聊天室")
exit()
if __name__ == "__main__":
ChatClient()
2.2 借助LLM编写图形化程序,并进行协议的完整实现
在手写基本的加密通信程序后,我借助LLM进行了图形化程序的开发。
首先是将上面设计的通信协议进行完整实现。通过与AI的多轮交流、调试程序,最终确定了一套可用的实现路径。我意识到基本的单线程通信难以支持:(1)双方的实时交流,(2)多个文件的并行发送,(3)文件发送过程中控制码的发送。因此,在LLM的帮助下,迭代了原本的程序,应用多线程技术来处理比较复杂的程序逻辑。
通过多次调试,最终构建了一个具备图形界面的加密聊天室程序,并与同伴进行了通信实验。
实验结果如下:
- 作为客户端,与对方连接,进行文本与文件收发。

- 作为客户端,发送文件,测试对方暂停续发与拒绝接收,程序均运行正常。

-
为保持界面整洁,主页只展示明文,双击消息可查看该消息的加密密文。
![屏幕截图 2026-05-02 225253]()
-
作为服务器端,与对方通信。
![屏幕截图 2026-05-02 225511]()
完成项目后,我将代码提交到了gitee仓库。仓库地址如下
3. 实验过程中遇到的问题和解决过程
问题1:端口占用导致服务器启动失败
- 问题描述:在多次启动服务器后,再次运行时出现
OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次的错误。 - 解决方案:在创建socket后添加
setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)选项,允许地址重用,这样服务器关闭后可以立即重新启动。
问题2:单线程通信无法支持实时交互
- 问题描述:最初的单线程实现中,服务器在等待用户输入时无法接收客户端消息,导致通信不顺畅。
- 解决方案:采用多线程技术,将接受连接、接收消息和命令输入分别放在不同的线程中处理,实现真正的实时通信。
问题3:加密解密失败
- 问题描述:客户端和服务器使用相同的口令进行加密解密,但有时会出现解密失败的情况。
- 解决方案:检查发现是AES-CBC模式需要初始化向量(IV),每次加密都需要生成新的IV并与密文一起发送,解密时使用相同的IV进行解密。
问题4:文件传输过程中通信阻塞
- 问题描述:在进行文件传输时,无法同时进行文本消息的收发,影响用户体验。
- 解决方案:使用多线程处理文件传输,将文件传输和文本消息分别放在不同的线程中,实现并行处理。
问题5:心跳检测机制不完善
- 问题描述:长时间不通信时无法检测对方是否在线,容易出现假连接状态。
- 解决方案:实现心跳检测机制,每10秒发送一次心跳包,当30秒未收到对方心跳时自动断开连接。
4. 实验感悟与思考
通过本次实验,我深入理解了Socket网络编程的原理和实践方法。以下是我的几点感悟:
4.1 网络通信的复杂性
网络通信涉及到客户端和服务器的协调、数据的加密解密、异常处理等多个方面。特别是在实现加密通信时,需要确保加密算法的正确性和安全性,同时处理好密钥的管理。
4.2 多线程编程的重要性
在网络编程中,单线程往往无法满足实时通信的需求。通过使用多线程,可以实现并发处理,提高程序的响应速度和用户体验。但同时也需要注意线程安全和资源共享的问题。
4.3 协议设计的重要性
一个良好的通信协议是确保通信双方正确交互的关键。在本次实验中,我设计了一套完整的控制码协议,涵盖了认证、消息传输、文件传输等多个方面,使通信过程更加规范和可靠。
4.4 调试与测试的必要性
在开发过程中,调试和测试是不可或缺的环节。通过与同伴的联调测试,我发现了许多潜在的问题,并逐步完善了程序的功能和稳定性。
4.5 安全意识的提升
通过实现加密通信,我深刻认识到网络安全的重要性。在实际应用中,数据的加密传输是保护用户隐私和数据安全的重要手段。
5. 代码托管
我将代码托管到了gitee仓库。仓库地址如下:





浙公网安备 33010602011771号