2 传输层: TCP UDP: UNIX 网络编程 卷1

时间:2021-6-19 作者:qvyue
key points:

1. 本章目的: 
(1) 从 网络编程 角度 提供足够的细节 
以理解 如何使用 TCP UDP 协议

(2) 讲述 TCP UDP 的 设计 / 实现

2. 传输层 协议 TCP UDP 都 转而使用 网际协议 IP

3. UDP vs. TCP
简单 / 不可靠 数据报 协议

复杂 / 可靠 字节流 协议

4. key points:

了解 `传输层协议` provide 给 `应用进程` 的 `服务`,
才能弄清 `传输层协议 处理什么 / 应用进程 处理什么`

5. `TCP 某些特性 一旦理解, 
就很容易 write 健壮的 client / server program`,
也 很容易 使用 诸如 netstat 等 tools 来 debug client / server program

2.1 总图

2 传输层: TCP UDP: UNIX 网络编程 卷1
1. `网络应用` 

(1) 最左边 `tcpdump` 用 BPF/DLPI 
`不通过 传输层, 直接与 数据链路通信`

(2) 其余 下面的 虚线 标记为 `API, 通常是 套接字` 或 XTI

(3) `Linux 用 特殊套接字 SOCK_PACKET` 提供对 数据链路 的 访问

(4) traceroute 用 2种套接字:
`IP/ICMP 套接字` 用于访问 IP/ICMP

2. 术语

(1) IPv4/6: Internet Protocol(网际协议) version 4/6
给 TCP / UDP 等 provide `分组递送服务`

(2) TCP: Transmission Control Protocol(传输控制协议)
面向连接
`为 用户进程 provide` 可靠的 全双工 字节流

`TCP 套接字` 是 `流套接字(stream socket)`
TCP 关心 确认 / 超时 / 重传 等 细节

(3) UDP: User Datagram Protocol(用户数据报协议)
无连接
UDP 套接字 是 数据报套接字(datagram socket)
UDP 数据报 不保证 最终到达 目的地

(4) ICMP: Internet Control Message Protocol

处理 router 与 主机 之间 的 error / control msg

这些 msgs 通常由 TCP/IP 网络支持软件(而不是 用户进) 产生 和 处理

(5) IGMP: Internet Group Management Protococl
用于 多播

(6) ARP: Address Resolution Protocol

把 IPv4 地址 映射为 MAC/硬件地址

应用: 以太网 / 令牌环网 / FDDI 等 广播网络

`点到点 网络 上 不需要`

(7) RARP: Reverse ...

MAC 地址 -> IPv4 地址

(8) BPF: BSD packet filter
Berkeley kernel

(9) DLPI: datalink provider interface
SVR4 kernel

2.2 UDP

1. `应用进程` 往 1个 `UDP 套接字` 写入 1 个 msg 
-> 被 `封装` (encapsulating) 到 1个 `UDP 数据报`
-> 被 封装 到 1个 `IP 数据报`
-> 发送 到 目的地

2. UDP 3 不保证 `UDP 数据报`
(1) `会 到达` 其 最终目的地

(2) `先后顺序` 跨网络后 保持不变

(3) `只到达 1 次`

3. UDP 编程 所遇问题

(1) 数据报 `无法 递送给 UDP 套接字` 的 cases:
1) 到达 其 最终目的地, 但 校验出错
2) 中途丢了

(2) 若想 确保 UDP 数据报 到达其 目的地,
要往 `应用程序` 中 add 许多特性:
来自对端的 确认 / 本端的 超时 与 重传 等

4. UDP 数据报 有 长度, 会随 数据 一起 递送 给 应用进程

TCP 是 字节流, 无边界

5. UDP provide `connectionless` service

reason: `UDP client 与 server 之间 不必存在 长期关系`

// eg. 
UDP client 可用 同一套接字 发 数据报 给 多个 server

UDP server 可用 同一套接字 从 多个 client 接收 数据报

2.3 TCP

1. client 与 server 间 connection

client 与 server:
建立连接 -> 交换 data -> 终止连接

2. reliability (可靠性)

(1) 发端 发 data, 要求 对端 回 一个 确认

(2) 若 没收到 确认, 则 

1) 自动重传 + 等待更长时间

2) 数次重传 失败后, 
(`通过 放弃重传 并 中断连接`) 通知 client 传输失败

=>TCP 并不保证 data 一定会被 对端 接收

reliability 指 数据的可靠递送 or 故障的可靠通知

3. dynamically calculate RTT

round-trip time 往返时间

(1) TCP 会估算 2 种 RTT
1) `client / server` 间 RTT

2) `给定连接` 的 RTT: 因 `RTT 受 网络流通 影响`

(2) RTT 在  LAN/WAN 约 几 毫秒 / 秒

4. sequencing: every Byte 关联 1 个 sequence number

分节: TCP 传给 IP 的 data uint

(1) 如 某 应用 write 2048 Bytes 到 `TCP 套接字`,
导致 TCP 发送 分 `2 个分节`:
分节 1 是 Byte1-1024, 分节 2 是 Byte1024-2048

(2) 收端 TCP 据 every byte 的 序列号 可 `丢弃 重复 data`

如 `发端 认为1个分节 已丢失 并 因此重传`
, 
而 该分节只是因网络 `拥塞` 而 延迟到达

5. flow control (流量控制): 通知窗口(advertised window) + 接收 buf(缓冲区)

通知窗口 指出 接收 buf 当前可用 space size

(1) 用 `通知窗口` tell 对端 
`任何时刻, 本端 1次能从对端 accept 的 bytes number`

(2) 通知窗口 `时刻动态变化`

1) 收到 data 时, window 变小
2) 收端 应用 从 接收 buf read 时, window 变大

6. full-duplec (全双工)

在 1个 给定连接 上, 
任何时刻 `应用` 可在 发/收 2 个方向上 既发送又接收
=> 
`TCP 要为 发送/接收 2个方向 都 跟踪
序列号 窗口大小 等信息`

2.4 TCP 连接建立/连接终止/选项/状态转移图/观察分组

要理解 connect accept close, 并 使用 netstat 调试 TCP 应用,
必须 了解 TCP 连接建立/终止 过程,
并 掌握 TCP 状态转移图

1. TCP 连接建立: 3路握手/3个分节

(0) server 被动打开(passive open)
1) 目的
ready to accept 外来连接

2) how? 
socket: 协议族/套接字类型/                                                                                                                                                                                                                                                      指定协议的序号(0 -> 则 用 前2个参数组合 下 相应的 系统默认值)

bind: sockfd / pointer to 协议地址结构 / 协议地址结构大小

listen: sockfd / OS kernel 为 相应套接字排队的 最大连接数

(1) client 发起 主动打开(active open)

1) how?
connect: sockfd / pointer to 协议地址结构 / 协议地址结构大小

2) 导致 / result in
client TCP 发送 1个 SYN(同步) 分节,
tell 对端(server) 本端(client) 将在 (待建立的)
connection 中 发送 data 的 `初始序列号`

3) 通常 SYN 分节 不带 data, 相应 IP 数据报 =
IP 首 + TCP 首 + 可能的 TCP 选项

(2) server 在 `单个分节中` 发 `对 client SYN 的 确认` 和 `自己的 SYN`

(3) client 确认 server 的 SYN
(1) SYN/ACK 中的 序号:

1) `SYN 中的 发送号 n` 是 SYN 发端 要发的 `初始序列号` 
+ `SYN 分节 占 1 Byte 序列号空间`
=> SYN 发端 要发的 `下一序列号 为 n +1`

2) `ACK 中的 确认号` 是 ACK 发端 
`expect 接收 的 下一序列号`

=>
`对 SYN 的 ACK 中 确认号 是 SYN 的 序列号 + 1`

(2) FIN 也占 1 Byte 序列号空间`
`对 FIN 的 ACK 中 确认号 是 FIN 的 序列号 + 1`

2. TCP 连接 终止: 4次挥手/4个分节

(1) 本端 `主动关闭(active close)`

1) how? 
某 `应用进程 调 close`

2) result? 
该端 TCP 发 FIN 分节( 表示 `本端 data 已发完` )

(2) 对端 收到 FIN 后 `被动关闭(passive close)`

how?
read 返回 0 -> no longer read

result? 

1) `FIN 的 接收` 作 `end-of-file`(文件结束符)
放到 该 
`应用进程` 的 `data 等待接收 队列 tail`

FIN 的接收 表示 收端应用进程 在 相应连接上 
`再无 extra data 可收`

2) `发 对 FIN 的 确认`

(3) 一段时间后, 对端 `应用进程 取到 end-of-file`, 
才 `调 close 关闭 它的 套接字`

result?
`发 FIN`

(4) 本端 确认 该 FIN
note:
(1) TCP 连接终止 `通常` 需要 4 个 分节, 但有2种例外

case1: step1 的 FIN 随 data 发送

case2: step2/3 的 ACK/FIN 合为 1个 分节

(2) half-close

step2/3 之间, 被动关闭端 可能 发 data 到 主动关闭端

(3) 发 FIN 的 另 2种 case

Unix 进程 自愿( 调 exit 或 由 应用进程 返回) 
/ 非自愿(收到 终止本进程 的 信号) 终止 时,
`所有 打开的 描述符 都被关闭`,
导致 任何 打开的 TCP 连接上 发 FIN

3. TCP 选项

1 个 SYN 可有 多个 TCP 选项
(1) MSS 选项
maximum segment size 最大分节大小

`SYN 发端` 用于 tell 对端 
发端自己 在 本 connection 中 `愿意接收 的 最大数据量`

=> `发端 TCP 用 收端的 MSS 作 发送分节 的最大大小`

(2) 窗口规模选项

1) TCP 首部 `通知窗口 字段` 占 16 位
=> 最大 通知窗口 = 2^16 - 1 = 65535

2) 为 提供 更大窗口, 窗口规模选项 用于指定 
TCP 首部 通知窗口 要 左移 的 位数(0-14)
=> 最大通知窗口 65535 * 2^14 约 1GB

3) TCP 连接 两端都支持该选项, 才能使用之

(3) 时间戳 选项
高速网络连接 必需, 
可 防止 `失而复现的分组` 可能造成 的 数据损坏

4. TCP 态转换图

2 传输层: TCP UDP: UNIX 网络编程 卷1
状态转移图 并不难理解,
可分 3 部分 + 1 key

part1: 沿 client/粗实线 的 正常状态转换 一路下去

CLOSED
-> 应用: 主动打开 -> 导致 -> 发: SYN
-> SYN_SENT
-> 收: ACK + SYN -> 导致-> 发: ACK
-> ESTABLISHED
->  应用: 关闭 -> result in -> 发: FIN
-> FIN_WAIT_1
-> 收: ACK -> 发: 无
-> FIN_WAIT_2
-> 收: FIN -> result in -> 发 ACK
-> TIME_WAIT 
-> 2 MSL 超时
-> CLOSED

part2: 沿 server/粗虚线 的 正常状态转换 一路下去


part3: 其余 异常 转换

client 端:
1) 到 同时打开
2) 到 同时关闭
3) 同时关闭 到 TIME_WAIT
4) FIN_WAIT1 接收 ACK + SYN
5) SYN_SENT -> 应用关闭

server 端:
接收 RST

key:
start 状态 处于 client 端 还是 server 端 -> 应用进程 可能 发起什么操作 or 接收到 什么分节 -> 导致 发送 什么 & 转换到 什么状态

note:
(1) TCP 为 connection 定义 11 种状态

netstat 可 显示 11 种 状态名, 
用于 debug client/server 程序时, 监视 TCP 状态变化

(2) 应用进程

TCP OS 内核进程 之上的 应用层 用户进程

(3) TCP 规定 如何 基于 
`当前状态 + 应用进程发起的操作 / 所接收的分节` 
从 一个状态 转换到 另一状态

//eg
某 `应用进程` 在 close 状态 下 `执行` 
主动打开 
-> TCP `发 SYN` 
-> 新状态 为 syn_sent
-> 该 TCP 接着 `收 ACK + SYN`
-> 该 TCP 将 `发 ACK`, 且 新状态 为 established

(4) 细实线 中 除 server 接收 RST 之外, 其余 均为 
`client 端 操作`:
应用 (应用进程 发起操作)
接收
发送

(5) 应用(进程发起操作) / 接收 -> 导致 -> 发送

5. 观察分组

2 传输层: TCP UDP: UNIX 网络编程 卷1
展示 client / server 端 经历的 TCP 状态

client / server 通告的 MSS 可不同, 536 / 1460 Bytes
1460(以太网 IPv4 典型值)

捎带 piggybacking

server 对 client 请求 的 ACK 伴随 应答 发送

6. 若 第 3 次握手 失败

`server 会定时 重发 ACK + SYN`, 
重传 指定次数 后, 仍未 收到 ACK
则 一段时间后, `server 自动关闭 该 connection`

但 `client 认为 该 connection 已建立`, 
若 client 向 server 写 数据, server 将 以 `RST` 响应

`RST: Reset 异常关闭连接`

2.5 TIME_WAIT 状态

1. TCP 网络编程中 最难理解 的是 TCP 的 TIME_WAIT 状态

2. 主动关闭 端 在 TIME_WAIT 状态 持续时间 为 2MSL

3. MSL: maximum segment lifetime

(1) MSL 是 `IP 数据报` 在 Internet 中的 `最长 存活时间`

MSL 值有上限:
因为 `IP 数据报` 的 `hop limit 字段` 为 8位
=> 最大 255
hop limit `不是真正的 time limit`, 但我们仍 假设:
`具有 最大 hop limit(255)` 的 `分组 在网络中 存活时间 

5. TIME_WAIT 状态 存在的理由?

(1) 可靠地 实现 TCP 全双工 连接的终止

(2) 保证了 每建立1个 新 TCP 连接 时, 来自该连接 的 先前化身 的 老的重复分组 已在网络中 消逝

具体来说:
(1) 假定 主动关闭 端(主端) last ACK 丢失, 
对端 将 重发 last FIN
=> 

主动关闭 端 必须 维护 TCP 状态信息 (TIME_WAIT 状态), 才能 重发 last ACK, 才能 可靠地 实现 TCP 全双工 连接的终止

`若 主端 不维护..., 主端 将 reponse a RST
-> 对端 将 解释为 错误`

note: 
本例 也说明了 `为什么 处于 
TIME_WAIT 状态 的一端 是 主动关闭 端:`
因为 `该端 可能要 重传 last ACK`

(2) 

化身( incarnation ): IP 和 Port 都相同前/后 2 个 连接, 互为 先前/新 化身

TCP 不给 处于 TIME_WAIT 状态 的 连接 发起 新化身, 又 TIME_WAIT 状态 持续时间 为 2MSL

=> 
保证了 `某方向上 分组(如 last ACK)` 最多存活 MSL 即 被丢弃, 
`另一方向上 应答(如 重发 的 last FIN)` 最多 存活 MSL 也被丢弃

=> 
`保证了 每建立1个 新 TCP 连接 时, 来自 该连接 的 
先前化身 的 老的重复分组 已在网络中消逝`

2.6 端口号

任何时候, 多个进程 可能同时使用 TCP/UDP/SCTP 
这3种协议 中的 任一种

TCP/UDP/SCTP 协议 都用 端口号(port number)区分 多进程 (的 server/clients 端)

IANA 维护(`但 只控制 well-known port`) 2类/3种 端口:

1. 标识 server

可能的话, `相同的端口号` 分给 `TCP/UDP` 的 `同一给定服务`

eg.

端口号80 / 6000-6063 都被赋予 TCP及UDP 的 web / X Window server

尽管目前 web / XWindow server 的 实现都用TCP

FTP server: 21

(1) `众所周知端口(well-known port): 0-1023`

即 BSD `保留端口`

使用 `reserved port` 的 `server` 必须 以 `超级用户特权` 启动

(2) registered port: 1024-49151

`2. 标识 clients`

`临时端口(ephemeral port)`: 
(3/4)*65536 = 49152-65535

1) client 使用, 短期存活 

2) 由 传输层协议 自动赋予 client

3. TCP 连接 的 套接字对(socket pair)

(1) 定义 `TCP 连接 两端 的 2个套接字 的 4 元组:`

`本地 IP 地址 / 本地 TCP 端口号 / 外地 IP 地址 / 外地 TCP 端口号`

每个 `套接字` 即 `IP 地址 和 端口号`

(2) `唯一标识` 一个网络上 `每个TCP 连接`

2.7 TCP 端口号 与 并发服务器

本节 线索:

1个 `单宿(单 IP 地址) ciients 主机` 上 起 `2 个 client` 
通过 `不同 TCP 端口 均 连接到` 
1个 `多宿(多 IP 地址) server` 的 `同一 IP 地址:port` 上
-> `并发 server: `
`并发 server` 中 主 server 循环 通过 
`派生 1 个 子进程 来 处理 每个 新连接`

1. TCP server 在 port 21 上 被动打开

套接字对 记号

*:21, *.*本地 IP 地址 的 星号通配符(wildcard): 表示 任意 1 个

通配地址 如何指定: 调 bind 前 把 套接字地址结构IP 地址字段 设为 INADDR_ANY

2 传输层: TCP UDP: UNIX 网络编程 卷1
image.png

2. 第 1 个 client 对 server 的 连接请求

2 传输层: TCP UDP: UNIX 网络编程 卷1
image.png

3. 并发 server fork 自身的 copy, 让 子进程 处理 client 的 请求

至此, sercer 上 `必须区分 监听套接字` 与 `已连接套接字`

fork 详解见 4.7 节
2 传输层: TCP UDP: UNIX 网络编程 卷1

4. 第 2 个 client 与 同一 server 的 同一 IP地址:port 的 连接

TCP 必须 查看 套接字对 所有 4 个元素 才能确定 
由 `哪个 端点( 即 父进程 或 某个子进程 )` 
接收 某个到达的 TCP分节:
`来自 client1 套接字 / client2 套接字 / 其他` 的
`TCP 分节 被 递送给 子进程1 / 子进程2 / 父进程`
2 传输层: TCP UDP: UNIX 网络编程 卷1

2.8 缓冲区 大小 及 限制

本节线索:
影响 `IP 数据报 大小` 的 `限制` 有哪些?
分别 如何`影响` `应用进程` 能够 `传送的数据` ?

2.8.1 IP 数据报 大小限制 和 影响

1. IPv4 数据报 最大大小: 65536 Bytes

含 IPv4 首部

2. 链路 MTU: 净 IP 数据 单片 最大大小

MTU: maximum transmission unit: `最大传输单元`

(1) `硬件 规定`

(2) `以太网 MTU = 1500 Bytes`

(3) `IPv4` 要求的 `链路 MTU 最小值 = 68 Bytes:`

最大 IPv4 首部 (`20 Bytes 固定长度` + `最大 40 Bytes 选项部分`)

拼接 `最小 片段` (IPv4 首部 中 `片段偏移` 字段 以 `8 Byte 为 单位`)

(4) `path MTU (路径 MTU)`

1) 是 两主机间 路径最小 的 MTU

2) 常见 路径 MTU: 1500 Bytes 的 以太网 MTU

3) 2 主机间 相反方向上 `路由 往往 不对称`

(5) IPv4 数据报 分片 ( fragmentation ) / 重组 ( reassembling )

1) `IP 数据报 超过` 其 `外出链路 MTU` 时, IPv4/6 执行 `分片`,
这些 片段 到达 最终目的地 前, 通常 `不会` 被 `重组`

2) `IPv4 主机 / router `对其 `产生 / 转发` 的 `IP 数据报` 执行 `分片`

(6) IPv4 首部DF(don't frament / 不分片) 位

router 接收 的 IPv4 数据报 
`超过 其 外出链路 MTU, 且 DF 位 is set` 时, router 产生 1个 
`ICMPV4 出错消息: 目的不可达, 需分片 但 DF 位 已设置`

(7) IPv4/6 最小 重组缓冲区大小 ( minimum reassembly buffer size )

IPv4/6 实现必须保证的 

IP 数据报 大小 的 最小值, 对 IPv4 该 值为 576 Bytes

3. TCP MSS: 净 TCP 数据量 最大值

MSS: maximum segment size 最大分节大小

用于 `向 对端 TCP 通告` 对端在 `每个 TCP 分节` 中  
能发送的 `TCP 数据量 的 最大值`

(1) 目的: 告诉 对端, 本端 `重组 缓冲区大小` 的 实际值, 
从而 试图 `避免 分节`

(2) 使用 IPv4 的 MSS: 16 位 字段 是合适的, 因为 
`IPv4` 数据报 中 `净 TCP 数据量` 最大为 65495 Bytes 
    = 65535 - 20 - 20 

2.8.2 TCP 输出

2 传输层: TCP UDP: UNIX 网络编程 卷1
image.png
某 `应用进程 write` data 到 1 个 `TCP 套接字` 步骤:

`每个 TCP 套接字` 有 1个 `发送 缓冲区`, 
    可用 SO_SNDBUF 套接字选项 更改

(1) 应用进程 调 write 时, `内核` 从 `应用进程 buf`
    `copy` 所有 data 到 `写 套接字 发送 buf`

(2) 若 `套接字 发送 buf` 容不下 `应用进程 buf` 的 `所有 data`:
套接字 发送 buf 
1) 更小
2) 已有其他 data

则, 

应用进程 被置 休眠 态 (假定 该套接字 是 阻塞的 (default) ), 内核不从 write 系统调用 return, 直到 应..buf 所有 data 都 copy套..发送 buf

=> `从` 写 TCP 套接字 的 `write 成功 return` 表示:
  `可 reuse 应..buf`
  
(3) 发端 TCP 从 套接字 发送 buf `取 data` 发给 对端 TCP

1) 发端 TCP 以 `MSS 大小 或 更小` 的 块 + TCP 首部
-> TCP 分节 -> 递送给 IP

MSS 是 `对端 通告的值` 或 `576 - 40 = 536 (对端 没通告 MSS 选项)`

2) IP 层: TCP 分节 + IP 首部 -> IP 数据报
-> 按 其 `目的 IP 地址` 查 `路由表` 确定 `外出链路 接口`
-> 若 IP 数据报 > 外出链路 MTU -> IP 将 IP 数据报 `分片`
    MSS 选项: 试图 避免 分片
-> 数据链路
    `输出队列`: 若 full -> 新 IP 分组 被 `丢弃` 
     -> 沿 协议栈 向上 return a error 
     -> `TCP` 收到 error, 后续合适时刻 `重传` 相应 分节
        `应用进程 不知 该 case`

(4) `发端 TCP` 从 套接字 发送 buf `只 丢弃 (对端) 已确认 data`

2.9 Internet 标准服务

echo 回射

time 流逝的时间

2.10 Internet 常见应用 的 协议

ping / traceoute: `使用 ICMP` 实现的 `网络诊断` 应用

traceoute: `自行构造 UDP 分组` 来 
    `发送 并 read` 所引发的 `ICMP 应答`
2 传输层: TCP UDP: UNIX 网络编程 卷1
image.png
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。