传输层:TCP和UDP
使用IPv4和IPv6的应用程序
平时我们使用的tcpdump
、ping
、traceroute
属于TCP/IP协议族,虽然叫TCP/IP协议族,但是这个协议族还涉及到许多其他成员。下图是其概貌。
- tcpdump直接同数据链路层通信,使用BPF或DLPI的接口不用套接口或XTI。
- tcpdump以外的9个应用程序通常是套接口或XTI。
- traceroute程序使用两种套接口:IP接口和ICMP套接口。
- ICMP,网际控制消息协议。处理路由器和主机之间的错误和控制消息,ping程序使用ICMP。
ping和ICMP
下图是ping百度主页的结果:
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2
Request timeout for icmp_seq 3
64 bytes from 14.215.177.39: icmp_seq=0 ttl=55 time=4262.004 ms
64 bytes from 14.215.177.39: icmp_seq=1 ttl=55 time=3726.616 ms
64 bytes from 14.215.177.39: icmp_seq=2 ttl=55 time=4887.194 ms
64 bytes from 14.215.177.39: icmp_seq=3 ttl=55 time=4479.124 ms
Request timeout for icmp_seq 8
64 bytes from 14.215.177.39: icmp_seq=4 ttl=55 time=5194.957 ms
ICMP没有端口
说ping
一个端口,是不妥的。如果有人说ping
一下80端口通不通,其实指的是发送一个TCP请求探测一下80端口能不能回包。
真正的ping
使用的是ICMP,没有端口一说。
无连接、不可靠的UDP
无连接
UDP客户与服务器之间不必存在长期的关系。
一个UDP客户可以通过同一个UDP套接口发送数据报给不同的服务器。
一个UDP服务器可以通过同一个UDP套接口从不同的客户收数据报
不可靠
UDP本身不提供确认、序列号RTT估算、超时及重传机制。
如果一个UDP数据报在网上被复制,那所有的拷贝都可能送到接收方的主机。
如果一个UDP客户发送不同的数据报到同一个目的地,它们的顺序可能被打乱。
使用UDP的服务
实时音视频聊天、一些在线游戏等时间敏感的应用,适用于UDP。这些场景下,使用者可以忍受一定程度的数据丢包,但是不能容忍过多的延迟。
面向连接、可靠的TCP
面向连接
正如第一章的时间日期程序——“接受客户连接,发送应答”步骤所说:
TCP连接使用三路握手(three-way handshake)来建立,当握手完毕时,accept函数返回,其返回值是一个称为已连接(connected descriptor)的新描述字(connfd)。此描述字用于与新客户的通信。accept为每个连接到服务器的客户返回一个新的已连接描述字。
TCP连接使用三路握手(three-way handshake)来建立,当握手完毕时,accept函数返回,其返回值是一个称为已连接(connected descriptor)的新描述字(connfd)。此描述字用于与新客户的通信。accept为每个连接到服务器的客户返回一个新的已连接描述字。
确认
TCP发送数据,有超时及重传机制,数次重传失败后,TCP才放弃。这一点使得TCP比UDP可能消耗更多的时间。
序号
TCP根据数据分节的序列号,进行排序,去重,将完整的数据传递给应用进程。
流量控制
TCP有接收缓冲区,缓冲区满后,必须等到应用进程从缓冲区读取数据后才能继续接收新的数据。
UDP发送数据不管接收方的缓冲区是不是能装下。
全双工
最后,TCP的连接是全双工的,意味着接收方也可以在下一时刻成为发送方,这一前提下,追踪每个方向上数据流的状态信息(序列号、通告窗口)大小,就显得尤为重要了。
TCP连接的建立和终止
建立:三路握手
建立一个TCP连接的步骤:
- 被动打开 服务器通过调用
socket
、bind
、listen
,准备好接受外来的连接。 - 主动打开 客户端调用
connect
,发送一个SYN分节,告知服务器建立连接后数据的初始序列号。 - 服务器确认 服务器必须确认客户端发来的SYN分节,ACK=SYN+1,并且自己也发一个SYN分节给客户端。
- 客户端确认 客户端必须确认服务器的SYN。
TCP选项
下面是第一个分节的抓包,包含了一些选项
16:25:52.755769 IP localhost.60933 > localhost.daytime: Flags [S], seq 1267114631, win 65535, options [mss 16344,nop,wscale 5,nop,nop,TS val 4110257572 ecr 0,sackOK,eol], length 0
0x0000: 4500 0040 0000 4000 4006 0000 7f00 0001 E..@..@.@.......
0x0010: 7f00 0001 ee05 000d 4b86 a287 0000 0000 ........K.......
0x0020: b002 ffff fe34 0000 0204 3fd8 0103 0305 .....4....?.....
0x0030: 0101 080a f4fd 8da4 0000 0000 0402 0000 ................
- MSS选项。TCP发送的SYN中带上这个选项,通知对方它的最大分节大小,即它能接受的每个TCP分节中的最大数据量。
- 窗口规模选项。TCP双方能够通知对方的最大窗口大小是65535,因为TCP Header里,这个字段只占16位(2^16-1)。
- 时间戳选项。在高速连接中,一些由于暂时的路由的原因造成的迷途分组,在路由稳定后,正常到达目的地,TS选项可以防止上述过程可能造成的数据损坏。
终止:四次挥手
终止一个TCP连接的步骤:
- 主动关闭 某个应用进程首先调用
close
,我们称这一端执行主动关闭,它发出第一个FIN分节。 - 被动关闭
接收到FIN的端执行被动关闭,确认对这个FIN的接收,这次接收意味着应用进程在相应连接上再也接收不到额外数据(它的接收会作为文件结束符发给被动方应用进程)。 - 被动方发起关闭 一段时间后,收到文件结束符的应用进程也调用
close
关闭它的套接口,向主动方发送一个FIN。 - 主动方确认 接收到FIN分节后,主动关闭的一方也要确认这个分节。
上图演示了客户端发起的主动关闭,实际上无论是客户端还是服务器都可以执行主动关闭。譬如HTTP(超文本传送协议)就是服务器执行主动关闭。
TCP状态转换图
- 为每一个连接定义11种状态。
- TCP规则决定状态的转换条件,这种转换基于当前状态及在该状态下所接收的分节。例如: 应用进程在
CLOSED
状态下执行一个主动打开:
在SYN_SENT
情况下收到附带ACK的SYN:
应用进程调用
close
主动关闭:应用进程在
ESTABLISHED
状态下接收到FIN:数据捎带
在TCP的建立连接的三次握手和终止连接的四次挥手之间,是数据分节的传输。此时服务器对客户请求的确认是伴随着服务器的应答发送的。这称为捎带,通常在服务器处理请求并产生应答的时间少于200ms时发生。如果服务器耗用更长的时间,如1s,就会先确认,再应答。
TCP数据传输
TCP还是UDP
上图这样的单一分节的请求和接收,使用TCP时,包括连接建立和连接终止的7个分节,以及最后一次客户对服务器数据的应答,有8个分节额外需要消耗。如果使用UDP,只有2个分组需要交换。
许多应用程序还是在使用UDP,因为它们需交换的数据量很小,也避免TCP连接建立和终止连接的额外开销。
TIME_WAIT状态
执行主动关闭的那端会在转换成CLOSED
之前进入这个状态。
- 停留在该状态的持续时间是最长分节生命期
MSL
的两倍,俗称2MSL
。 -
MSL
的值选择在30s~ 2min之间,这意味着TIME_WAIT
状态的延迟在1min~4min之间。 -
MSL
是IP数据报能在互联网中生存的最长时间。
存在TIME_WAIT状态的两个理由
- 实现终止TCP全双工连接的可靠性
假设最后一个主动关闭方发给被动方的ACK
丢失,被动方就会重发最终的FIN
,因此主动一方必须维护状态信息,以允许它重发对应的ACK
。为了实现全双工关闭(两个方向数据流都彻底关闭),TCP必须正确处理这四个分节中任何一个分节的丢失情况。 - 允许老的重复分节在网络中消逝
由于一个分节最多存在MSL
,那么TIME_WAIT
等待2个MSL
之后,便可以保证老的重复分节已经在网络中彻底丢弃。这样下一次在相同的IP地址和端口建立的连接,必然不会接收到老的请求分节了。
TCP并发服务器
并发服务器中,主服务器通过循环派生子进程来处理每个新的连接。
整理了一些最新LinuxC/C++服务器开发/架构师面试题、学习资料、教学视频(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享有需要的可以自行添加 学习交流群
有疑问加站长微信联系(非本文作者)