长连接
遗留问题
- 服务器断开后,客户端是如何保持长连接的?
tcp中的keep-alive
作用:保活、心跳、链路有效检测机制、定时清理失效的连接;当一个TCP连接两端长时间没有数据传输时(通常默认配置是2小时),发送keepalive探针,探测链接是否存活
原理:tcp的keep-alive机制发送的探测报文是将之前TCP报文的序列号减1,并设置1个字节,内容为“00”的应用层数据,这样对端能识别这个报文是tcp keep-alive探测报文并进行ack。 建立TCP连接时,就有定时器与之绑定,其中的一些定时器就用于处理keepalive过程。当keepalive定时器到0的时候,便会给对端发送一个不包含数据部分的keepalive探测包(probe packet)
一方发起的keepalive探针,另一方必须响应。响应可能是以下三种形式之一:
1. 对方回应了ACK。说明连接正常,重置计时器重新记时。如果接下来2小时还没有数据传输,那么还会继续发送keepalive探针,以确保连接存活。
2. 对方回复RST(意思是要求对端关闭异常连接且对端不需要回复 ACK)。表示这个连接已经不存在,那就关闭该连接。例如一方服务宕机后重启/程序崩溃
3. 没有回复。那就会触发超时重传,直到达到重传的次数,如果对端依然没有回复,那么就关闭该连接,说明socket已经被关闭了
相关配置:
$ ls /proc/sys/net/ipv4 | grep tcp_keepalive
tcp_keepalive_intvl keepalive探测包的发送间隔,默认为75秒
tcp_keepalive_probes 如果对方不予应答,探测包的最大发送次数,默认为9次。即连续9次发送,都没有应答的话,则关闭连接。
tcp_keepalive_time 连接的最大空闲(idle)时间,默认为7200秒,即2个小时。需要注意的是,这2个小时,指的是只有keepalive探测包,如果期间存在其他数据传输,则重新计时。
可以通过sysctl命令来查看和修改:
查看
sysctl -n net.ipv4.tcp_keepalive_time
修改
sysctl net.ipv4.tcp_keepalive_time=3600
添加上面的配置后输入 sysctl -p 使其生效,你可以使用 sysctl -a | grep keepalive 命令来查看当前的默认配置
常见的几种使用场景:
- 检测挂掉的连接(导致连接挂掉的原因很多,如服务停止、网络波动、宕机、应用重启等)
- 防止因为网络不活动而断连(使用NAT代理或者防火墙的时候,经常会出现这种问题)
- TCP层面的心跳检测
KeepAlive心跳和业务层面的心跳报文相比:
- TCP自带的KeepAlive使用简单,发送的数据包相比应用层心跳检测包更小,仅提供检测连接功能
- 应用层心跳包不依赖于传输层协议,无论传输层协议是TCP还是UDP都可以用
- 应用层心跳包可以定制,可以应对更复杂的情况或传输一些额外信息
- KeepAlive仅代表连接保持着,而心跳包往往还代表客户端可正常工作
http的keep-alive
作用:长连接,复用tcp连接,提高socket效率,减少tcp连接数,也就相应减少了主动关闭方的time_wait状态连接数,提高性能和提高httpd服务器的吞吐率 (更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)
缺点:长时间的tcp连接容易导致系统资源无效占用,处于长时间闲置状态,即在处理暂停期间,本来可以释放的资源仍旧被占用
限制:HTTP1.1的Keep-Alive机制仅对同域下的网络请求有效;而对于非同域下的请求,则需要重新建立一次TCP连接。 事实上,即使客户端和服务端都开启了Keep-Alive,服务端一般会主动关闭非活动的连接,否则会造成资源浪费
Keep-Alive模式下如何知道某一次数据传输结束
如果不是Keep-Alive模式,HTTP协议中客户端发送一个请求,服务器响应其请求,返回数据。服务器通常在发送回所请求的数据之后就关闭连接。这样客户端读数据时会返回EOF(-1),就知道数据已经接收完了。
但是如果开启了 Keep-Alive模式,那么客户端如何知道某一次的响应结束了呢?
- 如果是静态的响应数据,可以通过判断响应头部中的Content-Length字段,判断数据达到这个大小就知道数据传输结束了。
- 但是如果返回的数据是动态变化的,服务器不能第一时间知道数据长度,这样就没有Content-Length关键字了。这种情况下,服务器是分块传输数据的, Transfer-Encoding:chunk,这时候就要根据传输的数据块chunk来判断,数据传输结束的时候,最后的一个数据块chunk的长度是0。
- 当然对于动态的内容也可以使用的Content-Length方法:这时候要想准确获取长度,只能先开一个足够大的内存空间,等内容全部生成好再计算。 但这样做一方面需要更大的内存开销,另一方面也会让客户端等更久,影响TTFB(Time to First Byte)的值(用来衡量服务器响应速度)
HTTP keep-alive和TCP keepalive的区别
TCP keepalive指的是TCP保活计时器(keepalive timer)。设想有这样的情况:客户已主动与服务器建立了TCP连接。但后来客户端的主机突然出故障。 显然,服务器以后就不能再收到客户发来的数据。因此,应当有措施使服务器不要再白白等待下去。这就是使用保活计时器。服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置通常是两小时。若两小时没有收到客户的数据,服务器就发送一个探测报文段,以后则每隔75秒发送一次。若一连发送10个探测报文段后仍无客户的响应,服务器就认为客户端出了故障,接着就关闭这个连接。
仅当nginx的keepalive_timeout值设置高于tcp_keepalive_time,并且距此tcp连接传输的最后一个http响应,经过了tcp_keepalive_time时间之后, 操作系统才会发送侦测包来决定是否要丢弃这个TCP连接。一般不会出现这种情况,除非你需要这样做。
http1.0 vs http1.1 vs http2.0
- http1.0默认关闭长连接,需要明确设置Connection:Keep-Alive头开启
- http1.1默认开启keepalive,有没有请求头Connection:Keep-Alive都行,除非在请求头或响应头中指明要关闭:Connection: Close。设置Keep-Alive: timeout=5, max=100,表示这个TCP通道可以保持5秒,最多接收100次请求就断开
- HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。当然HTTP1.1也可以多建立几个TCP连接,来支持处理更多并发的请求,但是创建TCP连接本身也是有开销的
协议是这样规定的,至于支不支持还得看服务器(比如tomcat)和客户端(比如浏览器)的具体实现
http1.0/ 1.1 /2 需要客户端和服务器都支持;firefox可用通过debug的network查看http version
服务器进程通知TCP断开TCP连接(只是通知,此时TCP连接不一定关闭),TCP发送完数据后,实际断开该连接