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 224943

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

屏幕截图 2026-05-02 225228

  • 为保持界面整洁,主页只展示明文,双击消息可查看该消息的加密密文。
    屏幕截图 2026-05-02 225253

  • 作为服务器端,与对方通信。
    屏幕截图 2026-05-02 225511

完成项目后,我将代码提交到了gitee仓库。仓库地址如下

3_chat_gui_v4.py

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仓库。仓库地址如下:

Python实验代码

6. 参考资料

posted @ 2026-05-07 19:54  Silylorica  阅读(11)  评论(0)    收藏  举报