COMP5416 Week 07 Socket 编程实验与期中反馈总结
COMP5416 Week 07 总结:Socket 编程实验 + 期中反馈
课程:COMP5416/COMP4416 — Advanced Network Technologies,Semester 2 2026 讲师:Professor Vincent Gramoli 来源:
W7_Lab.pdf/W7_Lab_solution.pdf(Lab 5: TCP Socket)、ClientServerCode.zip(4 个 Python 文件)、Exam Prep/W7-QuizFeedback.pdf(期中 quiz 讲评,8 页)。本周没有转录稿。本周讲课内容:Week 7–8 共用一份 Multimedia 讲义(
W7-Multimedia.pdf,放在 Week 8 文件夹,文件名标注 "Weeks 7 and 8"), 讲义内容统一写在 Week 08 Multimedia 讲课总结,本文只整理 Week 7 独有的实验和 quiz 反馈,避免重复。为什么单独成篇:这个 lab 是动手写代码,和 Week 6 的 TCP 理论关系密切(能在 Wireshark 里亲眼看到三次握手), 但内容(socket API)在 Week 06 TCP 讲义里只出现了两行 Java 伪代码,单独写更清楚。
第一部分:Socket 是什么
一、Socket 的位置
Socket(套接字)是应用进程与传输层之间的「门」:应用把数据从门里递出去,由操作系统里的 TCP/UDP 负责送到对面那扇门。
| 层 | 谁控制 |
|---|---|
| 应用层(进程) | 应用开发者 |
| ─── socket ─── | 两者的接口 |
| 传输层及以下 | 操作系统 |
两种 socket 对应两种传输服务:
| TCP socket | UDP socket | |
|---|---|---|
| Python 类型 | socket(AF_INET, SOCK_STREAM) |
socket(AF_INET, SOCK_DGRAM) |
| 连接 | 先建连接(三次握手) | 无连接 |
| 每次发送是否带目的地址 | 否(连接已绑定对方) | 是(sendto(msg, (ip, port))) |
| 服务器端套接字数 | 欢迎套接字 1 个 + 每客户 1 个连接套接字 | 1 个套接字服务所有客户 |
| 数据形态 | 字节流 | 数据报 |
这一张表就是 Week 05 「UDP 二元组 vs TCP 四元组」在代码层面的样子。
第二部分:TCP 客户端与服务器代码逐行解读
二、TCPLabServer.py
from socket import * |
| 行 | 作用 | 对应的 TCP 行为 |
|---|---|---|
socket(AF_INET, SOCK_STREAM) |
建 IPv4 + TCP 套接字 | — |
bind(('', 12000)) |
绑定端口
12000;'' 表示监听本机所有网卡地址 |
确定「目的端口」 |
listen(1) |
进入 LISTEN 状态,参数是等待队列长度 | 服务器 LISTEN |
accept() |
阻塞,直到有客户端完成三次握手;返回一个新的
connectionSocket |
SYN RCVD → ESTAB |
recv(1024) |
最多读 1024 字节 | 从接收缓冲取数据(rwnd 随之变化) |
send(...) |
回送大写字符串 | — |
connectionSocket.close() |
只关这个客户的连接,欢迎套接字继续监听 | 发 FIN |
两个套接字的分工(高频概念题): -
serverSocket(欢迎套接字 welcoming socket):只负责「接门铃」,一直在 LISTEN。 -connectionSocket(连接套接字):每个客户一个,由四元组唯一标识,真正收发数据。这正是 Week 05 讲的「Web 服务器为每个连接的客户建立不同的套接字」。
三、TCPLabClient.py
from socket import * |
| 行 | 作用 | 对应的 TCP 行为 |
|---|---|---|
connect((serverName, serverPort)) |
发起连接 | 触发三次握手(SYN → SYNACK → ACK) |
send(...) |
发数据 | 注释强调:不用再写服务器地址,因为连接已经建立 |
recv(1024) |
等服务器回复 | — |
close() |
关闭 | 发 FIN,进入 FIN_WAIT_1 |
客户端没有
bind():操作系统会自动分配一个临时(ephemeral)源端口。encode/decode('utf-8'):Python 3 的 socket 只收发bytes,字符串必须编码 —— 讲义里的旧代码如果直接send(sentence)会报错,这就是 lab 说「可能需要修改代码中的错误」的原因之一。
第三部分:UDP 客户端与服务器
四、UDPLabServer.py
from socket import * |
五、UDPLabClient.py
from socket import * |
六、TCP 与 UDP 代码的逐项差别
| 对比点 | TCP 版 | UDP 版 |
|---|---|---|
| socket 类型 | SOCK_STREAM |
SOCK_DGRAM |
服务器 listen() /
accept() |
有 | 没有 |
客户端 connect() |
有 | 没有 |
| 发送 | send(data) |
sendto(data, (ip, port)) |
| 接收 | recv(n) → 只返回数据 |
recvfrom(n) → 返回
(数据, 发送方地址) |
| 服务器怎么知道回给谁 | 连接套接字天然绑定了对方 | 从 recvfrom 拿到
clientAddress 再 sendto 回去 |
| 端口 | 12000 | 12003(两个程序可同时跑,不冲突) |
UDP 服务器只有一个套接字,所有客户的数据报都进这一个套接字 —— 正是 Week 05 讲义 p12 的「目的 IP、端口相同但源不同的分组,会被导向同一个套接字」。
UDP 版不会因为服务器没开而在
sendto时报错:数据报直接丢进网络就不管了,客户端会卡在recvfrom上一直等; TCP 版服务器没开时,connect()立刻失败(Connection refused),因为握手的 SYN 会被对方回 RST。
第四部分:Lab 题目与官方答案
Exercise 1:同一台机器上跑 TCP 客户端和服务器
Q:先运行客户端还是服务器? 官方答案:服务器。
原因:TCP 客户端一启动就
connect(),如果此时没有进程在 12000 端口 LISTEN,握手失败。 服务器必须先bind+listen,处于等待状态。
Q:这种场景下 serverName 填什么?
官方答案:127.0.0.1。
127.0.0.1是环回地址(loopback),数据不出网卡,在本机协议栈里绕一圈回来。写'localhost'也行(会被 DNS/hosts 解析成 127.0.0.1)。
步骤 4:客户端输入小写句子,服务器回大写句子(例如
aaaaa → AAAAA)。
Exercise 2:远程服务器
Q:要连到对的机器,需要改什么?
官方答案:先知道服务器的 IP
地址,然后把它写进客户端代码(serverName)。
补充要点(做实验时会遇到): - 服务器代码
bind(('', port))已经监听所有网卡,不用改; - 防火墙可能挡住 12000 端口; - 端口号两边要一致。
Wireshark 抓包:客户端发
aaaaa、服务器回 AAAAA,应该能看到:
| 顺序 | 段 | 要点 |
|---|---|---|
| 1 | 客户端 → 服务器 [SYN] | 随机初始序号(Wireshark 默认显示相对序号 Seq=0) |
| 2 | 服务器 → 客户端 [SYN, ACK] | Ack = 客户端 ISN + 1 |
| 3 | 客户端 → 服务器 [ACK] | 握手完成 |
| 4 | 客户端 → 服务器 [PSH,
ACK],载荷 aaaaa |
Seq=1,Len=5 |
| 5 | 服务器 → 客户端 [ACK](可能与下一个合并) | Ack=6 |
| 6 | 服务器 → 客户端 [PSH,
ACK],载荷 AAAAA |
Len=5 |
| 7+ | [FIN, ACK] / [ACK] 若干 | 关闭连接 |
用 Week 06 的知识读包: - 第 5 行 Ack=6:客户端的数据占字节 1–5,下一个期望字节是 6 —— ACK = 对方 Seq + 数据长度。 - 三次握手的 SYN 消耗 1 个序号,所以数据从相对序号 1 开始。 - 服务器代码里先
close()(回复后立即关闭连接套接字),所以往往是服务器先发 FIN。谁先调用 close 谁就是「主动关闭方」,会进入 TIME_WAIT。
步骤 3:和同学交换角色再跑一次。
Exercise 3:UDP 版
用 UDP 代码重做 Exercise 1。(无官方答案,下面是该观察到的现象)
Wireshark 里只会看到两个 UDP 数据报:一个请求、一个回复。没有 SYN/ACK/FIN,没有序号,UDP 首部只有 8 字节(源端口、目的端口、长度、校验和)。 这就是「UDP 无连接、首部小」最直观的证据。
第五部分:期中 Quiz 反馈(W7-QuizFeedback.pdf)
期中 quiz 范围按 Ed 上的更正是 Week 1–4(见 Week 05 笔记顶部说明)。讲评只挑了几道题,下面逐题重算。 这些题的知识点分别在 Week 01、Week 02 和 Tutorial 01 博弈论。
题 1:Queueing —— 收费站车队(First car)
题设:20 辆车的车队(类比 20 比特分组);收费站每 1 秒服务 1 辆车;两收费站相距 200 km,车速 10 km/s;车队整体到齐后才开始服务(存储转发)。 问:第一辆车离开收费站后,多久进入下一个收费站的服务?
答案:39 秒。
思路:第一辆车 20 s 就到了,但它得等整个车队到齐。最后一辆车比第一辆晚 19 s 离开,再开 20 s 到达 —— 这就是存储转发:路由器要收完整个分组才能转发。
题 2:Performance —— 占用的链路比例
题设: - 电路交换:
问:一个用户在传输时,占用链路容量的比例?
答案:0.2(两种情况都是 20 / 100 = 20%)。
陷阱:30% 的活跃概率与「传输时占多少」无关,它影响的是能支持多少用户(分组交换的统计复用优势),不影响这一问。
题 3:传播时延(Link 1 propagation delay)
题设:Link 1 长 3 km,传播速度
分组长度 16000 bit、链路速率 1000 Mbps 是干扰信息 —— 传播时延只和距离、速度有关,与分组大小和速率无关。
题 4:Throughput —— 客户端链路利用率
题设:4 对服务器-客户端共享中间链路
每条路径的瓶颈:
题 5(Question 5):信道博弈 —— 对称收益(更正过)
题设:两个用户共享无线信道,每个时隙选择发送或静默。 - 只有一人发送:发送者 +16,另一人 0 - 两人都发(碰撞):各 −8 - 都不发:都 0
问:给定
推导:用户 1 发送的期望收益
发送严格更优
正确答案:
讲评原话:批改时答案设错了,正确答案是 < 2/3;选 = 2/3 或 < 2/3 的同学都给了满分。 (
时用户 1 发与不发无差异, 取任何值都是最优,所以 = 2/3 也算说得通。)
题 6(Question 9):非对称收益的均衡(题目有笔误)
题设: - 只有用户 1 发:用户 1 +24,用户 2 0 - 只有用户 2 发:用户 2 +16,用户 1 0 - 都发:用户 1 −8,用户 2 −4 - 都不发:都 0
题面问「pure-strategy Nash equilibria」,标准答案是
推导(混合策略均衡 = 让对方无差异):
用户 1 无差异条件(由
用户 2 无差异条件(由
讲评原话:「这道题有 typo,所有同学都给满分。」 笔误在哪:
是混合策略纳什均衡,而题面写成了 pure。 这个博弈的纯策略纳什均衡是 (发, 不发) 和 (不发, 发),即 和 。 记住套路:求混合均衡时,
由「用户 2 的收益」解出, 由「用户 1 的收益」解出 —— 是让对方无差异,最容易搞反。
讲评最后一页:推荐用 Kurose & Ross 交互练习 继续刷题。
第六部分:本周要点速记
| 项 | 答案 |
|---|---|
| 先启动谁 | 服务器 |
| 本机测试的 serverName | 127.0.0.1(环回地址) |
| 跨机器要改什么 | 客户端里的服务器 IP |
| TCP 服务器有几种套接字 | 欢迎套接字 + 每客户一个连接套接字 |
accept() 返回什么 |
新的连接套接字 + 客户地址 |
connect() 触发什么 |
三次握手 |
| UDP 的发送/接收函数 | sendto /
recvfrom(每次都带地址) |
| Python 3 socket 收发类型 | bytes,要
encode/decode |
| Wireshark 里 UDP 版能看到什么 | 只有两个数据报,没有握手 |
| 混合策略均衡 | 让对方无差异 |
下一篇:Week 08 Multimedia(Week 7–8 的讲课内容 + W8 TCP lab 解答)。