TCP是面向字节流的服务。字节流服务和数据包服务的区别对应到实际编程中,体现为通信双方是否必须执行相同次数的读、写操作(这只是表现形式)。 当发送端应用程序连续执行多次写操作时,发送缓冲区中这些等待发送的数据可能被封装成一个或多个TCP报文段发出。因此,TCP模块发送出的TCP报文段的个数和应用程序执行的写操作次数之间没有固定的数量关系。
当接收端收到一个或多个TCP报文段后,TCP模块将他们携带的应用程序数据按照TCP报文段的序号依次放入TCP接收缓冲区中,并通知应用程序读取数据。接收端应用程序可以一次将TCP接收缓冲区中的数据全部独处,也可以分多次读取,这取决于用户指定的应用程序读缓冲区的大小。因此,应用程序执行的读操作次数和TCP模块接收到的TCP报文段个数之间也没有固定的数量关系。
综上,发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,这就是字节流的概念:应用程序对数据的发送和接收是没有边界限制的。UDP则不然,发送端应用程序每执行一次写操作,UDP模块就将其封装层呢一个UDP数据包并发送之。接收端必须 技术针对每一个 UDP数据包执行读操作(通过recvfrom系统调用),否则就会丢包(这经常发生在较慢的服务器上)。并且,如果用户没有指定足够的应用程序缓冲区来读取UDP数据,则UDP数据将被截断。
TCP传输是可靠的:
- 使用发送应答机制,即发送端发送的每个TCO报文段 都必须得到接收方的应答,才任务这个TCP报文段传输成功。
- 采用超时重传机制,发送端在发送出一个TCP报文段之后启动定时器,如果在定时时间内未接收到应答,它将重发该报文段
- TCP报文段最终是以IP数据包发送的,而IP数据报到达接收端可能乱序、重复,所以TCP协议还会接收到的TCP报文段重排、整理,再交付给应用层。
TCP头部结构
- 16位端口号:告知主机该报文段来自哪里(源端口)以及传给哪个上层协议或应用程序(目的端口)的。进行TCP通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名服务端口号,所有知名服务使用的端口号都定义在
/etc/service 文件中。 - 32位序号: 一次 TCP 通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。 假如主机 A 和主机 B 进行 TCP 通信,A 发送给 B 的第一个 TCP 报文段中,序号值被系统初始化位某个随机值 ISN(Initial Sequence Number,初始序号值)。那么在该传输方向上(从 A 到 B),后续的TCP报文段中的序号值将被系统设置为 ISN 加上该报文段所携带数据的第一个字节在整个字节流中的偏移。例如,某个TCP报文段传送的数据是字节流中的第 1025-2048 字节,那么该报文段的序号值就是 ISN+1025。另外一个传输方向的TCP报文段的序号值也具有相同的含义。
- 32位确认号: 用作对另一方发送来的 TCP 报文段的响应。 其值是收到TCP报文段的序号值+1
- 4位头部长度: 标识该TCP头部有多少个32bit字,TCP头部最长是60字节。
- 6位标志位:
- URG:表示紧急指针是否有效
- ACK:表示确认号是否 有效,带ACK标志的TCP报文段位确认报文段
- PSH:提示接收端应用程序应该立即从TCP接收缓冲区读走数据,位后续数据腾出空间
- RST:表示要求对方重新建立连接,带RST标志的TCP报文段为同步报文段
- SYN:表示请求建立一个连接,带SYN标志的报文段位同步报文段
- FIN:表示通知对方本端要关闭连接了,称带FIN标志的TCP报文段位结束报文段
- 16位窗口大小:TCP流量控制的手段,这里的窗口,指的是接收通告窗口(Receiver Window, RWND) ,它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
- 16位校验和: 由发送端填充,接收端对TCP报文段执行CRC算法以校验TCP报文段在传输过程中是否损坏。注意,这个校验不止包括TCP头部,还包括数据部分,这也是TCP可靠传输的一个重要保障。
- 16位紧急指针: 一个正的偏移量,它和序号字段的值相加表示最后一个紧急数据的下一个字节的序号。因此,确切得说这个字段是紧急指针相对当前序号的偏移,可以理解位紧急偏移。TCP紧急指针式发送端向接收端发送紧急数据的方法。
TCP头部最后一个选项字段options是可变长的可选信息。这部分最多包涵40字节,因为TCP头部最长是60字节,前面讨论的固定部分为20字节。 其中当 kind=2 表示最大报文段长度选项。TCP连接初始化时,通信双发使用该选项来协商最大报文段长度(Max Segament Size, MSS)。TCP模块通常将MSS设置为(MTU - 40)字节,减去20字节的TCP头部和20字节的IP头部。 这样携带TCP报文段的IP数据报的长度就不会超过MTU(经过数据链路层封装的数据称为帧,帧的最大传输单元Max Transmit Unit,即帧能最多携带多少上层协议数据,比如IP数据报,通常受到网络类型的限制),避免产生IP分片。对于以太网而言,MTU为1500,MSS为1460。 kind=3时时窗口扩大因子选项,作用为扩大接收通告窗口大小,即 RWND ,若 RWND 为 N,扩大窗口 因子为M,则最新的 RWND 为 N 左移 M 位,M的取值范围位 0 - 14。和 MSS 选项一样,该选项只能出现在同步报文段中,否则会被忽略。
The port numbers are divided into three ranges:
- Well-known ports: The well known ports are those from 0 - 1,023. DCCP well known ports should not be used without IANA registration. The registration procedure is defined in document RFC4340, section 19.9.
- Registered ports: The registered ports are those from 1,024 - 49,151. DCCP registered ports should not be used without IANA registration. The registration procedure is defined in document RFC4340, section 19.9.
- Dynamic and/or private ports: The dynamic and/or private ports are those from 49,152 - 65,535.
TCP连接的建立和关闭
TCP连接的建立
这里我们使用两台电脑来做实验,他们都连同一个局域网,分别如下
- Thinkpad T470s (A): server 192.168.31.23
- MacBook Pro(B): client 192.168.31.253
实验步骤如下:
启动服务器
这里启动 A 的本地服务程序,让其充当服务器
tcpdump 抓包分析
使用如下命令对两台实验电脑的网络连接进行抓包分析
sudo tcpdump -i wlp58s0 -nt '(src 192.168.31.23 and dst 192.168.31.253) or (src 192.168.31.253 and dst 192.168.31.23)'
在 B 上通过telnet命令连接 A,此时抓到如下的包
ARP, Request who-has 192.168.31.23 (bc:a8:a6:86:58:3a) tell 192.168.31.253, length 28
ARP, Reply 192.168.31.23 is-at bc:a8:a6:86:58:3a, length 28
IP 192.168.31.253.61413 > 192.168.31.23.9999: Flags [S], seq 1033380338, win 65535, options [mss 1460,nop,wscale 6,nop,nop,TS val 1304681531 ecr 0,sackOK,eol], length 0
IP 192.168.31.23.9999 > 192.168.31.253.61413: Flags [S.], seq 3268561399, ack 1033380339, win 65160, options [mss 1460,sackOK,TS val 559784661 ecr 1304681531,nop,wscale 7], length 0
IP 192.168.31.253.61413 > 192.168.31.23.9999: Flags [.], ack 1, win 2058, options [nop,nop,TS val 1304681572 ecr 559784661], length 0
ARP, Request who-has 192.168.31.253 tell 192.168.31.23, length 28
ARP, Reply 192.168.31.253 is-at 38:f9:d3:50:86:d2, length 28
根据上述信息可以画出TCP连接时三次握手的图
- 第一个TCP报文段包括SYN标志,是一个同步报文段,表示发起连接请求,同时该报文段包函一个 ISN 值为1033380338 的序号
- 第二个TCP报文段也是同步报文段表示服务器同意建立连接,同时发送自己 ISN 值为 3268561399 的序号,并对第一个报文段进行确认
- 第三个TCP报文段是对第二个同步报文段的确认,注意从第三个报文段开始,tcpdump 输出的序号值和确认值都是相对初始 ISN 值的偏移,我们可用 -S 选项 来打印序号的绝对值
TCP数据的传输
ARP, Request who-has 192.168.31.23 (bc:a8:a6:86:58:3a) tell 192.168.31.253, length 28
ARP, Reply 192.168.31.23 is-at bc:a8:a6:86:58:3a, length 28
IP 192.168.31.253.61413 > 192.168.31.23.9999: Flags [P.], seq 1:5, ack 1, win 2058, options [nop,nop,TS val 1305545821 ecr 559784661], length 4
IP 192.168.31.23.9999 > 192.168.31.253.61413: Flags [.], ack 5, win 510, options [nop,nop,TS val 560654959 ecr 1305545821], length 0
IP 192.168.31.23.9999 > 192.168.31.253.61413: Flags [P.], seq 1:18, ack 5, win 510, options [nop,nop,TS val 560654959 ecr 1305545821], length 17
IP 192.168.31.253.61413 > 192.168.31.23.9999: Flags [.], ack 18, win 2058, options [nop,nop,TS val 1305545910 ecr 560654959], length 0
ARP, Request who-has 192.168.31.253 tell 192.168.31.23, length 28
ARP, Reply 192.168.31.253 is-at 38:f9:d3:50:86:d2, length 28
ARP, Request who-has 192.168.31.23 (bc:a8:a6:86:58:3a) tell 192.168.31.253, length 28
ARP, Reply 192.168.31.23 is-at bc:a8:a6:86:58:3a, length 28
IP 192.168.31.253.61413 > 192.168.31.23.9999: Flags [P.], seq 5:10, ack 18, win 2058, options [nop,nop,TS val 1306668516 ecr 560654959], length 5
IP 192.168.31.23.9999 > 192.168.31.253.61413: Flags [.], ack 10, win 510, options [nop,nop,TS val 561785392 ecr 1306668516], length 0
IP 192.168.31.23.9999 > 192.168.31.253.61413: Flags [P.], seq 18:35, ack 10, win 510, options [nop,nop,TS val 561785392 ecr 1306668516], length 17
IP 192.168.31.253.61413 > 192.168.31.23.9999: Flags [.], ack 35, win 2058, options [nop,nop,TS val 1306668629 ecr 561785392], length 0
ARP, Request who-has 192.168.31.253 tell 192.168.31.23, length 28
ARP, Reply 192.168.31.253 is-at 38:f9:d3:50:86:d2, length 28
TCP连接的关闭
目前 tcpdump 抓包得到的结果如下
IP 192.168.31.253.61413 > 192.168.31.23.9999: Flags [F.], seq 10, ack 35, win 2058, options [nop,nop,TS val 1306757580 ecr 561785392], length 0
IP 192.168.31.23.9999 > 192.168.31.253.61413: Flags [F.], seq 35, ack 11, win 510, options [nop,nop,TS val 561874980 ecr 1306757580], length 0
IP 192.168.31.253.61413 > 192.168.31.23.9999: Flags [.], ack 36, win 2058, options [nop,nop,TS val 1306757641 ecr 561874980], length 0
转化成时序图如下 这个图和我们理解的四次挥手有所不同,一般的四次挥手如下 一般理解的四次挥手为上图 报文段4-报文段7的部分
- 第四个报文包函FIN标志,因此是一个结束报文段,即要求关闭连接,结束报文和同步报文段一样也占据一个序号值。
- 报文段5确认结束报文段
- 报文段6发送结束报文段
- 报文段7确认
实际上,仅用于确认目的的确认报文段5是可以省略的,因为结束报文段6也携带了该确认信息。确认报文段5是否出现在连接断开过程中,取决于TCP的延迟确认特性,即这就可以解释自己抓包TCP断开时只有三次挥手。 延迟确认:服务器每次发送的确认报文段都包含它需要发送的应用程序数据,它不马上确认上次收到的数据,而是在一段延迟时间后查看本端是否有数据需要发送,如果有,则和确认消息一起发出。因为服务器对客户请求处理得很快,所以它发送去确认报文段的时候总是有数据一起发送,延迟确认可以减少TCP报文段的数量。
半关闭
TCP连接是全双工的,所以它允许两个方向的数据传输被独立关闭**。换言之,通信的一端可以发送结束报文给对方,告诉它本端已经完成了数据的发送,但允许继续接收来自对方的数据,直到对方也发送结束报文段以关闭连接。TCP连接的这种状态称为半关闭。** 上图就是一个半连接的示例,服务器和客户端应用程序判断对方是否已经关闭连接的方法是:read 系统调用返回0(收到结束报文段)。
TIME_WAIT状态
从上述的状态转换图来看,客户端在收到报文6后,没有立刻进入 CLSOED 状态,而是转移到TIME_WAIT 。在这个状态,客户端连接要等待一段长为 2MSL(Maximum Segment Life,报文最长生存时间)的时间,才能完全关闭。MSL 是 TCP 报文段在网络中的最大生存时间,标准文档 RFC 1122 的建议值是 2 min。Linux 系统中默认的 MSL 为60s,可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查询 TIME_WAIT 状态存在的原因有两点:
- 可靠地终止TCP连接:如果报文7丢失,那么服务器就会重新给客户的发送报文6,此时客户的就需要停在在TIME_WAIT状态来处理服务器发送的报文6,即重新发送报文7。否则,客户端将会以复位报文段回复服务器,服务器会认为这是一个错误, 因为它期望的是一个像TCP报文段7那样的确认报文段。
- 保证让迟来的 TCP报文段有足够的时间被是被并丢弃: 即如果有其他的程序也想要使用同样的ip和端口号,那么它一定要等 2MSL 的时间,这样就能保证之前程序还在网络传送中的报文已经被丢弃,因为已经过了 2MSL 时间,即新服务不会收到以前程序的报文。 如果想要立刻端口服用,也可以通过 socket 选项的 SO_REUSEADDR 参数来设置。
TCP超时重传
我们来看一下在异常网络状况下(开始出现超时或丢包),TCP如何控制数据传输以保证其承诺的可靠服务。
TCP模块为每个TCP报文段都维护一个重传定时器,该定时器在TCP报文段第一次被发送是启动。如果超时时间内未收到接收方的应答,TCP模块将重传TCP报文段并重置定时器。至于下次重传的超时时间如何选择,以及最多执行多少次重传,就是 TCP的重传策略。
两个相关参数:
/proc/sys/net/ipv4/tcp_retires1 指定在底层IP接管之前TCP最少执行的重传次数,默认值是3/proc/sys/net/ipv4/tcp_retires2 指定在连接放弃前TCP最多可以用之行的重传次数,默认值是15
TCP报文段的重传在发送超时之前也可以发生,即快速重传。
Reference
1.Transmission_Control_Protocol 2.《高性能linux服务器开发》
|