http1.0,1.1和tcp,长连接和短连接,keepalive及其应用

/ 默认分类 / 0 条评论 / 1111浏览

http和tcp,长连接和短连接,keepalive

我再前面的博客中已经介绍了网络编程中的一些链接细节,客户端和服务端socket套接字的交互建立过程,socket通信最终建立的就是tcp,而说到 tcp链接,一些基础的三次握手四次挥手我们都清楚,这里我想和大家介绍下很多程序员都懵懂的概念;

http1.0和http1.1和http长连接,http短连接,http的keepalive和tcp长连接,tcp短连接,tcp的keepalive
上面几个概念,你是否都清楚呢?

1. 概念

从本质上来说,tcp没有所谓的长连接和短连接的概念,只有一个名字,就叫做tcp连接,是长是短主要取决于我们怎样去使用它;
如果非要说明,其实短连接就是在链接建立后,进行完一次数据交互后,就关闭tcp连接,比如直接调用socket.close(),如果不关闭,那么在 理想条件下(这里的理想条件是指客户端和服务端都没有保活机制.并且路由器也没有超时断开机制,详细可以参考《tcpip详解卷1》),可以认为连接 会一直保持,不会断开,及时没有数据交互.

所以,你可以这样认为,tcp连接只提供了连接建立和断开的接口,不同操作系统对于网络编程的支持实现就是调用tcp所提供的协议标准,需要建立 连接进行数据交互的系统可以自行控制tcp连接何时断开,所以所谓的tcp长短连接只是应用层面对于tcp连接的关闭调用的控制,你可以选择每次传输都 开启一个新的tcp连接,每次使用完都立刻关闭(当然这会比较占用资源和时间,因为频繁的连接建立和关闭会导致资源占用),也可以选择建立一次tcp连接 后每次传输数据都使用这个tcp连接,并且在不需要传输数据的时候也不关闭这个连接,这样连接就不会断开,这就是所谓的长连接,所以说,长短连接其实 是应用自己控制维持的,可以知道系统重启或者宕机才会关闭连接.

从本质上说,根本不存在http长连接和短连接,因为http是应用层协议,其进行数据交互肯定需要传输层的接入,而传出层最常用的就是tcp协议,在http1.0中,每一次数据传输都会建立一个tcp连接 并在结束后关闭连接,其实也就是应用层进行了tcp连接和断开的控制,这也就是所谓的http短连接,表面上感觉是http1.0传输数据,并且每次 进行一次数据交互就立刻断开;http1.1默认添加了Connection: keep-alive请求头,这样比如需要从同一个远程服务处获取几次js或者css文件, 那么第一次获取会建立一条tcp连接,但是数据传输完毕后不会主动断开连接,服务端检测到这个请求头就不会响应数据后断开连接,保持连接可以复用,下次需要和这个服务端交互还是会使用这个tcp连接,表面上感觉就是使用所谓的tcp长连接,http长连接;

2. tcp保活机制

http的的keepalive等于是告诉服务端,我们需要保持这个tcp连接,不要断开,我还要复用,而tcp中也有一个keepalive,即tcp保活机制

首先先来了解下什么是tcp保活机制:当tcp连接建立后,在服务端或客户端中,如果有在建立tcp连接socket的过程中开启了keepalive,例如java中 的socket.setKeepAlive(true); ,那么如果在一段时间内(即保活时间,keepalive time)连接出于非活动状态,即无数据交互,那么开启保 活机制的一端就会发送一个探测报文(只包含ack),如果没有收到对端的响应(ack),那么间隔一端时间后(keepalive interval)继续发送下一个探测 报文,如果直到发送次数达到探测次数上限(keepalive probes),那么当前端就会主动断开tcp连接.

上面的描述中设计的三个参数都是可以配合的,只是在不同的操作系统中设置的方法不同而已,Linux系统中,这些变量分别对应sysctl变量net.ipv4.tcp_keepalive_time、 net.ipv4.tcp_keepaliveJntvl、net.ipv4.tcp_keepalve probes,默认设置是7200秒(2小时)、75秒和9次探测.

探测报文得到的响应可能有下面几种情况,这里我截取的是jdk中网络编程的注释:

    /**
     * When the keepalive option is set for a TCP socket and no data
     * has been exchanged across the socket in either direction for
     * 2 hours (NOTE: the actual value is implementation dependent),
     * TCP automatically sends a keepalive probe to the peer. This probe is a
     * TCP segment to which the peer must respond.
     * One of three responses is expected:
     * 如果收到ack回应,表示对端还是活的,则过2小时候再探测看他活不活
     * 1. The peer responds with the expected ACK. The application is not
     *    notified (since everything is OK). TCP will send another probe
     *    following another 2 hours of inactivity.
     * 如果收到了RST回应,这是在对端出现故障宕机或者重启的时候才会发送这个,那么发送端也是直接断开tcp连接
     * 2. The peer responds with an RST, which tells the local TCP that
     *    the peer host has crashed and rebooted. The socket is closed.
     * 如果没有收到响应,并且次数达到阈值,那么断开连接
     * 3. There is no response from the peer. The socket is closed.
     *
     * The purpose of this option is to detect if the peer host crashes.
     *
     * Valid only for TCP socket: SocketImpl
     *
     * @see Socket#setKeepAlive
     * @see Socket#getKeepAlive
     */
    @Native public final static int SO_KEEPALIVE = 0x0008;

tcp保活机制默认是关闭的,并且这不是tcp规范中定义的,可以认为是操作系统的对tcp规范的实现中添加的.

ps 摘自《tcp ip协议详解卷1》:一些情况下, 客户端和服务器需要了解什么时候终止进程或者与对方断开连接。而在另 一些情况下, 虽然应用进程之间没有任何数据交换, 但仍然需要通过连接保持一个最小的数 据流TCP保活机制就是为了解决上述两种情况而设计的。保活机制是一种在不影响数据流 内容的情况下探测对方的方式。它是由一个保活计时器实现的。当计时器被激发, 连接一端 将发送一个保活探测( 简称保活) 报文, 另一端接收报文的同时会发送一个ACK 作为响应。

3. tcp长短连接深入探讨

前面已经介绍了,所谓的tcp长短连接,主要取决于我们怎样去使用,如果你想一直连着不断开,那么可以认为这就是长连接,如果每次传输完数据就断开,那么可以认为这就是短连接
短连接使用简单,每次传输都三次握手,握手成功后才发送数据,所以直接使用就很可靠了,但是在高并发场景下,每次输出数据都要建立建立连接,可想而知效率较低
长连接使用起来就要考虑很多问题了,比如第一次三次握手成功后建立了连接,每次发送数据都走这条连接,下次再发数据不是重新三次握手建立新连接发送,而是使用之前握过手建立 好的连接发送数据,那么就要保证连接的可靠性,连接是否可用,如果下次再次使用的时候这条连接已经由于某种原因导致无法和服务端通信了,需要重新连接才行,那么 本次数据传输就会出现异常,所以c,s两端需要维护好这条连接;
长连接经常被用来进行数据推送,比如A服务需要持续获取B服务的消息,那么可以有推和拉两种方式,拉的话就是A自己不断轮询,问B是否有消息了,如果是推的话,那么就是 如果B有数据就直接推送给A,但是前提是A需要在开始的时候和B建立好连接不断开,所以前提就是A和B维护好这条tcp长连接(典型的比如amqp中的消费者,就是先和broker建立tcp长连接, 然后broker中consumer订阅的队列有数据了,broker就立即将数据推送给consumer)
ps: 这也得力于tcp是一种全双工协议,连接双方都可以收发数据

所以要怎样维护好这个长连接呢?这就是重点,在很多rpc框架,如dubbo,配置中心,如nacos,消息中间件,如rabbitmq,他们都使用了tcp长连接,那么他们是怎样维护这个长连接保证连接 可靠可用呢?首先,可以开启tcp的保活机制,如果连接不可用就会被断开;但是这远远不够,原因有如下几点:

所以,在上面所说的几个框架中都实现了心跳检测,是的心跳检测就是c,s双方维持长连接最常用的手段

Dubbo底层使用的通信框架是netty,netty就是对java中NIO的封装,如下面这段duubo中的代码:

public class NettyHandler extends SimpleChannelHandler { 
// <ip:port, channel>
private final Map<String, Channel> channels = new ConcurrentHashMap<String, Channel>();  
 
} 
 
public class NettyServer extends AbstractServer implements Server { 
// <ip:port, channel>
private Map<String, Channel> channels;  
 
} 

连接维护在map集合中,客户端和服务端都会通过心跳检测线程,不断的发送特定的心跳检测数据包,验证连接的可用性,如果服务端发现连接不可用就直接断开,如果客户端发现连接不可用 就重新建立连接.