TCP
三次握手、四次握手
建立连接时需要进行三次握手:
- 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认。SYN:同步序列编号(Synchronize Sequence Numbers)
- 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
ACK=1表示消息收到了,ack表示下一个数据包的序号
释放连接时需要进行4次握手:
- 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
- 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态(持续2MSL,Maximum Segment Lifetime),接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
除去这种单方面发起关闭连接的情况,双方还可以同时发起关闭,那流程是这样的:
疑问
- 建立连接为什么需要3次握手,2次不行吗?
若建立连接只需两次握手,客户端并没有太大的变化,仍然需要获得服务端的应答后才进入ESTABLISHED状态,而服务端在收到连接请求后就进入ESTABLISHED状态。此时如果网络拥塞,客户端发送的连接请求迟迟到不了服务端,客户端便超时重发请求,如果服务端正确接收并确认应答,双方便开始通信,通信结束后释放连接。此时,如果那个失效的连接请求抵达了服务端,由于只有两次握手,服务端收到请求就会进入ESTABLISHED状态,等待发送数据或主动发送数据。但此时的客户端早已进入CLOSED状态,服务端将会一直等待下去,这样浪费服务端连接资源。
- 为什么建立连接3次握手而关闭连接需要4次?
TCP是双向的,所以需要在两个方向分别关闭,每个方向的关闭又需要请求和确认,所以一共就4次。
-
TCP四次挥手客户端关闭链接为什么要等待2倍MSL(Maximum Segment Lifetime)?
1) 如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。 2) 如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。
-
Linux TCP状态TIME_WAIT 过多怎么处理?
短连接
建立TCP连接,发送完数据之后就断开连接,HTTP服务一般使用短链接,HTTP1.0仅支持短连接,HTTP1.1支持keep-alive。
长连接
多用于读写频繁,且连接数量不大的情况。在长连接中一般是没有条件能够判断读写什么时候结束,所以必须要加长度报文头。读函数先是读取报文头的长度,再根据这个长度去读相应长度的报文。
TCP keep-alive保活机制
TCP的保活机制
TCP中的保活机制是一个可选项,并不是必须的, 默认是关闭的。通过Linux配置或者应用程序中的socket连接参数可以打开。
Netty中打开保活机制的方式为
bootstrap.option(ChannelOption.TCP_NODELAY, true)
linux系统级别设置TCP保活机制的方式为修改
/etc/sysctl.conf
文件# /etc/sysctl.conf文件添加配置 net.ipv4.tcp_keepalive_intvl = 3 net.ipv4.tcp_keepalive_probes = 2 net.ipv4.tcp_keepalive_time = 6
主要用在服务器端,用于检测已建立TCP链接的客户端的状态,防止因客户端崩溃或者客户端网络不可达,而服务器端一直保持该TCP链接,占用服务器端的大量资源(因为Linux系统中可以创建的总TCP链接数是有限制的)。 保活机制原理:服务器端设置TCP保活机制的保活时间keepIdle(linux默认是2小时),即在TCP链接超过该时间没有任何数据交互时,发送保活探测报文;设置保活探测报文的发送时间间隔keepInterval(默认75s);设置保活探测报文的总发送次数keepCount(默认10)。如果在keepCount次的保活探测报文均没有收到客户端的回应,则服务器端即关闭与客户端的TCP链接。 如果发现客户端正常,则重置保活时间。 保活探测报文: TCP保活探测报文的内容为,将之前已发送数据的最大序列号序列的减1,并设置数据大小为1个字节,数据内容为“0”,然后发送到客户端,客户端对服务器端保活探测报文发送确认报文。
通过
netstat
,lsof
等指令查看到连接的状态处于 ESTABLISHED 状态并不是一件非常靠谱的事,因为连接可能已死,但没有被系统感知到使用 TCP 通信的过程中出现了
open too many files
的异常,那就应该检查一下,你是不是创建了太多的连接,而没有关闭
TCP连接保活的两种方式:
- 通过TCP自带的keep-alive功能,服务端自动去定时发送探测报文
- 应用程序客户端自己定时发送心跳包到服务端,可以使用timer,ScheduledExecutorService等,netty内置的
IdleStateHandler
支持心跳检测、失败重连
如果客户端设置了Connection:Keep-Alive
参数,服务端是不会主动去关闭连接的,这样的话可能会导致客户端数量很少,但是服务端出现很多连接数没有及时关闭,占用服务端资源。
解决办法:
-
客户端修改,去掉
Connection:Keep-Alive
参数 -
服务端从系统层面,设置linux系统的TCP保活参数
# /etc/sysctl.conf文件添加配置
net.ipv4.tcp_keepalive_intvl = 3
net.ipv4.tcp_keepalive_probes = 2
net.ipv4.tcp_keepalive_time = 6
客户端与服务端空闲时间达到6秒之后,服务端每隔3秒检测下客户端存活情况,一共检测两次,如果在6+3*2=12之内客户端进程退出了,服务端就会主动关闭该连接。服务端检测客户端存活是通过发送基于TCP的keep alive报文,客户端进程如果没有退出,就会发送确认keep alive的响应报文。
这种方式存在的问题是服务器频繁检测客户端存活情况,会浪费带宽。
- 服务端修改,在消息发送完毕之后主动关闭连接
对于netty服务端,新增一个CLOSE监听器, 当channel write操作完成时,CLOSE监听器主动close掉channel
ChannelFuture future = channel.write(response);
if ((!HttpHeaders.isKeepAlive(response)) || (!response.containsHeader("Content-Length"))) {
future.addListener(ChannelFutureListener.CLOSE);
}
TCP keep-alive HTTP keep-alive的关系
HTTP1.0仅支持短链接,HTTP1.1默认使用长连接,客户端请求默认添加参数
TCP keep-alive与HTTP keep-alive区别:
TCP的keep-alive机制主要是用来判断对方是否存活的
HTTP的keep-alive机制主要是用来复用TCP连接
各种异常情况
-
服务器主机崩溃 客户端在给服务器发送数据时,由于收不到服务器端回传的ACK确认报文,正常情况下,客户端TCP均会进行超时重传,一般为重传12次大约9分钟后才放弃重传,并关闭客户端TCP链接。
-
服务器主机崩溃后重启 如果服务器主机在崩溃重启的这段时间里,客户端没有向服务器发送数据,即客户端没有因重传次数超过限制关闭TCP链接。则在服务器重启后,当客户端再向服务器发送TCP报文时,由于服务器中的TCP链接已经关闭,会直接向客户端回复RST报文,客户端在接收RST报文后关闭自己的TCP链接。
-
服务器主机断网或者中间路由器出现故障 与情况1类似,客户端会进行超时重传,直到重传次数超过后放弃重传,并关闭客户端TCP链接。(因为TCP中会忽略目的主机不可达和目的网络不可达的ICMP报文,并进行重传,直到重传总时间超过限制)
-
服务器主机断网或者中间路由器出现故障后又恢复 如果在服务器主机断网或者中间路由器出现故障这段时间内,客户端和服务器之间没有进行相互通信,即双方均没有察觉对方目的不可达,则在恢复网络链接后两端的TCP链接均有效,能够正常继续进行通信。
如果在服务器主机断网或者中间路由器出现故障这段时间内,客户端因向服务器发送数据超时,并重传总时间超过限制关闭TCP链接。则再网络恢复后,服务器再向客户端发送TCP报文时,客户端也会直接恢复RST报文,服务器再收到RST报文后关闭自己的TCP链接。
-
服务器关机或服务器进程被终止 正常情况下服务器主机被关机时,操作系统都会事先通知所有仍在运行的进程,并先将所有进程终止后,再继续关闭电脑。而所有的进程在被终止时,Unix操作系统内核都会事先去关闭所有已经打开的TCP链接,即向客户端发生FIN标志报文,进行四次握手关闭连接。 因此,对于这种情况,客户端是能够察觉到并正常关闭TCP链接。
-
服务器的端口被关闭 如果在通信过程中,服务器的监听端口被管理员或系统禁掉,则当客户端再向服务器发送TCP报文时,服务器在收到该报文后,由于发送该目的端口没有处于监听状态,则会直接向客户端发送RST报文,客户端在收到RST报文后会直接关闭自己TCP链接。
TCP与UDP区别
- TCP是面向连接的(在客户端和服务器之间传输数据之前要先建立连接),UDP是无连接的(发送数据之前不需要先建立连接)
- TCP提供可靠的服务(通过TCP传输的数据。无差错,不丢失,不重复,且按序到达);UDP提供面向事务的简单的不可靠的传输。
- UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性比较高的通讯或广播通信。随着网速的提高,UDP使用越来越多。
- 没一条TCP连接只能是点到点的,UDP支持一对一,一对多和多对多的交互通信。
- TCP对系统资源要去比较多,UDP对系统资源要求比较少
- UDP程序结构更加简单
- TCP是流模式,UDP是数据报模式
HTTP2
HTTP协议主要有三个版本:
- HTTP1.0 只有短链接
- HTTP1.1 引入了长连接,默认长连接,服务端可以设置response
Connection:close
来关闭长连接,一个请求就会占用一个连接 - HTTP2 解决HTTP1.1的性能问题,一个请求可以多路复用
header压缩
HTTP/1.1的header带有大量信息,而且每次都要重复发送。HTTP/2 为了减少这部分开销,采用了HPACK 头部压缩算法对Header进行压缩。
多路复用
多路复用允许同时通过单一的HTTP/2.0连接发起多重的请求-响应消息。在HTTP1.1协议中,浏览器客户端在同一时间,针对同一域名下的请求有一定数量的限制,超过了这个限制的请求就会被阻塞。而多路复用允许同时通过单一的 HTTP2.0 连接发起多重的“请求-响应”消息。
服务端推送
简单来讲就是当用户的浏览器和服务器在建立连接后,服务器主动将一些资源推送给浏览器并缓存起来的机制。有了缓存,当浏览器想要访问已缓存的资源的时候就可以直接从缓存中读取了。