TCP连接管理、可靠传输机制、提升传输性能机制
TCP协议格式
TCP全称为“传输控制协议”,是传输层一种重要的协议
TCP协议段格式
十六位源端口号、十六位目的端口号、三十二位序号、三十二位确认序号、四位首部长度、保留六位、十六位窗口指针大小、十六位校验和、十六位紧急指针、数据。
-
源端口号/目的端口号,记录了数据的传递方向以及目标
-
4位TCP报头长度:表示该条数据有多少个4字节,所以TCP最大长度为15*4=60
-
32为***和32位确认***
-
6位标志位:
URG:紧急指针是否有效
ACK:确认号是否有效
PSH:提示接收端应用程序立即从tcp缓冲区读走
RST:对方要求重新建立连接,我们把携带RST标示的称为复位报文段
SYN:请求建立连接,;我们把携带SYN标识符的称为同步报文段
FIN:通知对方要关闭连接,我们称携带FIN标识的为结束报文段 -
十六位窗口号:滑动窗口的大小
-
十六位校验和:发送端填充,CRC校验,接受端校验和不光包含TCP首部,也包含数据部分。
-
十六位紧急指针:标识哪部分数据是紧急数据
-
40字节头部选项
TCP三次握手建立连接过程
服务器状态转化
[CLOSED -> LISTEN] 服务器端调用用listen后进入入LISTEN状态, 等待客户端连接;
[LISTEN -> SYN_RCVD] 一一旦监听到连接请求(同步报文文段), 就将该连接放入入内核等待队列中, 并向
客户端发送SYN确认报文文.
[SYN_RCVD -> ESTABLISHED] 服务端一一旦收到客户端的确认报文文, 就进入入ESTABLISHED状态,
可以进行行读写数据了.
[ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用用close), 服务器会收到结束报文文段,
服务器返回确认报文文段并进入入CLOSE_WAIT;
[CLOSE_WAIT -> LAST_ACK] 进入入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的
数据); 当服务器真正调用用close关闭连接时, 会向客户端发送FIN, 此时服务器进入入LAST_ACK状态,
等待最后一一个ACK到来(这个ACK是客户端确认收到了FIN)
[LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接.
客户端状态转化
[CLOSED -> SYN_SENT] 客户端调用用connect, 发送同步报文文段;[SYN_SENT -> ESTABLISHED] connect调用用成功, 则进入入ESTABLISHED状态, 开始读写数据;
[ESTABLISHED -> FIN_WAIT_1] 客户端主动调用用close时, 向服务器发送结束报文文段, 同时进入入
FIN_WAIT_1;
[FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文文段的确认, 则进入入FIN_WAIT_2, 开始
等待服务器的结束报文文段;
[FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文文段, 进入入TIME_WAIT, 并发出
LAST_ACK;
[TIME_WAIT -> CLOSED] 客户端要等待一一个2MSL(Max Segment Life, 报文最大生存时间)的时间,
才会进入入CLOSED状态
关于等待两个MSL时间的解释
MSL时间,即报文最大生存时间,在网络上的报文都是有存活时间的,当报文在网络上传输的时间超过了这个报文的存活时间,报文就会在网络中被丢弃。当客户端发送完一个ACK包给服务器之后变成TIME_WAIT状态,这时候可能ACK并没有发送到服务器端,这时候经历的时间是一个MSL时间,ACK丢弃在网络之中,然后有超时重传机制的服务器会再次发送一个FIN包,这样又会经历一个MSL时间。为了防止下一个客户端被之前客户端的报文所影响,所以必须等待两个MSL时间。(客户端和服务端的情况可能发生变化,在特殊情况下)
解决TIME_WAIT状态引起的bind失败的方法
采用setsockopt()来设置socket描述符的SOL_REUSEADDR
为1
在 socket 与 bind 之间插入如下代码:
int opt = 1;
setsockopt(lis_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
TCP传输的特点:
- 有连接
- 可靠传输
- 面向字节流
TCP保证可靠传输的机制
- 确认应答机制:对每次发送的数据报需要确认到达
- 超时重传机制:对没收到的数据(即未收到确认到达的数据报)进行重新发送
- 32位序号与32位确认序号:保证数据传输的有序性
确认应答(ACK)机制
每一个ACK都会有对应的确认序号,意思是告诉发送者已经收到了那些数据,下一条数据你从哪里开始发
超时重传机制
超时重传是TCP协议保证数据可靠性的另一个重要机制,其原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的ACK报文,那么就重新发送数据,直到发送成功为止。
超时重传时间RTO是根据往返时间RTT(Karn算法)推导出来的一个合适的时间。
Linux中最大超时时间以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
如果重发一一次之后, 仍然得不到应答, 等待 2×500ms 后再进行行重传.
如果仍然得不到应答, 等待 4×500ms 进行行重传. 依次类推, 以指数形式递增.
累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接
TCP为了保证可靠传输采用了一系列机制,但是这些机制背后带来的是性能的下降,所以有设置了一系列的机制来提高传输性能。
提高传输性能的机制
- 滑动窗口机制与快速重传机制
- 流量控制机制
- 拥塞控制机制
- 延迟应答机制
- 捎带应答机制
滑动窗口机制
当采取传统的发送一条数据等待回复,收到回复再发送一条数据的机制来看,虽然保证了可靠传输的条件之一,但是效率十分底下,所以采用滑动窗口机制来批量发送数据
窗口大小是指无需等待应答二可以直接继续发送的数据的最大值,先假设窗口大小为4段(4000字节),发送前四段的时候无需等待ACK回复,直接发送。收到第一个ACK之后窗口向后滑动,继续发送第五个段的数据,以此类推。操作系统内核需要维护滑动窗口,需要开辟一段缓存区,叫做发送缓冲区,来记录当前哪些数据还没有应答,应答的数据在缓冲区就会被删除。窗口的大小也就是网络吞吐率的大小。
快速重传机制
在滑动窗口机制中有两种比较特殊的情况:
1.数据传输来,服务端返回ACK的时候在网络中丢失。
2.数据传输的途中直接丢失。
面对情况一:
ACK丢失之后可以通过后续的ACK来确定,即第二段的ACK丢失,但是第三段的ACK已经被客户端所接收到,默认第二段报文也被送达到了。
面对情况二:
数据报在丢失之后,连续发送三次丢失报文的信息
如:
第一段报文丢失,而此时已经发送到了第四段的报文,再次发送的时候会一直发送第一段的请求ACK,知道第一段被重新发送才会回复第四段的的ACK。
流量控制
接收端的数据处理速度是有限的额,如果发送的太快,会导致接受缓冲区被塞满,接下来到达的报文都会被丢弃,再进行重传,降低了性能。
TCP通过流量控制来决定发送端的数据,这个机制就是 流量控制机制
即每次进行ACK回复的时候都会将接收端的大小放入TCP首部的窗口大小字段当中,通过ACK告诉发送端
窗口字段越大代表网络吞吐量越高,当缓冲区还剩余很大的空间的时候,窗口大小字段的值会比较大,而当缓冲区快满的时候,就会返回一个较小的值,来实现动态控制流量。如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口
拥塞控制
虽然有滑动窗口机制来提高发送数据量,但是在起始阶段,也就是不知网络条件的状况下就开始大量传输数据就有可能造成丢包和大量重传,所以需要引入一个慢启动的装置来摸清当前网络状态。
一开始定义阻塞窗口的大小十分小,每次成功传输都以指数级增长,到达窗口的阈值之后会放缓增长速度,当出现丢包状况时,窗口的值立马降为初始值,而阈值变为原来的一半,这个机制能有效的保证在不同网络状况下的传输都变得高效
延迟应答
接收到的主机立刻返回ACK应答,这时候可能窗口会比较小,所以可以为了提高网络的吞吐量的情况下适当的延迟应答,让返回的窗口大小尽可能的变大。
具体的数量和超时时间, 依操作系统不同也有差异; 超时时间取200ms;
捎带应答
全双工服务(Full Duplex Communication)
一个TCP连接允许数据在任何一个方向流动,并允许任何一个应用程序在任何时刻发送数据。即当两个进程 A 和 B 建立连接后,任何一方均能发送数据给另一方。当分组从 A 发往B 时,可携带对 B 发来数据的确认。同理,当分组从 B 发往 A 时,可携带对 A 发来数据的确认。即采用捎带确认的机制。
面向字节流
创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
调用write时, 数据会先写入发送缓冲区中;
如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区然后应用用程序可以调用用read从接收缓冲区拿数据;
另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据,也可以写数据. 这个概念叫做 全双工由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如:
写100个字节数据时, 可以调用一次write写100个字节, 也可以调用用100次write, 每次写一个字节;读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;
粘包问题
面向字节流的报文无法区分数据与数据之间的界限,所以要解决粘包问题,可以从以下几个方法中来看:
- 规定包的长度
- 在包的头部加上该包的长度
- 确定包与包之间的分隔符
基于TCP的应用层知名协议
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP