简介:本资源是一份面向高校计算机网络课程设计的完整实践方案,专为本科生课程实训与网络工程初学者打造,聚焦中小企业级电子购物网站的网络架构设计与服务部署。内容涵盖需求分析、拓扑规划、子网划分(≥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 53tcp 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里的网络真相
以点击“加入购物车”为例,抓包中必出现以下四组帧(按时间顺序):
DNS查询(UDP 53端口)
- 现象:
Standard query A localhost(无响应)→Standard query A 127.0.0.1(有响应) - 为什么重要:证明你的浏览器真走了DNS解析流程。若直接命中hosts文件,Wireshark里看不到UDP 53包,需手动
ping localhost触发DNS。
- 现象:
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抓不到。
- 现象:
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复用连接)。
- 现象:
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恢复能力
这是最贴近生产环境的测试。操作步骤:
- 启动Flask和Socket服务
- 浏览器打开
http://localhost:5000,加入商品到购物车 - 物理拔掉网线(或禁用Wi-Fi),等待30秒
- 重新插回网线,刷新页面
预期结果:
- 页面应正常加载(HTTP服务在本地,不受外网影响)
- 购物车数据仍在(因
cart_db在内存,非分布式Session) - 若你扩展了Redis存储,则需验证Redis连接自动重连
我的血泪教训:第一次做课设时,我花3天调通功能,却在答辩前2小时发现
tc注入丢包后购物车清空——根源是Socket客户端没设settimeout(),导致recv()无限等待,Flask主线程卡死。从此养成习惯:任何socket.recv()前必设超时,任何requests.post()必加timeout=(3, 10)。网络编程里,超时不是可选项,是生存底线。希望帮到你。
本文还有配套的精品资源,点击获取