1 TCP/IP
1 概述
(1) 典型 C/S ( client / server )
[0] 设计 网络应用, 认为 总是由 client 发起 request
可 简化 protocol 和 program
[1] web server 程序
守护程序: 长时间运行
`response 网络请求` 时, 才 `发 网络消息`
[2] `web client 程序`: 浏览器
`与 server 进程 的 通信 总是由 client 进程 发起`
(2) TCP/IP 协议族
`web client 与 server 间 用
TCP (Transmission Control Protocol) 通信
|
| 转而 用
|/
IP(Internet Protocol) 通信
|
| 转而 用
|/
以太网协议 通信
(3)
socket: 套接口 // 翻译才更准确
1 | |\
| | 1
| |
n | | 1
\| |
sockfd: socket discriptor / 套接字
|
|/
small integer: 标识 套接字
`socket func 用 sockfd` 去 访问 `具体 socket`
(4) 网络编程 API
|
| 之一
|/
sockets API
是 `应用层 到 传输层/其他层` 的 访问接口
套接字地址结构: 套构
2 简单的 时间获取 C/S program
(1) C 端
[1] socket(): create TCP 套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0)
_ _ _ _ _ _ _ _| | |
| |/ |/
| 网际 字节流
|
| port:13
| |\ 时间获取 server 的
| | 众所周知 port
| |
| | servaddr.sin_port = htons(13): host to network short ( integer )
| |
| [2] 指定 对端(Server) 协议族 / port / ipaddr
| | |
| |/ | inet_pton(AF_INET, argv[1], &servaddr.sin_addr)
| 地址族 |/ |
| AF_INET ... presentation to number |
| 把 ASCII 命令行参数 "192.168.*.*" |
| 转换为 合适格式 |
| |
| [3] connect(): Connect 到 server |
| |
| connect(sockfd, (SA *) &servaddr, sizeof(servaddr) |
|_ _ _ _ _ _ _ _ _ _ _ _ | |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | |
[4] read 对端 (Server) 的 Response + 处理
| |
| 放 | 如
|/ |/
char 数组 fputs 到 stdout
read() 函数: 每次最多 读 MAXLINE 个 char
+ TCP 是 `无边界` 字节流
=> 循环 read(), 直到 read() 返回/读取 char 数 == 0
while ( (n = read(sockfd, recvline, MAXLINE) ) > 0) {
recvline[n] = 0; // 第 n+1 elem 置 null
fputs(recvline, stdout);
}
(2) S 端
[1] Socket
int listenfd = Socket(AF_INET, SOCK_STREAM, 0);
|
|_ _ _ _ _ _ _ _
[2] Bind | |
| |
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr) );
|
|
1] 地址族 AF_INET
2] port: 众所周知 port
3] 地址: 用 自身 `任一 网络接口地址`
INADDR_ANY
[3] Listen: 转化 为 `监听套接字`: 以 accept 来自 对端(client) 的 connection
Listen(listenfd, LISTENQ);
|
|/
由 普通套接字 转化为 `监听套接字`
[4] Accept + Response
|
|/
1] 阻塞 thread
|
| 何时被 `唤醒`
|/
直到 client connection 到达 + 被 OS kernel 接受
2] 3次握手: 建立连接
3] accept 返回 `connfd`
connfd = Accept(listenfd, (SA *) NULL, NULL);
————————————————————————————————————————————————————————————————————————————
(3) 3种 套接字 | 作用
————————————————————————————————————————————————————————————————————————————
[1] 普通 sockfd | [1] Client
| 1] socket(AF_INET, SOCK_STREAM, 0)
| 2] connect(sockfd, (SA *) &servaddr
| 3] read(sockfd, pRecv, ...)
|
| [2] Server
| 1] Socket(AF_INET, SOCK_STREAM, 0)
|
| 2] Bind(listenfd, (SA *) &servaddr, ...)
| |
| |/
| 普通 sockfd
|
| 3] Listen(listenfd, LISTENQ);
| | |
| | connnection queue 最大 size
| |/
| 普通 sockfd ( listenfd ) 被转换成
| 监听 sockfd( listenfd)
————————————————————————————————————————————————————————————————————————————
| [2] Server
|
[2] 监听 sockfd | 1] Listen(listenfd, LISTENQ);
|
listenfd | `监听` 来自 client 的 connection -> 放 connnection queue: 最大 size = LISTENQ
|
| 2] Accept(listenfd, ...)
————————————————————————————————————————————————————————————————————————————
| [2] Server
|
[3] 已连接 sockfd | 1] Write(connfd, pBuf, ... )
|
connfd | `处理 connection` 来自 client 的
|
| `每个 connection 1个 connfd`
|
| 处理 完该 connection 必须 Close: Close(connfd)
|
| 2] Close(connfd)
|
| close 引发 normal TCP `连接终止 序列`
|
| 2 个方向 上 都 `发 FIN` + 等 对端确认
————————————————————————————————————————————————————————————————————————————
3 本 Server 为 `迭代 Server`
|
|/
运行快: time + ctime => 用 迭代 Server 合理
——————————————————————————————————————————————————————————
[1] 迭代 Server: 只能 同时处理 1 个 connection:
来自 n Clients 的 n 个 connections
connnection queue
OS kernel 选1个 connnection 给 Accept 返回
——————————————————————————————————————————————————————————
[2] 并发 Server: 能 同时处理 n 个 connections
2种方法
——————————————————————————————————————————————
1] fork 1 个 子进程: for 每个 Client
——————————————————————————————————————————————
2] thread 代替 fork
——————————————————————————————————————————————————————————
4 错误处理: `包裹函数` wrapperFunc
|
首字母大写
(1) 缩短程序 + 处理前后 可 doOtherThings
[1] 完成 `实际 函数调用`
[2] check return value
[3] 出错 时, 终止进程
5 Unix errno 值: global var
[1] E 开头, 通常在 <sys/errno.h>
[2] global -> share -> 多线程 Prob -> Solu
6 OSI 模型
(1) OSI ( open systems interconnection ) 模型
ISO 搞的
(2) OSI 与 网际协议族 映射
应 表 会 传 网 数 物
应 TCP/UDP IPv4/6 网络设备驱动程序和硬件
[1] OSI 下两层: 随 OS 提供 / 设备驱动程序 和 网络硬件
通常, 只 care 数据链路 某些特性:
以太网 MTU 大小 1500 Bytes
[2] 应用层: Web C / S
对 IP, OSI 上 3 层 几乎 没区别
[3] why so design?
————————————————————————————————————————————————————————————————
处理 | 构成
————————————————————————————————————————————————————————————————
上 3 层 网络应用 ( FTP / HTTP ) | user process / 进程
————————————————————————————————————————————————————————————————
下 4 层 通信细节 | 作 OS kernel
|
[1] 发 data |
|
[2-1] wait ACK |
[2-2] sort 无序到达 的 data |
[2-3] 计算 校验和 |
————————————————————————————————————————————————————————————————
分离 `用户进程` 与 `OS kernel`:
`应 / 传 层 间 interface` 是 `构建 API 的 自然位置`
————————————————————————————————————————————————————————————————
7 POSIX
Portable Operating System Interface
2 传输层
线索
1 TCP UDP 设计 / 实现
2 传输层 协议 TCP UDP 都 转而使用 网际协议 IP
3 UDP vs. TCP
简单 / 不可靠 数据报 协议
复杂 / 可靠 字节流 协议
4 `传输层协议` -> `应用进程`
provide 的 服务
2者各 处理什么
5 `TCP 某些特性 一旦理解, 就很容易
write 健壮的 + 用 netstat 等 tools debug C/S program`
0 概述
(1) `网络应用`
[1] tcpdump
用 BPF/DLPI
`不经 传输层`, 直接与 数据链路通信
[2] `Linux` 特殊套接字 `SOCK_PACKET`
访问 `数据链路`
[3] traceroute
用 `IP/ICMP 套接字` 访问 IP/ICMP
(2) 术语
[1] TCP: Transmission Control Protocol(传输控制协议
给 `用户进程` provide 可靠的 `全双工 字节流`
TCP 套接字 是 `流套接字(stream socket)`
[2] UDP: User Datagram Protocol (用户数据报协议)
UDP 套接字 是 `数据报套接字(datagram socket)`
[3] ICMP: Internet Control Message Protocol
处理 router 与 主机 间
error / control msg
由 `TCP/IP 网络支持软件` 产生 和 处理
|
| Note
|/
而不是 用户进程
[4] IGMP: Internet Group Management Protococl
多播
[5] ARP: Address Resolution Protocol
IPv4 地址 -> MAC/硬件地址
以太网 / 令牌环网 / FDDI 等 广播网络
Note
`点到点` 网络 `不需要`
[6] RARP: Reverse ...
MAC 地址 -> IPv4 地址
1 UDP
UDP 数据报
|
| 封装
|/
IP 数据报
`无连接` service
reason: `C 与 S 间 不必存在 长期关系`
————————————————————————
1 C + 1 套接字 -> n S
————————————————————————
1 S + 1 套接字 <- n C
————————————————————————
=>
(1) 3个 不保证
不保证 `UDP 数据报`
[1] `会 到达` 最终 dst
要想保证
`应用程序 add 特性: 确认 / 超时 与 重传`
[2] `顺序` 跨网络后 不变
[3] `只到达 1 次`
(2) Note
数据报 `无法 送给 UDP 套接字` 的 2种 cases
[1] 到达 dst + 校验出错
[2] 中途丢了
(3) UDP 数据报 有 length: 随 数据 一起 递送 给 应用进程
TCP 无 length
是 字节流
2 TCP
面向连接: C 与 S 间 connection
C -> S
建立连接 -> 交换 data -> 终止连接
=>
[1] 可靠 / reliability`
————————————————————————
1] `数据` 的可靠 `递送`
————————————————————————
2] `故障` 的可靠 `通知`
————————————————————————
`而不是` 保证 data `一定被接收`
发端
发 data
等 对端 确认
没收到 确认
1] 自动重传 + 等更长时间
2] 数次重传 失败后,
通过 `放弃重传 并 中断连接`
通知 对端 传输失败
[2] 动态 估 RTT
|
round-trip time 往返时间
2 种 RTT ( LAN/WAN: ms / s)
————————————————————————————
1] `C/S` 间 RTT
2] `给定连接` RTT
|
|/
`RTT 受 网络流通 影响`
————————————————————————————
[3] sequencing
关联
1 Byte <- - -> 1 序号
| |
| |/
| sequence number
|
| n(1024) Bytes -> 1 分节
|/
分节: TCP 传给 IP 的 DataUint
收端 TCP 据 byteSeq `丢弃 重复 data`
[4] flow control (流量控制)
接收 buf(缓冲区)
|
| current 可用 size
|/
通知 窗口(advertised window)
1] tell 对端 maxSendBytesNum
2] 动态变化
1> 收 data => 变小
2> 应用 read from 接收 buf => 变大
[5] full-duplex (全双工)
`应用` 可同时在 connection 上 收 + 发
\ /
都有 seqNum / windowSize
3 TCP 连接建立/连接终止
意义
帮助 理解 connect accept close
用 netstat 调 TCP 应用
IP 数据报 = IP 首 + TCP 首 + 可能的 TCP 选项
(0) 3次握手 / 4次挥手 本质
——————————————————————————————————————————————————————————————————————————————————
C/S 两端 都 进行一次 发 + 收 ACK, 来 确保 `自己发的 被对方收到`
|
|
1] SYN
2] FIN
=> 都是 4 个 分节: 1 / 2 / 3 / 4
区别
——————————————————————————————————————————————————————————————————————
1] 连接建立 第2/3 分节 `合并 为1个分节 都由 对端发送 => 3次 握手
——————————————————————————————————————————————————————————————————————
2] 连接终止 第2/3 分节 `不能合并` 为1个分节 => 4次 挥手
|
|/
dataRecvQueue 还有 data,
FIN 还没取到
——————————————————————————————————————————————————————————————————————————————————
[0] SYN 与 FIN 均 占 1 Byte 序号空间
[1] `SYN/ACK 分节` 中 序号
1 Byte 序号空间
|\
| 占
|
`SYN x = 要发的 `(初始) 序号`
=> 本端: `要发` 的 `下一序号 = x +1`
|
| 等于
|
=> 对端: `期待接收` 的 `下一序号` ACK y
[2]
FIN x
|
| + 1
|/
ACK y
(1) TCP 连接建立: 3路握手/3个分节`
[0] S 被动 open
|
| 准备 accept 外来连接
|
| socket
| bind
| listen
|/
[1] C 发起 `主动 open`
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
connect |
|
=> client TCP `发 SYN(同步)` 分节 -> (通常) 不带 data |
| |
| tell 对端 S - - - - - - - - - - - - -
|/ |
自己要发 data 的 `(初始)序号` |
|
- - - - - - - - - - - - - - - - - - - - - - - - - |
|
[2] 对端 S 回 ACK + 发 SYN
|
|
[3] C 回 ACK
————————————————————————————————————————————————————————————————————
(2) TCP 连接 终止: 4次挥手/4个分节`
[1] 本端 `主动 close`
`close 直接 被 ( 应用进程 ) 调用`
|
| =>
|/
TCP `发 FIN`
|
| tell 对端
|/
自己 data 已发完
[2] 对端 收到 FIN 后 `被动关闭`
read 返回 0: no longer read
=> `FIN 的 接收 作 `end-of-file`(文件结束符) 放 `应用进程` 的 `dataRecvQueue` 尾 + `回 ACK`
| |
| |
|- - - - - - - - - - - - - - - -
some time later [3] `取到 eof == 收到 FIN`
=> `close`
|
| =>
|/
`发 FIN`
|
|/
[4] 本端 回 ACK
(3) Note
[1] 发 FIN 的 另 2种 case
`close 间接 被 ( 运行库 ) 调用`
`进程 终止` => `所有 connfd` 上都调 `close` => 相应 connection 发 FIN
1] 自愿
1> 调 exit
2> 从 main 返回
2] 非自愿
收到 终止本进程 的 signal
[2] 2种 异常终止
1] step1 的 FIN 随 data 发送
2] step2/3 中 ACK/FIN 合为 1个 分节
[3] half-close
step2/3 间, `被动关闭端` 仍发
=> shutdown 相关
4 TIME_WAIT 状态
[1] `主动 close 端`
在 TIME_WAIT 状态
持续时间 = 2MSL
|
| maximum segment lifetime
| |
| | 指
| IP 数据报
|/
MSL
上限 255
`IP 数据报: hop limit 字段 = 8位
[2] `迷途 重复分组 / lost duplicate`
|
| 如
|/
TCP 分节
原因: `路由 异常`
|
|
|/
修复: 期间 `路由循环(router1/2 把分组 相互传来传去)`
[3] TIME_WAIT 状态 存在的理由?
1] 实现 可靠的 连接终止
2] 保证 每建立1个 新 connection
来自该连接 的
`先前化身 的 老的重复分组 已消逝`
5 TCP 端口号 与 并发服务器
`单宿(单 IP 地址) clients 主机` 上 起 `2 个 client`
通过 `不同 TCP 端口 均 连接到`
1个 `多宿 server` 的 `同一 IP 地址:port` 上
并发 server
(1) TCP server 在 port 21 上 被动打开
(2) client1 `connect 请求`
(3) 并发 server `fork` 自身的 copy,
让 `子进程` 处理 client1 `connect 请求`
(4) client2 `connect 请求`: 同 server + 同 IP地址:port
TCP 必须查看 `两端 socketPair = 4 个元素`
才能确定让 `哪个 子进程 去处理 哪个 connection`
6 TCP 选项/状态转移图/观察分组
(1) TCP 选项
1SYN -> n TCP 选项 ( 在 `TCP 首部` )
[1] `MSS: maximum segment size 最大分节大小`
=> `发端 TCP` 用 `收端 MSS` 作 `发送分节 最大大小`
[2] `窗口规模: 通知窗口 ( 字段 占 16 位 )` 要 `左移 的 位数 (0-14)`
|
|/
变大
TCP 两端都支持该选项, 才能使用之
[3] `时间戳`
(2) `TCP 态转换图`
状态转移图 并不难理解, 可分 3 部分 + 1 key
`part1: 沿 粗实线 / Client 正常状态转换`
CLOSED
-> 应用: 主动打开 -> 导致 -> 发: SYN
-> SYN_SENT
-> 收: ACK + SYN -> 导致-> 发: ACK
-> ESTABLISHED
-> 应用: 关闭 -> 导致 -> 发: FIN
-> FIN_WAIT_1
-> 收: ACK -> 发: <无>
-> FIN_WAIT_2
-> 收: FIN -> 导致 -> 发 ACK
-> TIME_WAIT
-> 2 MSL 超时
-> CLOSED
`part2: 沿 粗虚线 Server 正常状态转换`
CLOSED
-> 应用: 被动打开 -> 导致 -> 发: <无>
-> LISTEN
-> 收: SYN -> 导致-> 发: ACK + SYN
-> SYN_RCVD
-> 收: ACK -> 导致 -> 发: <无>
-> ESTABLISHED
-> 收: FIN -> 导致 -> 发: ACK
-> CLOSE_WAIT
-> 应用: 关闭 -> 导致 -> 发: FIN
-> LASK_ACK
-> 收: ACK -> 导致 -> 发: <无>
-> CLOSED
`part3: other 转换`
Client 端:
1) 到 同时打开
2) 到 同时关闭
3) 同时关闭 到 TIME_WAIT
4) FIN_WAIT1 接收 ACK + SYN
5) SYN_SENT -> 应用关闭
Server 端:
收 RST
[1] SYN_SENT
-> 1) 应用: 关闭 或 超时
-> CLOSED
-> 2) 收: ACK + SYN -> 导致-> 发: ACK
-> ESTABLISHED
-> 3) 收: SYN ( `同时打开` ) -> 导致 -> 发: ACK
-> SYN_RCVD
[2] FIN_WAIT_1
-> 1) 收: ACK -> 发: 无
-> FIN_WAIT_2
-> 收: FIN -> 导致 -> 发 ACK
-> 2) 收: ACK + SYN -> 导致 -> 发: ACK
-> 3) 收: FIN ( `同时关闭` ) -> 导致 -> 发: ACK
-> CLOSING
-> TIME_WAIT
key: `start 状态 处于 C 端 还是 S 端`
-> `应用进程` 可能 `发起 什么操作` or `收到 什么分节`
-> `导致 发送 什么` & `转换到 什么状态`
note:
(1) TCP 为 connection 定义 11 种状态
netstat 可 显示 11 种 状态名
用于 debug C/S 程序时, 监视 TCP 状态变化
(2) ( 应用 ) 进程
TCP OS 内核进程 之上 应用层 用户进程
(3) TCP 规定 如何 基于
`当前状态 + 应用进程 发起的操作 / 所接收的分节`
从 一个状态 转换到 另一状态
//eg
某 `应用进程` 在 CLOSED 状态 下 `执行`
主动打开
-> TCP `发 SYN`
-> 新状态 为 SYN_SENT
-> 该 TCP `收 ACK + SYN`
-> 该 TCP 将 `发 ACK`, 且 新状态 为 ESTABLISH
(4) 细实线 中 除 server 接收 RST 之外, 其余 均为
`client 端 操作`:
应用 (应用进程 发起操作)
接收
发送
(5) 应用(进程发起操作) / 接收 -> 导致 -> 发送
(3) 观察分组
展示 C/ S 端 经历的 TCP 状态
C / S 通告的 MSS 可不同, 536 / 1460 Bytes
1460(以太网 IPv4 典型值)
`捎带 piggybacking`
S 对 C 请求 的 `ACK 随 应答 发送`
6 第 3 次握手 失败`
`server 会定时 重发 ACK + SYN`,
重传 指定次数 后, 仍未 收到 ACK
一段时间后, `server 自动关闭 该 conn`
此时
`client 认为 该 conn 已建立` -> `C 向 S 写 数据`, S 回 `RST`
`RST: Reset 异常关闭连接`
7 端口号
`TCP/UDP/SCTP 协议` 都用 `端口号(port number)` 来 `区分 多进程 (的 server/clients 端)`
2类/3种 端口:
(1) 标识 server
可能的话, `相同的端口号` 分给 `TCP/UDP` 的 `同一给定服务`
端口号`80 / 6000-6063` 都被赋予 TCP及UDP 的 `web / X Window server`
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 连接`