TCP协议:传输控制协议,是一个有链接,可靠的,面向字节流的传输层协议。
TCP三次握手:
1.怎么三次握手?
从上面的两幅图中我们可以看出:
第一次握手:SYN = 1, seq = x
客户端发送一个TCP的SYN标准位为1的包,知名客户端打算连接的服务器的端口(请求同步),并选择序号seq = x 表明传送数据时的第一个数据字节 的序号是x。seq是随机值。
发送完毕,客户端进入SYN_SEND状态。
第二次握手:SYN = 1, ACK = 1, seq = y, ack = x + 1
服务器的TCP接收到链接请求后,如果同意,责罚四栋确认(ACK)包来进行应答。所以SYN,ACK均为1.服务器端选择自己的ISN序列号y(随机值),放到seq中,同时将将确认序号ack设置为客户的ISN+1,即为x + 1。表明下一次希望收到的数据从x+ 1开始。
发送完毕,服务器端进入SYN_RCVD状态
第三次握手:ACK = 1, ack = y + 1, seq = x + 1
客户端收到此报文段后再次发送确认包(ACK), SYN标志位为0,ACK标志位为1,并且把发送来的ACK序列号字段+1放在ack发送给对方,并且向对端发送请求的数据seq = x + 1
发送完毕,客户端进入ESTABLISHED状态,当服务器端接收到这个确认包时,也进入ESTABLISHED状态。
从打电话的例子可以通俗的理解我们的三次握手的过程:我跟张三在信号不好的情况下打电话(张三类比为服务器,我类比为客户端)
我: 张三,我是CZF,能听到吗?
张三: 我听到你是CZF了(确认),我是张三,你能听到吗?
我: 我听到你是张三了。
从上面的对话可以看出来,对话的任何一步少了,都会导致通信发生错误。
2.为什么是三次握手,为什么不是两次或者四次?
为了防止已经失效的链接请求报文段忽然有传送到了服务端,因而产生错误或者说是为了解决网络中存在延迟的重复分组的问题。
简单的理解就是:当存在这样一种情况时两次握手就会出现错误。客户端向服务器发送了一个连接请求,但是这个请求延时到达(并非丢失)了,那么服务器端就会向客户端返回响应,如果是两次握手,那么这个新的连接就建立了,但是这个响应到达客户端,客户端并不会理睬,而服务器却还在等待客户端发送数据,这样就造成了服务器端资源的浪费。
为什么不是四次也很好理解。如果三次就能够确定正常连接,就没有必要在进行确认,来浪费资源了。
在套接字函数介绍一文中,listen()函数展示了内核维护的两个队列:
半连接队列:
该队列为每个客户端的SYN包开设一个条目,该条目表明服务器已经收到了SYN包,并向客户端发出确认,正在等待客户的确认包。这些条目所识别的链接在服务器处于SYN_RCVD状态,当服务器收到客户端的确认包(三次握手成功)或者
本条目超时的时候,才删除这个队列中的条目。
半连接时间:服务器从接受到SYN包到确认这个包无效的最长时间。这个时间值是所有重传请求包的最长等待时间总和。
有时我们也称半连接存活时间为Timeout时间、SYN_RCVD时间。
完全连接队列:
该队列为每个已经完成三次握手的客户开设一个条目,默认情况下SOMAXCONN定义了这个队列的大小为128.
正是因为TCP三次握手的机制,就产生了一种针对三次握手的黑客攻击方式 --- SYN泛洪攻击,简单却非常有效。
四次挥手:
1.怎么四次挥手?
结合上面几张图片我们就可以总结出TCP的四次挥手过程:
第一次挥手(客户端):FIN = 1, seq = u
客户端发起关闭连接的请求,发送FIN标志位为1的包,表示自己已经没有数据可以发送了,但是仍然可以接收数据,因为有可能服务器还有数据要发送,所以发送自己的序列号seq = u,等待服务器确认
发送完毕,客户端进入FIN_WAIT_1状态
第二次挥手(服务器):ACK = 1, seq = v, ack = u + 1
服务器确认客户端的FIN包,b并且发送确认包,以及自己的序列号seq = v, 并对之前的客户端的u进行确认。,表明自己已经接收到了客户端的断开连接请求,但是自己还没有准备好断开连接,因为可能还有数据要发送。
发送完毕,服务器端进入CLOSE_WAIT状态,客户端接收到服务器的确认包时,进人FIN_WAIT_2状态,等待服务器关闭连接。
第三次挥手(服务器):FIN = 1, ACK = 1, seq = w, ack = u + 1
服务器已经没有要想客户端发送的数据,应用进程释放TCP链接,表明服务器段准备断开连接,向客户端发送断开链接请求FIN置为1的包.
发送完毕,服务器进人LAST_ACK,等待来自客户端最后一个ACK.
第四次挥手(客户端):ACK = 1, seq = u + 1, ack = w + 1
客户端收到服务器的关闭请求,发送一个确认包(ACK = 1,ack = w + 1, seq = u + 1),并进入TIME_WAIT状态,等待可能出现的要求重传的ACK包。服务器端接受到这个确认包后,关闭连接进人CLOSED状态
客户端等待某个固定定时间(两个最大生命周期,2MSL)之后,没有收到服务器端的ACK认为服务器端已经正常关闭了连接,于是自己也关闭连接,进入CLOSED.
为什么第三次挥手要发送seq和ack?
因为 TCP 是可靠的全双工传输,所以需要确保客户端的链接关闭正确,并且防止传送的数据包在网络中延迟出现的错误,如果后面又使用同样的端口建立了一个 TCP 链接而且现在要释放(完成了两次挥手),刚才延迟的包现在到了,这时也许服务器还有数据要发送,但是客户端收到延迟的包,就直接确认返回ACK,从而出现非正常关闭。
四次挥手通过挂断电话的的方式通俗的理解为:
我: 张三,我要说的话已经说完了,我要挂断电话了。
张三: 好的,我知道你话说完了,我还有几句话要跟你说,balabala.....
张三: 你的话说完了,我的话也说完了,我也要挂断电话了。
我: 好的张三,我知道你要挂断电话了,你挂断电话吧。
.......一段时间后.....(2MSL)
我: (OS:张三确实没有话要说了)挂断电话。
2.为什么四次挥手?
因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
有时候也不一定是四次挥手,可能第二次和第三次挥手合并为一次,即达到三次挥手。
3.理解TIME_WAIT状态:又称为2MSL状态。
TIME_WAIT一般持续时间为4min,但现实通常为1min,2min。
CenterOS7下为60s:
它的作用是重传最后一个ack报文,确保对方可以收到。因为如果对方没有收到ack的话,会重传fin报文,处于time_wait状态的套接字会立即向对方重发ack报文。同时在这段时间内,该链接在对话期间于网际路由上产生的残留报文(延时到达)传过来时,都会被立即丢弃掉。4分钟的时间足以使得这些残留报文彻底消逝。不然当新的端口被重复利用时,这些残留报文可能会干扰新的链接。
TIME_WAIT的影响:
1.当某个端口处于TIME_WAIT时,这个端口将不能被使用。
2.如果某个端口处于TIME_WAIT时意味着这个TCP链接没有完全的断开,那么bind这个端口就会失败。如果是服务器的话,当你在服务器有客户端链接时,将服务器进程干掉,那么服务器bind的端口就进入了TIME_WAIT状态,服务器将无法在2MSL内启动。
3.服务器需要处理大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很多数量的客户端来请求).
这个时候如果由服务器端主动关闭连接(例如某些客户端不活跃, 就需要被服务器端主动清理掉), 就会产生大量TIME_WAIT连接.
由于我们的请求量很大, 就可能导致TIME_WAIT的连接数很多, 导致服务器的端口不够用, 无法处理新的连接。
4.使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符。即在服务器代码中的socket()和bind()之间插入以下代码片段
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
TIME_WAIT为什么是2MSL?
MSL是TCP报文的最大生存时间,因此TIME_WAIT持续存在2MSL的话,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失,同时也是在理论上保证最后一个报文可靠到达。(假设最后一个ACK丢失, 那么服务器会再重发一个FIN. 这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK)。