☰
计算机网络课设:用Flask+Socket实现可抓包验证的电子购物系统
2026/10/7 17:54:08 网站建设 项目流程

简介:本资源是一份面向高校计算机网络课程设计的完整实践方案,专为本科生课程实训与网络工程初学者打造,聚焦中小企业级电子购物网站的网络架构设计与服务部署。内容涵盖需求分析、拓扑规划、子网划分(≥4个)、IP地址分配、DNS/DHCP/WEB/FTP四大核心服务的软件选型与配置说明,并附有简易WEB主页实现思路,有效衔接TCP/IP模型、网络设备配置与服务器管理等关键知识点。压缩包为单个2.84MB的Word文档(.doc),结构清晰,含前言、任务书、五章主体内容(需求分析、拓扑结构、设备与子网配置、服务软件配置、界面设计)及小结与参考文献,文档内含详细配置步骤、参数说明与教学提示。目前已有2042人学习下载,适合用于课程报告撰写、实验复现与网络系统设计能力强化。

1. 计算机网络课程设计电子购物网站设计:不是搭个网页,而是用TCP/IP栈跑通一次真实交易闭环

“计算机网络课程设计电子购物网站设计”——这标题乍看像前端作业,实则是个典型的网络层+应用层联合验证型实训项目。它不考你能不能用Vue写出漂亮界面,而考你能否让一个HTTP请求从浏览器发出,经DNS解析、TCP三次握手、HTTP状态码流转、服务器端Socket监听与响应、再到客户端完整接收并渲染,全程可抓包、可调试、可复现。我带过6届网络课设,90%的学生卡在“页面能打开,但加购失败没报错,Wireshark里却看不到POST数据”,根源不是代码写错,而是根本没理解应用层协议如何依赖传输层可靠交付、传输层又如何受网络层路由影响。这个设计真正要练的是:用最小可行系统(一个商品页+购物车+订单提交)把OSI七层模型从纸面拉进Wireshark的Packet List里。适合刚学完谢希仁《计算机网络》第3~6章、正在啃TCP拥塞控制和HTTP状态机、需要把抽象协议具象化的本科生。别急着写HTML,先让netstat -an | grep :8080能看到你的服务端口真正在LISTEN。


2. 用Python Flask + Socket实现双协议栈:HTTP提供页面,原始Socket处理关键事务

课程设计最常翻车的点,是把“电子购物网站”当成纯Web开发任务——用Django或Spring Boot一气呵成,结果答辩时被问“请现场抓包展示三次握手过程”,当场哑火。真正的网络课设,必须暴露底层协议交互。我的做法是:HTTP只负责静态资源分发和表单渲染,所有核心业务逻辑(添加购物车、生成订单)走自定义TCP Socket通信。这样既能用浏览器访问,又能用telnet localhost 9999直连调试,Wireshark里清晰看到SYN/SYN-ACK/ACK、应用层自定义协议帧、FIN挥手全过程。

2.1 搭建双协议服务框架:Flask做门面,Socket做内核

先建基础目录结构:

shopping-system/ ├── app.py # Flask主服务(HTTP) ├── socket_server.py # 自定义TCP服务(购物车/订单核心) ├── static/ │ ├── css/ │ └── js/ └── templates/ ├── index.html # 商品列表页 └── cart.html # 购物车页

app.py只做三件事:提供HTML页面、转发用户操作到Socket、返回Socket响应结果。关键不是渲染逻辑,而是精准控制HTTP连接生命周期:

# app.py from flask import Flask, render_template, request, jsonify import socket import json app = Flask(__name__) def send_to_socket(data): """向本地Socket服务发送JSON指令,等待响应""" try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect(('localhost', 9999)) # 连接自定义Socket服务 s.sendall(json.dumps(data).encode('utf-8')) response = s.recv(4096) return json.loads(response.decode('utf-8')) except Exception as e: return {"status": "error", "msg": str(e)} @app.route('/') def home(): return render_template('index.html') @app.route('/api/add_to_cart', methods=['POST']) def add_to_cart(): # 接收HTTP POST,转为Socket指令 item_id = request.json.get('item_id') qty = request.json.get('qty', 1) result = send_to_socket({ "cmd": "add", "item_id": item_id, "qty": qty }) return jsonify(result) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)

注意:这里send_to_socket每次请求都新建Socket连接,模拟真实用户行为(非长连接)。若需测试TCP连接复用,可改用socket.setdefaulttimeout(5)并复用连接对象,但课程设计阶段建议保持简单——每个HTTP请求对应一次独立TCP会话,便于Wireshark逐帧分析。

2.2 实现Socket服务端:手动解析协议帧,暴露TCP状态细节

socket_server.py才是网络课设的核心战场。它不依赖任何Web框架,纯用socket模块实现,强制你处理粘包、心跳、连接超时等真实问题:

# socket_server.py import socket import json import threading from datetime import datetime # 模拟内存数据库(课程设计不引入MySQL,聚焦网络层) cart_db = {} def handle_client(conn, addr): print(f"[{datetime.now().strftime('%H:%M:%S')}] 新连接: {addr}") while True: try: # 关键:手动处理粘包!用\r\n作为消息边界 data = b"" while not data.endswith(b"\r\n"): chunk = conn.recv(1024) if not chunk: break data += chunk if not data: break # 解析JSON指令(必须以\r\n结尾,否则阻塞) try: cmd = json.loads(data.strip().decode('utf-8')) response = process_command(cmd) conn.sendall(json.dumps(response).encode('utf-8') + b"\r\n") except json.JSONDecodeError: conn.sendall(json.dumps({"status": "error", "msg": "Invalid JSON"}).encode('utf-8') + b"\r\n") except ConnectionResetError: print(f"[{datetime.now().strftime('%H:%M:%S')}] 连接重置: {addr}") break except Exception as e: print(f"[{datetime.now().strftime('%H:%M:%S')}] 处理异常: {e}") break conn.close() print(f"[{datetime.now().strftime('%H:%M:%S')}] 连接关闭: {addr}") def process_command(cmd): """处理购物车核心逻辑,此处暴露TCP可靠性保障点""" if cmd["cmd"] == "add": item_id = cmd["item_id"] qty = cmd.get("qty", 1) # 模拟库存检查(网络课设重点不是业务,而是状态同步) if item_id not in cart_db: cart_db[item_id] = qty else: cart_db[item_id] += qty return {"status": "success", "cart_size": len(cart_db)} elif cmd["cmd"] == "get_cart": return {"status": "success", "items": list(cart_db.items())} else: return {"status": "error", "msg": "Unknown command"} def start_server(): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 关键参数:SO_REUSEADDR避免端口占用 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(('localhost', 9999)) server_socket.listen(5) print("Socket服务启动,监听端口 9999...") while True: conn, addr = server_socket.accept() # 为每个连接开独立线程(课程设计不需高并发,避免select/poll复杂度) client_thread = threading.Thread(target=handle_client, args=(conn, addr)) client_thread.daemon = True client_thread.start() if __name__ == '__main__': start_server()

为什么用\r\n做消息边界?
这是课程设计中必须亲手写的粘包处理——HTTP用\r\n\r\n分隔头/体,SMTP用.结束,你的自定义协议也得有明确帧定界符。Wireshark里能看到每个sendall()调用对应一个TCP Segment,而recv()的阻塞行为直接体现TCP流式传输特性。若跳过这步直接用recv(4096),学生永远不懂“为什么抓包看到数据被拆成两包”。


3. 用Wireshark抓包验证七层模型:从DNS查询到HTTP 200 OK的完整链路

课程设计验收时,老师最想看到的不是页面多炫,而是你能指着Wireshark的Packet List说清每一行代表哪一层。本节教你怎么用最小抓包集证明你的系统真正在跑TCP/IP栈,而非仅靠浏览器渲染蒙混过关。

3.1 设置过滤器:聚焦三次握手、HTTP事务、Socket通信三类关键帧

启动Wireshark后,先设置捕获过滤器(Capture Filter)缩小范围,避免海量无关包干扰:

tcp port 5000 or tcp port 9999 or udp port 53
  • tcp port 5000:捕获Flask HTTP服务通信
  • tcp port 9999:捕获自定义Socket通信
  • udp port 53:捕获DNS查询(浏览器访问localhost:5000时自动触发)

开始捕获后,在浏览器访问http://localhost:5000,点击“加入购物车”,再刷新购物车页。停止捕获,用显示过滤器(Display Filter)分段分析:

过滤器表达式作用典型现象
tcp.port == 53 && dns查看DNS解析过程可见Standard query A localhost→Standard query response A 127.0.0.1
tcp.port == 5000 && http分析HTTP事务看到GET / HTTP/1.1→HTTP/1.1 200 OK,注意Connection: keep-alive字段
tcp.port == 9999定位Socket通信找到[SYN]→[SYN, ACK]→[ACK],随后是你的JSON数据帧

提示:右键某条TCP包 → “Follow” → “TCP Stream”,Wireshark会自动重组该连接的所有数据流。这是验证粘包处理是否正确的黄金方法——你发送的{"cmd":"add","item_id":"A001"}\r\n必须在Stream里完整显示,不能被截断或拼接。

3.2 关键帧解读:手把手教你读懂Wireshark里的网络真相

以点击“加入购物车”为例,抓包中必出现以下四组帧(按时间顺序):

  1. DNS查询(UDP 53端口)

    • 现象:Standard query A localhost(无响应)→Standard query A 127.0.0.1(有响应)
    • 为什么重要:证明你的浏览器真走了DNS解析流程。若直接命中hosts文件,Wireshark里看不到UDP 53包,需手动ping localhost触发DNS。
  2. TCP三次握手(端口5000)

    • 现象:[SYN] Seq=0→[SYN, ACK] Seq=0 Ack=1→[ACK] Seq=1 Ack=1
    • 血泪经验:若第二步[SYN, ACK]没出现,检查Flask是否真在LISTEN:netstat -an | grep :5000。常见错误是app.run()没加host='0.0.0.0',导致只监听127.0.0.1,Wireshark抓不到。
  3. HTTP POST请求(端口5000)

    • 现象:POST /api/add_to_cart HTTP/1.1→HTTP/1.1 200 OK
    • 注意点:查看Content-Length是否匹配实际JSON长度,Connection字段是否为keep-alive(证明HTTP/1.1复用连接)。
  4. Socket通信(端口9999)

    • 现象:[SYN]→[SYN, ACK]→[ACK]→{"cmd":"add","item_id":"A001"}\r\n→{"status":"success"}\r\n→[FIN, ACK]
    • 玄学排查:若看到[RST]包,说明Socket服务未启动或端口被占;若JSON数据后没有\r\n,recv()会一直阻塞——这就是你亲手写的粘包处理在起作用。

4. 避坑指南:课程设计中最常踩的5个网络层陷阱及现场急救方案

网络课设的崩溃点,90%不在代码语法,而在对协议栈的理解偏差。以下是我在实验室亲眼见过、学生反复提交失败的5个典型问题,附带Wireshark定位法和一行命令急救方案。

4.1 现象:页面能打开,但“加入购物车”按钮点击无反应,浏览器控制台无报错

原因:Flask服务监听127.0.0.1:5000,而AJAX请求发向localhost:5000,在部分系统(如WSL2)中localhost≠127.0.0.1,DNS解析失败导致请求超时。
解决:

  • 检查Flask启动日志是否含* Running on all addresses(即host='0.0.0.0')
  • 终端执行:curl -v http://127.0.0.1:5000/api/add_to_cart -H "Content-Type: application/json" -d '{"item_id":"A001"}'
  • 若curl成功但浏览器失败,说明是跨域或DNS问题,临时在/etc/hosts加127.0.0.1 localhost

4.2 现象:Wireshark抓到大量[TCP Retransmission]包,Socket通信极慢

原因:自定义Socket服务未设置SO_KEEPALIVE,客户端短连接频繁创建销毁,触发TCP TIME_WAIT堆积,端口耗尽。
解决:

  • 在socket_server.py的server_socket创建后添加:
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) server_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60) server_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10) server_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
  • 或更简单:重启前执行sudo ss -s查看TIME_WAIT数量,若>2000,用sudo sysctl -w net.ipv4.tcp_fin_timeout=30临时缩短超时。

4.3 现象:购物车数据偶尔丢失,Wireshark里看到[TCP Dup ACK]

原因:内存数据库cart_db非线程安全,多用户并发修改导致数据覆盖。课程设计虽不强调并发,但浏览器多标签页即触发竞态。
解决:

  • 在process_command函数开头加锁:
    import threading cart_lock = threading.Lock() # 全局锁 def process_command(cmd): with cart_lock: # 关键!包裹所有读写操作 if cmd["cmd"] == "add": # ...原有逻辑
  • 验证:开两个浏览器标签页同时加购,Wireshark应看到两个独立TCP流,且cart_size累加正确。

4.4 现象:telnet localhost 9999能连上,但发送JSON后无响应,Wireshark显示[TCP segment of a reassembled PDU]

原因:未按约定发送\r\n结尾,Socket服务端recv()阻塞等待消息边界。
解决:

  • telnet中输入后务必按Ctrl+]→send→ 输入\r\n(或直接用echo '{"cmd":"get_cart"}' | nc localhost 9999)
  • 在socket_server.py的handle_client里加日志:print(f"Raw recv: {data!r}"),确认收到\r\n

4.5 现象:答辩时老师用另一台电脑访问http://[本机IP]:5000打不开

原因:防火墙阻止外部访问,或Flask未绑定0.0.0.0。
解决:

  • Windows:Windows Defender 防火墙→高级设置→入站规则→ 新建规则 → 端口5000→ 允许连接
  • Linux:sudo ufw allow 5000
  • 代码确认:app.run(host='0.0.0.0', port=5000),而非host='127.0.0.1'

5. 进阶验证:用tc工具注入网络故障,证明你的系统具备容错韧性

课程设计的高分关键,不是功能完整,而是能主动制造并应对网络异常。很多学生做到“能跑通”就停了,但老师真正想考察的是:当丢包、延迟、乱序发生时,你的TCP连接是否自动重传?HTTP请求是否超时重试?购物车数据是否因连接中断而丢失?本节教你用Linux自带的tc(Traffic Control)工具,在本地模拟真实网络病态,用Wireshark和日志双重验证系统健壮性。

5.1 注入20%随机丢包:检验TCP重传机制是否生效

在终端执行(需root权限):

# 对回环接口lo注入丢包(影响localhost通信) sudo tc qdisc add dev lo root netem loss 20% # 验证:ping自己,应看到约20%丢包率 ping -c 10 127.0.0.1 | grep "packet loss"

此时再访问http://localhost:5000,Wireshark中将密集出现[TCP Retransmission]和[TCP Spurious Retransmission]。重点观察:

  • Flask的HTTP响应是否仍能到达(TCP自动重传保障)
  • Socket通信中,{"cmd":"add"}指令是否最终成功(你的send_to_socket函数是否带重试逻辑)

若购物车始终为空,说明你的Socket客户端没处理超时——立即补上:

# 在app.py的send_to_socket函数中 def send_to_socket(data): for attempt in range(3): # 最多重试3次 try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(5) # 关键!设置超时,否则永久阻塞 s.connect(('localhost', 9999)) s.sendall(json.dumps(data).encode('utf-8')) response = s.recv(4096) return json.loads(response.decode('utf-8')) except (socket.timeout, ConnectionRefusedError) as e: if attempt == 2: raise e time.sleep(0.5) # 指数退避可选

5.2 注入100ms固定延迟:验证HTTP连接复用与Keep-Alive有效性

# 清除之前规则 sudo tc qdisc del dev lo root # 添加100ms延迟 sudo tc qdisc add dev lo root netem delay 100ms # 测试:curl两次,对比时间差 time curl -s http://localhost:5000 > /dev/null time curl -s http://localhost:5000 > /dev/null

若第二次耗时显著减少(如从105ms降到5ms),说明HTTP Keep-Alive生效,复用了TCP连接;若两次时间相近,检查Flask响应头是否含Connection: keep-alive(默认开启,除非显式关闭)。

5.3 模拟网络分区:拔掉网线再插回,检验Session恢复能力

这是最贴近生产环境的测试。操作步骤:

  1. 启动Flask和Socket服务
  2. 浏览器打开http://localhost:5000,加入商品到购物车
  3. 物理拔掉网线(或禁用Wi-Fi),等待30秒
  4. 重新插回网线,刷新页面

预期结果:

  • 页面应正常加载(HTTP服务在本地,不受外网影响)
  • 购物车数据仍在(因cart_db在内存,非分布式Session)
  • 若你扩展了Redis存储,则需验证Redis连接自动重连

我的血泪教训:第一次做课设时,我花3天调通功能,却在答辩前2小时发现tc注入丢包后购物车清空——根源是Socket客户端没设settimeout(),导致recv()无限等待,Flask主线程卡死。从此养成习惯:任何socket.recv()前必设超时,任何requests.post()必加timeout=(3, 10)。网络编程里,超时不是可选项,是生存底线。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询