TCP协议是什么
TCP协议是面向连接的、可靠的、基于字节流的的传输层协议,工作在不可靠的IP协议之上,确保接收端的数据与发送端的数据一致
TCP与UDP的一些区别
- 连接
TCP需要建立连接传输数据,UDP不需要,即刻传输 - 可靠性
TCP保证数据的可靠性,保证数据不丢失,无差错,不重复,UDP不保证但可以基于UDP实现一个可靠传输如QUIC协议 - 拥塞控制与流量控制
TCP有拥塞控制与流量控制,UDP没有,网络拥堵不影响UDP的发送速率 - 传输方式
TCP是面向字节流的没有边界,UDP是面向报文的有边界,因而TCP有一个粘包问题,需要用户程序解决,常见的解决方法是在报文头部加入报文长度
但TCP与UDP可以同时绑定到一个端口,并不会引起冲突,对应的包会被分别发送到TCP与UDP的处理程序中
TCP的三次握手
服务器进入LISTEN状态,等待客户端连接,客户端随机选择一个初始序列号ISN(Initial Sequence Number)并发送SYN报文给服务器,进入SYN-SENT状态
如果此报文丢失,客户端会重发SYN报文,服务器收到后会重新发送SYN+ACK报文服务器收到SYN报文后,返回SYN+ACK报文给客户端,将ISN+1填入确认序列字段,进入SYN-RECEIVED状态
如果此报文丢失,服务器会重发SYN+ACK报文,同时客户端会以为SYN报文丢失,重发SYN报文SYN攻击与防范
攻击者发送大量的SYN报文给服务器,服务器会为每一个SYN报文分配一个资源,当资源耗尽后服务器无法再接受新的连接,造成客户端的SYN报文会被丢弃无法建立连接,这种攻击叫做SYN攻击
防范方法:
- 增大半连接队列长度与全连接队列长度
- 启用SYN Cookie,服务器不会为每一个SYN报文分配资源,而是根据SYN报文计算一个cookie,客户端收到SYN+ACK报文后会将cookie发送给服务器,服务器根据cookie计算出ISN,然后建立连接
- 减少SYN+ACK的重传次数,避免被攻击者的SYN报文占用资源
TCP连接在内核表现为结构体
TCP连接在linux内核中表现为一个socket结构体,包含了TCP的状态信息,即使拔掉网线内核也不会更改该结构体的内容,TCP状态不会改变,拔出网线短时间内插回大概率可以继续使用该连接,如果长期不插回网线则可能触发对方的保活机制导致连接断开,如果双方都没有开启保活机制,但有数据传输则会触发过多重传导致断开TCP连接
- 客户端回应最后一个ACK包,将server_isn+1填入确认序列字段,进入ESTABLISHED状态,服务器收到后也进入ESTABLISHED状态
客户端不会重发ACK报文,因为当客户端接收到SYN+ACK后客户端已经建立了连接,但服务端会重传SYN+ACK报文,客户端会产生新的ACK报文发送
前两次握手不携带数据,第三次握手携带数据,客户端可以在发送第三次握手报文后马上发送数据报文,这两个报文的确认序列字段都是ISN+1,所以握手报文丢失后如果数据报文抵达也会建立连接
三次握手的主要目的是为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误
如果只用两次握手,可能会造成资源的浪费与错误,下面是一种情形:
客户端发送请求报文后宕机然后重启重新发送新的的请求报文,第一个SYN报文比新的SYN报文先抵达服务器,如果只有两次握手,服务器收到SYN后就进入ESTABLISHED状态,发送数据,而这个连接已经失效了,白白发送了数据
总结:第三次握手给客户端一个确认机会,防止历史连接的报文段突然又传送到了服务端建立无效连接
为什么TCP初始化序列号是随机的
- 防止历史报文被下一个连接重用(一个TCP可以由源地址+源端口+目的地址+目的端口唯一确定)
如果序列号固定,那么历史连接的序列号可能会被下一个连接重用,导致数据错乱 - 安全性,防止黑客通过猜测序列号来伪造TCP报文
利用RST报文关闭TCP连接
主机可以利用killcx工具发送RST报文给服务器,服务器收到RST报文后会立即关闭连接,这样可以避免等待TIME-WAIT状态的时间
killcx工具的原理是向服务器发送一个SYN报文,服务器会根据Seq判断出这个连接不是之前的连接,发送正确的确认序列回来,killcx根据此报文伪造一个RST报文发送给服务器,服务器收到RST报文后会立即关闭连接
为什么TCP要有MSS,不使用IP层的MTU
如果TCP将整个报文交给IP层,当传输的报文很大被IP层分片时,如果其中一个分片丢失,那么整个报文都要重传,这样会造成很大的浪费
TCP四次挥手
双方都可以主动断开连接,下面以客户端主动断开连接为例
客户端发送FIN报文给服务器,进入FIN-WAIT-1状态
此报文丢失客户端会重传,超过最大重传次数则会直接进入CLOSED状态
如果是进程崩溃内核可以感知并主动发送FIN报文,如果是主机崩溃则只能等服务端的保活机制或重传机制来断开连接服务器收到FIN报文后,发送ACK报文给客户端,进入CLOSE-WAIT状态,客户端收到ACK报文后进入FIN-WAIT-2状态
由于ACK报文不会重传,所以客户端会一直重传FIN报文,服务器再生成新的ACK报文发送给客户端,客户端发起FIN报文给到服务器的信息是客户端关闭了发送数据的通道,但是服务器还可以发送数据给客户端接收服务器发送FIN报文给客户端,进入LAST-ACK状态
此报文丢失服务器会重传,超过最大重传次数则会直接进入CLOSED状态,同时客户端长时间收不到FIN报文也会自动进入CLOSED状态客户端收到FIN报文后,发送ACK报文给服务器,进入TIME-WAIT状态,等待2MSL(Max Segment Lifttime)后进入CLOSED状态,服务器收到ACK报文后进入CLOSED状态
当客户端发送ACK报文后开始计时,如果服务器没有收到ACK报文,服务器会重传FIN报文,客户端收到FIN报文后会重新发送ACK报文重新计时
进入TIME-WAIT状态的主要两个目的:
防止主动方历史数据被后面相同四元组的连接重用,如果没有TIME-WAIT,马上相同的四元组又建立了一个新连接,之前连接的包的序列如果恰好在接收窗口中就会引起数据错乱。所以TIME-WAIT的作用就是等待足够长时间处理历史数据确保不会与新连接的数据混淆
保证被动连接的一方能被正确关闭,如果ACK报文丢失,服务器会重传FIN报文,客户端收到FIN报文后会重新发送ACK报文帮助服务器正确关闭连接
TIME-WAIT状态的优化
- 复用TIME-WAIT状态的连接并引入时间戳
- 设定TIME-WAIT连接数目,超过数目则将后面的连接直接关闭
- 设置socket选项,调用close后立即关闭连接,不进入TIME-WAIT状态
服务器出现大量TIME-WAIT状态连接的原因
- HTTP没有使用长连接,服务器每处理一个请求就关闭连接
- HTTP长连接超时,服务器主动关闭连接
- HTTP长连接请求数量过多,一些服务器后端在一个长连接的请求参数过多时会关闭这个连接
TCP重传机制
- 超时重传,由时间驱动,发送数据时启动一个定时器,超过时间没有收到ACK报文则重传报文,这里涉及一个RTO的计算
- 快速重传,由数据驱动,假设发送方发送了1-6共6个数据,其中1到达之后3-5比2先到达,接收方就会连续发送3个2的ack报文,此时重传报文2
此方式解决了超时时间的但无法确定重传时需要重传哪些报文 - SACK,选择性重传,在TCP头部加入一个SACK段,将已接收的信息发送给发送方,使发送方只重传丢失的数据
- D-SACK,使用SACK告诉发送方哪些数据重复接收了,可以解决ACK包丢失和网络延迟,让发送方知道数据包是丢失了还是延迟
TCP滑动窗口
为了保证效率,TCP引入了滑动窗口机制,窗口大小即是无需等待ACK报文而可以继续发送的数据最大值,窗口大小由接收方通告给发送方(TCP头部当中的window字段),发送方根据接收方的通告调整窗口大小,窗口大小的调整可以根据网络拥塞情况调整,这样可以保证网络的稳定性。
TCP窗口关闭
当接收方的缓冲区满之后就会发送一个0窗口通知给发送方,此时发送方会停止发送数据,等待接收方的缓冲区有空间后再发送数据,但发送方发送的新的窗口的报文如果丢失接收方则会一直陷入等待状态,所以TCP引入了一个持久定时器,当发送方发送了一个0窗口通知后,发送方会启动一个持久定时器,超过时间后发送一个窗口探测报文给接收方,接收方收到后会回复一个窗口大小,发送方收到后会重新发送数据,若仍然为0则重设定时器。
糊涂窗口综合症
如果接收方太忙来不及读取窗口接收数据就会导致发送方的发送窗口越来越小,最后接收方腾出几个字节空间发送方就会马上发送几个字节数据给接收方,而一个TCP报文的头部大小加上IP就有40字节,会使得传输效率极低
导致该问题的两个原因:
- 接收方通知小窗口
- 发送方发送小数据
解决方法: - 当接收方的窗口小于某个值时直接发送一个0窗口通知,阻止发送方发送小数据
- 发送方使用Nagle算法,将小数据合并成一个大数据发送
但有些情况下Nagle算法会导致延迟,所以可以使用TCP_NODELAY选项关闭Nagle算法,比如telnet协议就需要频繁的发送小数据
TCP拥塞控制
流量控制是发送方与接收方的协调,拥塞控制是发送方与网络的协调,防止发送方的数据填满整个网络
为了实现拥塞控制,TCP引入了拥塞窗口,该窗口根据网络拥塞情况变化,最终的发送窗口大小是拥塞窗口和接收窗口的最小值
拥塞控制的四个算法
- 慢启动
每当发送方收到一个ACK报文就会将拥塞窗口大小加倍,直到达到一个阈值,然后进入拥塞避免状态 - 拥塞避免
将拥塞窗口大小线性增长,直到网络拥塞触发超时重传,进入拥塞发送状态 - 拥塞发生
- 超时重传
将sstresh设置为cwnd/2,cwnd设置为1(初始值),然后进入慢启动状态 - 快速重传
触发快速重传TCP认为网络的拥堵不是非常严重,只是丢失了部分数据,将cwnd设置为cwnd/2,ssthresh设置为cwnd,进入快速恢复状态
- 快速恢复
将cwnd设置为ssthresh+3,表示收到了三个重复的ACK报文,然后每收到一个重复的ACK报文cwnd加1(这里增加cwns是为了快速得到新的确认解决根本问题),如果收到新的ACK报文则将cwnd设置为ssthresh,进入拥塞避免状态
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2128099421@qq.com