计算机网络
TCP三次握手四次挥手详情
TCP
面向连接(一对一)、可靠的(保证报文一定能到接收端)、基于字节流(消息被操作系统分成多个报文,接收方不知道消息边界无法读出有效的用户消息;报文有序目前一个报文没收到时候及时收到后面的报文,也不能扔给应用层处理;对重复报文自动丢弃)的传输层通信协议。
TCP连接
用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket(IP地址和端口号)、序列号(解决乱序问题)、窗口大小(流量控制)称为连接
唯一确定TCP连接
四元组:源地址(IP头部 通过IP协议发送报文给对方主机)、源端口(TCP头部 告诉TCP头部要把报文发给哪个进程)、目的地址、目的端口
TCP头格式
序列号:解决乱序问题(建立连接时候计算机生成的随机数作为初始值,通过SYN包传给接收端主机,发送一次数据就累加一次数据字节数的大小)。
确认应答号:解决丢包问题(下一次期望收到的数据序列号,发送端收到这个确认应答就可以人为这个序号以前的数据都被正常接收了)
控制位:ACK:为1时确认应答字段变为有效,TCP规定除了最初建立时的SYN包以外这个位必须 设置成1;
RST:为1时,TCP连接中出现异常必须强制断开连接;
SYN:为1时希望建立连接,在序列号字段进行序列号初始值设定;
FIN:为1时说明今后没数据在发送,希望断开连接。双方主机相互交换FIN为1的TCP段
TCP和UDP
①连接 TCP:面向连接的传输层协议,传输数据前先建立连接; UDP:即刻传,不建立连接
②服务对象 TCP:一对一两点服务;UDP:一对一一对多多对多的交互通信
③可靠性 TCP:可靠传输,数据无差错、不重复、无丢失、按序到;UDP:尽最大努力传输,不保证可靠
④拥塞控制、流量控制 TCP:有拥塞控制和流量控制机制,保证数据传输的安全性;UDP:没这个机制,即使网络拥堵,也不影响UDP发送速率
⑤传输方式 TCP:流式传输,没有边界,保证顺序和可靠;UDP:一个包一个包传,有边界,但可能会丢会乱;
⑥分片 TCP数据大于MSS(除IP和TCP头部后,一个网络包能容纳的TCP数据最大长度),在传输层分片,目标主机收到也同样在传输层组装,如果中途丢失分片,只需要重传这个分片;UDP:数据大于MTU(一个网络报的最大长度)在IP层分片,目标主机收到,在IP层完成组装,传给传输层。
适用场景:TCP:FTP文件传输、HTTP/HTTPS
UDP(随时发数据):包总量较少的通信(DNS、SNMP)、视频、音频等多媒体通信、广播通信
TCP三次握手
①一开始,客户端服务端都在CLOSE状态。服务端主动监听某个端口,处于LISTEN状态
②客户端随机初始化序列号(Client_isn),放在TCP首部序号字段,把SYN标志成1,把第一个SYN发送给服务端,表示向服务端发起连接,这个报文不包含应用层数据,之后客户端处于SYN-SENT状态。
③服务端收到客户端的SYN报文后,首先初始化自己的序列号(server_isn),把这个填到TCP首部序号字段里。然后把TCP首部的确认应答号字段填Client_isn+1,把SYN和ACK标成1,发给客户端,这个报文也不包含应用层数据,服务端处于SYN_RCVD状态。
④客户端收到报文,向服务端返回最后一个应答报文,首先把TCP首部ACK标为1,确认应答字段填server_isn+1,发报文给服务端,这次保温可以携带客户端到服务端的数据,客户端处于ESTABLISHED状态。
⑤服务端收到应答报文,进入ESTABLISHED状态。
【Linux系统中,查看TCP状态:netstat -napt】
三次握手原因
①阻止重复历史链接的初始化
网络拥堵情况下:旧报文(90)比新报文(100)更早到达服务端,服务端返回SYN+ACK报文给客户端,这条报文确认号是91;客户端收到后,自己期望的是101,返回RST;服务端收到RST,释放连接,后续新的SYN抵达,正常完成三次握手。
两次握手情况下:服务端收到SYN就进入ESTABLISHED,就能给对方发送数据了,但是客户端还没有进入ESTABLISHED状态,假如这次是历史连接,客户端判断为历史连接时会返回RST,服务端已经在连接状态,服务端向客户端发送数据前没有阻止掉历史连接,导致服务端建立了一个历史连接还白白发送了数据,浪费了服务端的资源。
②同步双方的初始序列号
服务端发送携带初始序列号的SYN报文时候,需要服务端回一个ACK应答报文,表示客户端的SYN报文已经被服务端接受了,当服务端发送初始序列号时候也需要一个应答,这样一来一回,双方初始序列号就能被可靠同步。【两次握手只能保证一方初始序列号被对方接收,没办法保证双方都能被确认接收】
③避免资源浪费
只有两次握手时候,服务端每收到一个SYN就建立一次连接,一旦客户端SYN在网络中阻塞,重复发送了这条报文,服务端收到请求就建立好多个冗余无效链接,资源浪费。
每次都要不同的初始化序列号
①防止历史报文被下一个相同四元组的连接接收
②安全性:防止黑客伪装的相同序列号TCP报文被对方接收
IP层会分片,为什么TCP层还需要MSS
IP层本身没有超时重传机制,如果当一个IP分片丢失,整个IP报文都得重传。由TCP负责超时重传
第一次握手丢失
客户端发出的SYN收不到对用SYN+ACK,出发超时重传,重传的SYN报文序列号都一样。重传次数由Linux系统的tcp_syn_retries参数控制(一般是5)。每次超时的时间是上一次的两倍。超过最大重传次数后还没回应,客户端自动断开。
第二次握手丢失
二握包含两个内容:SYN(服务端发起建立TCP连接的报文)+ACK(对第一次握手的确认)
这次丢失,客户端没收到响应,重传SYN;
服务端也没收到三次握手的响应,重传SYN-ACK,最大重传次数由tcp-synack-retries参数决定。
第三次握手丢失
服务端每收到确认报文,触发超时重传,SYN-ACK重发,直到收到第三次握手或者达到最大重传次数。
SYN攻击及其避免
SYN攻击:攻击者短时间伪造不同IP地址的SYN报文,服务端每收到一个SYN就进入SYN-RCVD状态,但是他发出去的SYN-ACK报文无法得到未知IP主机的ACK应答,久而久之占满服务端的半连接队列,服务端无法为用户正常服务。【打满服务端的半连接队列,这样之后收到的报文就会被抛弃】
半连接队列:SYN队列
全连接队列:Accept队列
正常流程:服务端收到客户端的SYN报文时,创建一个半连接对象,然后把他加入到内核的SYN队列,发送SYN+ACK后,等到收到ACK时,从SYN队列取出一个半连接对象,然后创建一个新对象放到Accept队列,应用通过调用accept()的socket接口,从Accept队里取出连接对象。
避免方式:
1、调大netdev_max_backlog(网卡接收速度大于内核处理速度时候,保存这些数据包的队列最大值参数)
2、增大半连接队列(三个参数:net/ipv4.tcp_max_syn_backlog/listen()函数中的backlog、net.core.somaxconn)
3、开启syncookies【不使用SYN队列的情况下建立连接,SYN队列满之后,后续再收到不会丢弃,而是根据算法,计算一个cookies值,把这个值放到第二次握手的序列号里,服务端收到客户端应答报文,先检查包的合法性,合法就把连接对象放到Accept队列,最后应用程序调用Accept()接口,从队列取出连接】
4、减少SYN-ACK重传次数:服务端受到SYN攻击时,有大量的SYN-RECV状态的TCP连接,处在这个状态的TCP会重传SYN-ACK,重传次数达到上限即断连
四次挥手
①客户端打算关闭连接,发送TCP首部FIN位置标志为1的报文,即FIN报文,客户端就进入FIN_WAIT_1的状态
②服务端收到,发送应答报文ACK,然后进入CLOSE_WAIT状态
③客户端收到SCK,进入FIN_WAIT_2状态
④服务端处理完数据,向客户端发送FIN报文,进入LAST_ACK状态
⑤客户端收到FIN,回一个ACK,进入TIME_WAIT状态
⑥服务端收到ACK,进入CLOSE状态,服务端完成连接关闭
⑦客户端经过2MSL时间后,自动进入CLOSE状态,至此完成连接关闭
注意:主动关闭连接的才有TIME_WAIT状态
为什么需要四次
关闭连接时,客户端向服务端发FIN,仅代表他不发了,但他还能收
服务端回应时,先回ACK,但是他这时候可能还有没处理完的数据,等他不用发的时候,才发送FIN同意关闭。
第一次丢
主动关闭方调用close()函数,向服务端发FIN报文,然后进入FIN_WAIT_1状态。如果这次挥手丢了,收不到被动方的ACK,触发超时重传(tcp_orphan_retries),超过重传次数,就不发FIN报文,等一段时间(上次超时时间的2倍),还没回应,就自动进入close状态。
第二次丢
ACK不重传,所以还是主动关闭方重传FIN报文,与第一次丢一模一样【调用close函数的关闭和shutdown函数的关闭:前者不能收发,后者只不能发,所以如果是用后者发起的关闭,没收到三握会进入死等】
第三次丢
没收到主动方的ACK,服务端重传三次挥手报文,达到重传次数,还没收到,自动断连
客户端是通过close函数关闭连接,FIN_WAIT_2有时长限制,所以如果在该时长下还没收到三次握手,客户端自动断连。
第四次丢
服务端重发FIN报文,直至超出最大重传次数
客户端进入TIME_WAIT状态,开启2MSL定时器,中途收到FIN就重置定时器,等待2MSL,自动断连。
2MSL时间原因
MSL:报文最大生存时间 TTL:IP数据可经过的最大路由数 MSL>=TTL
网络中可能存在来自发送方的数据包,当这些数据包被接受党处理后会向对方响应一来一回2MSL。允许报文丢失一次。
需要TIME_WAIT状态原因
① 防止历史连接中数据被后面相同四元组错误接收(足以让两个方向上数据包都被丢弃,使得原来连接的数据包在网络中自然消失,再出现的数据包一定是新连接产生的)
② 保证被动关闭方正确关闭(确保ACK被对方收到)
TIME_WAIT太多的危害
① 占用系统资源(服务端主动关闭) 服务端只监听一个端口
② 占用端口资源(客户端) 占满所有端口资源
服务器出现大量TIME_WAIT原因
主动关闭方才有的,说明服务器主动断开了很多TCP连接,情况有三:HTTP没有长连接;HTTP长连接超时;HTTP长连接请求数量上限 。不管哪一方禁用长连接,都由服务端主动关闭连接
服务器出现大量CLOSE_WAIT原因
被动关闭方才有的状态,且若被动关闭方没有调用close函数,无法发出FIN报文,就无法使得CLOSE_WAIT转变为LAST_ACK。
所以说明服务端程序没有用close函数关闭连接。通常都是代码的问题
【TCP服务端流程】
创建服务端socket,bind绑定端口、listen监听端口
服务端socket注册到epoll
epoll_wait等待连接到来,连接到来调用accep获取已连接的socket
将已连接的socke注册到epoll
epoll_wait等待事件发生
对方连接关闭时,我方调用close
如果已经建立连接,客户端突然出现故障
客户端主机发生宕机或断电,服务端一直不发送数据的话,永远无法感知客户端宕机这个事件
TCP保活机制,定义一个时间段,这个时间段内没有任何连接相关的活动,机制开始作用,隔一个时间就发送探测报文,连续发几个都没有响应救人位当前TCP连接已经死亡,错误信息上报至应用程序。【区别主机宕机和进程崩溃:进程崩溃后操作系统在回收进程资源时会发送FIN报文,主机宕机无法感知】
如果已经建立连接,但服务端进程崩溃怎么办
内核回收进程中所有TCP连接资源,内核会发送第一次回收FIN报文,后续回收也都在内核完成,不需要进场参与,所以及时进程退出,还是能和客户端完成四次挥手过程。