最近重新翻看了相关资料,结合工作上的问题对TCP相关知识重新做了个总结,对之前的记录做了修改和补充。
1. 定义
-
IP协议
-
IP 协议实际上是用来查找地址的,而它对应的层级也是网络层,也可以称之为网际互联层,区别不大。
-
TCP协议
-
TCP 协议是用来规范传输规则的,和IP 协议是不同的,而它对应的层级是传输层,也就是IP去寻找地址,把所有的传输任务都交给TCP,而TCP相当于一个快递员的角色。
2. 三次握手
-
- seq:sequence number 的缩写,即序号。seq表示的则是自己传递的序号,TCP在传输的时候,其中每一个字节,都会有一个序号,发送数据的时候,会把第一个数据的第一个序号发送给对方,就是看到的第一步,而接收的这一方面,会按照这个序号来检查是否是一个连接完整的数据,是完整的话就继续,不完整的话就重新发送。保证数据完整性不被破坏。
- ack: 注意是小写的 acknoledgement number的缩写 表示确认号 ,要和ACK(确认位)区分 ,接收端用它来给发送端返回接受消息的数据信息,而这时候,它的值就是表明我想接收下一个数据包了。而这个值就是下一个数据包的开始的序号,而这个ack所代表的值的序号前面的数据都已经接受成功了。ack是对seq的确认,所以这里的ack其实是seq+1
- ACK确认位: 只有当ACK=1的时候ack才会起到作用,而在我们第一次请求的时候,是没有需要确认的接受的数据的,这个时候ACK=0,正常通信下ACK=1.
- SYN: 同步位 ,作用是用于建立连接时同步序号,刚建立连接的时候,ACK=0这时ack就不起作用,当接收端收到SYN=1的报文的时候,会将ack设置为接收到的seq+1的值,这时候的ack的值就是根据SYN来设置的。同步位的时候seq不消耗序号
- FIN:终止位,在本图没有体现,在四次挥手的时候能完全体现出来。而它是用来在数据传输都完成之后来释放连接的。
- 上图解析:
- 第一次握手(SYN=1,seq=x)
- 客户端发送一个TCP的SYN标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(seq)中。发送完毕后,客户端进入SYN_SEND状态 TCP 发送SYN标志位的时候seq 是不携带数据的,但是要消耗一个序号
- 第二次握手(ack=x+1,ACK=1,seq=y,SYN=1)
- 服务器发回确认包(ACK)应答。即SYN和ACK标志位都为1。服务器端选择自己ISN序列号,放到seq域中,同时将确认序号ack设置为客户的ISN加1,即x+1。发送完毕后,服务端进入SYN_RCVD状态
- 第三次握手(ack=y+1,ACK=1,seq=x+1)
- 客户端再次发送确认包(ACK),SYN标志位为0,ACK标志位为1,并且把服务器发来的ACK序号字段+1,放在ack中返回。发送完毕后,客户端进入ESTABLISHED状态,当服务端接收到这个包时也会进入ESTABLISHED状态,TCP握手结束。这里的ack 是针对第二次握手的seq=y 的确认,seq=x+1 是对第一次发送seq=x的递增,因为第一次握手的时候SYN消耗了一个需要,第三次如果不携带数据的话就是x+1
3. 四次挥手
- 流程解读:
- 第一次挥手(FIN=1,ACK=1,seq=u ,ack=v)
- 假如客户端想要关闭连接,客户端发送一个FIN标志位置为1的包,表示自己已经没有数据可以发送了,但是任然可以接受数据,发送完毕后,客户端进入FIN_WAIT_1状态
- 第二次挥手(ACK=1,seq=v,ack=u+1)
- 服务器端接收到客户端发送的FIN包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但是服务端还没有准备好关闭连接。发送完毕后,服务器端进入CLOSE_WAIT状态,客户端收到这个确认包后,进入FIN_WAIT_2状态,等待服务器关闭连接。 (seq等于客户端上次确认的需要即v)
- 第三次挥手(FIN=1,ACK=1,seq=w,ack=u+1)
- 服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN值为1。发送完毕后,服务器端进入LAST_ACK状态 ,等待来自客户端的最后一个ACK。
- 第四次挥手(ACK=1,seq=u+1,ack=w+1)
- 客户端收到服务端的关闭请求时,发送一个确认包,并且进入TIME_WAIT状态,等待可能出现的要求重传的ACK包。
- 服务器端接收到这个包后,关闭连接,进入CLOSED状态
- 客户端等待了某个固定时间(两个最大段生命周期,2MSL,2Maximum Segment Lifetime,默认1MSL2分钟)之后,没有收到服务器端的ACK,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入CLOSED状态。两次后会重传直到超时。如果多了会有大量半链接阻塞队列。
4. 为什么连接三次握手,而断开是四次挥手呢
- 这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示客户端不再发送数据,但是还能接受数据,服务端是否现在关闭数据发送通道需要上层应用决定,因此ACK和FIN一般分开发送。
5. 服务器保持了大量TIME_WAIT状态
-
原因
-
为什么?
- 防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
- 可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。
-
解决方案
-
编辑文件/etc/sysctl.conf,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
6. 服务器保持了大量CLOSE_WAIT状态
7. Connection reset
- 一般访问量比较大的web服务器日志中或多或少能看到一些Connection reset 的报错。举个例子,A服务器访问B服务器的服务,B服务器由于异常等原因关闭了连接,A服务还在向B服务这个连接中读取数据,由于java的Socket.close()语义和TCP的FIN标志语义不一样,不是表示不再发送数据,而是标示不再接收数据,会向A服务器发送RST标志,A服务器就会报出Connection reset 。
8. Connection reset by peer
- 和上面相反,B服务器向关闭了连接的A服务器写数据就会导致B服务器报这个错,常见就是客户端有请求超时限制,服务端的接口比较慢超过了这个限制,客户端超时后关闭了连接,服务端还在向客户端写数据就会触发这个报错。
|