Home Tags Posts tagged with "心跳"

心跳

最近系统的学习了网络编程的相关知识,当然也离不开Tcp这位重量级,而自己手里写的这个项目 基于Bio的聊天室 其本质也是消息的传递,最接近Tcp的实现原理,因此根据对Tcp的学习,引出本人对自己项目的思考。

起因是我考虑将该聊天室做的可靠,健壮,同时往消息队列的模型上靠拢,因此,必须考虑从客户端发往服务器端的消息的可靠性和安全性。

 

我们知道,基于Tcp实现的Socket编程,其本质就是Socket连接 = Tcp 连接,所以很多原本考虑在Socket层面实现的保证安全性和可靠性的机制其实Tcp已经实现了,Tcp具有以下机制:

保证可靠性的机制

  • 校验和
  • 序列号(按序到达)
  • 确认应答
  • 超时重传
  • 连接管理
  • 流量控制
  • 拥塞控制

提高性能的机制

  • 滑动窗口
  • 快速重传
  • 延迟应答
  • 捎带应答

 

 

针对这些机制,仔细梳理我所写的项目的实现

连接和断开

由于是基于Tcp实现的Socket编程,因此不用考虑连接和正常断开的问题(Tcp的三次握手和四次挥手)

但是如果已经建立了连接,而客户端突然故障,这种情况下开启Tcp的保活计时器,修改其参数,即开启其心跳机制,同时为了避免其Bug(详情可搜索另一篇博文),我在应用层实现了心跳机制,可以保证客户端突发故障,服务器端能快速知晓并关闭连接。

而服务器端突然故障,则没关系,因为客户端是在向服务器端写入的,比如 客户端发送消息 哈哈哈哈 成功(这个成功会有个显示,类似于QQ消息的已发送状态),而无论是在客户端下次发送之前还是发送中与服务器连接失败,客户端都会立即收到通知,即客户端显示 未发送成功 ,这样客户端就可以采取下一步动作了(采取什么动作需要再进行设计 例如重新连接 重传 等等)

 

消息确认的必要性

昨晚我思考了几个问题,在客户端发送消息给服务器端的过程中,可能出现以下几种情况:

消息还未发送

  1. 客户端刚准备发送消息,服务器端挂了,这个时候因为会抛出异常,客户端就知晓这条消息发不出去了,因此这条消息不会丢失也不会出错(丢失起码要先发出去)

消息已经在发送过程中

  1. 客户端的这条消息在传递过程中,服务器端挂了,也会抛出异常,但是客户端不知道这条消息是否发送成功(因为有可能服务器已经收到了才挂,也有可能是服务器端没收到就挂了),只知道服务器端挂了,而客户端此时不知道是否应该重发,导致迷茫

上述分析情况单纯的考虑了服务器端对消息的接收情况,而服务器端收到消息后,可能还有一些其他操作,例如转发,存储,加工等等,因此必须同时考虑服务器对消息的处理情况(建立在服务器端成功收到消息的基础上)

服务器端成功接收消息,但在对消息进行加工,转发,存储的时候,不管是成功还是失败,这个结果对客户端是不可知的,因为目前客户端只会对 “客户端和服务器端的连接中断或失败”这个事件作出反应,也就是抛出异常;这样肯定不行啊,我客户端一头雾水,一问三不知,不知道我的消息发送出去没,不知道服务器端收到消息没,不知道服务器端成功处理消息没。很显然,解决这个问题就是让客户端了解所发消息的状态

消息状态粗略的可分为:

  1. 未发送出去
  2. 服务器端成功接收了该消息但处理失败
  3. 服务器端成功处理了该消息

而确认应答就可以让客户端知道消息的状态,从而进行其他操作。

 

TCP的确认应答机制(ACK机制) 

 

TCP将每个字节的数据都进行了编号, 即为序列号.

每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你要从哪里开始发.
比如, 客户端向服务器发送了1005字节的数据, 服务器返回给客户端的确认序号是1003, 那么说明服务器只收到了1-1002的数据.
1003, 1004, 1005都没收到.
此时客户端就会从1003开始重发.

 

既然有了TCP的确认应答(ACK机制),为什么还需要我们在应用层实现应答机制?

我理解的是,TCP的确认应答确认的范围是在一条消息内,它对每个字节的数据进行标号,比如一条消息如果占300个字节,那么它可以确保这300个字节的消息中,如果有丢失的,可以重发

例如:这一条消息占300个字节,由于网络阻塞或其他原因,只发送了158个字节,那么服务器端的ACK只会回应159,这样的话客户端就会从159开始重发,确保这一条消息完全发送。

理解了TCP应答机制的实现,那么我们就知道了TCP的确认应答并不能解决我们上述所需要的 让客户端了解所发消息的状态,因此还需要我们在应用层实现应答机制。

 

应用层实现应答机制

那么如何设计该应用层的应答机制呢?首先需要区分消息状态是从客户端本身还是从服务器端来进行反馈

消息状态由客户端本身进行反馈(ACK)

从客户端A发送消息到该消息在网上进行传输之前,如果服务器端连接断开或客户端本身的原因,已经无法写入,则反馈 未发送成功

类似于QQ发送消息,结果本地网络已经中断了,会反馈发送失败,这个状态由客户端自身反馈。

消息状态由服务器端进行反馈(ACK)

客户端A发送消息后,由于网络拥堵等原因,消息无法到达服务器端B,因为服务器端根本没收到消息,则无法回应,而客户端本身也无法反馈该消息状态,这种情况必须在客户端使用超时重传策略进行解决;(问题一:消息在客户端发给服务器端的过程中丢失)

客户端A发送消息后,服务器端成功接收,但是在处理的时候失败了,这个时候需要由服务器端向客户端反馈状态,同样的,如果处理成功也需要反馈,也就是说服务器端只向客户端反馈对消息进行处理之后的状态;那么此处必须要考虑到的就是 由客户端本身进行反馈还好,该反馈一般不会丢失,但是由服务器端进行的反馈是有可能丢失的,如果这个ACK丢失了,客户端就还是不知道该消息的状态 因此 仅仅有确认应答机制是不够的。(问题二:ACK在服务器端发向客户端的途中丢失)

TCP则是使用超时重传机制来解决 消息在客户端发给服务器端的过程中丢失 和 ACK丢失 两个问题的

 

TCP超时重传机制

 

主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B
如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发
但是主机A没收到确认应答也可能是ACK丢失了.

这种情况下, 主机B会收到很多重复数据.
那么TCP协议需要识别出哪些包是重复的, 并且把重复的丢弃.
这时候利用前面提到的序列号, 就可以很容易做到去重.

 

既然有了TCP的超时重传,为什么还需要应用层实现超时重传?

还是一个范围问题,TCP解决的是字节层面(或者称之为数据包层面的问题)而应用层则解决的是所发送的消息层面的问题

 

应用层实现超时重传

客户端A发送消息,服务器端接收并处理消息,反馈消息状态给客户端A,这是消息确认应答的流程

在这个流程中,如果确认应答过程中ACK丢失,则使用超时重传的策略来解决;

具体来说就是:

客户端A发送消息给服务器端,可能因为网络拥堵,数据无法到达服务器端(即消息在发送过程中,服务器端还没接收到消息就丢失了),在客户端A使用超时重传机制,如果客户端A在一个特定的时间间隔内没有收到服务器端发来的确认应答, 就进行重发;

发生超时重传的情景有两个:

  1. 第一个就是客户端消息在发送过程中丢失,
  2. 第二个就是来自服务器端的ACK丢失;

第一种情景还好,可是第二种情景,服务器端已经收到了该消息,客户端进行超时重传势必会导致服务器端收到许多重复消息;

那么必须在服务器端进行消息去重,由TCP的超时重传引出

关于消息去重我们可以用以下做法

每条发送的消息有一个唯一的序列号,在服务器端收到一条消息后,会先比较该ID表示是否存在,存在则表示该消息在服务器里面了,反馈客户端就好,不需要再对这条重复的消息进行太多操作

那么超时时间如何确定呢?

最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”.
但是这个时间的长短, 随着网络环境的不同, 是有差异的.
如果超时时间设的太长, 会影响整体的重传效率; 如果超时时间设的太短, 有可能会频繁发送重复的包.

TCP为了保证任何环境下都能保持较高性能的通信, 因此会动态计算这个最大超时时间.

Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍.
如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传. 如果仍然得不到应答, 等待 4*500ms 进行重传.
依次类推, 以指数形式递增. 累计到一定的重传次数, TCP认为网络异常或者对端主机出现异常, 强制关闭连接.

到此,我们来回顾一下,我们为了让客户端了解它所发送的消息的状态并根据状态进行其他操作,使用了确认应答机制,而确认应答机制无法解决两个问题(ACK丢失和消息在发送的时候丢失),为了解决和完善确认应答机制,我们使用了超时重传机制,而超时重传在ack丢失的情况下会导致服务器端收到重复消息,我们又使用最开始设计的传递的消息的唯一序列号来进行去重,环环相扣,确保了消息从客户端传递到服务器端的可靠性。

 

 

参考:https://blog.csdn.net/sinat_36629696/article/details/80740678

在完成自己的Bio项目时,考虑到了心跳检测,有必要整理相关知识,特此记录

什么是掉线?

一. 从程序的角度看待TCP掉线

       TCP掉线的原因多种多样、不一而足,比如,客人的电脑突然断电、OS崩溃、路由器重启、网线接触不良、因为P2P下载软件而导致网络资源短缺、Internet网络的不稳定等等,但是从程序的角度来说,我们可以总结为两种情况:程序能立即感知的掉线和程序不能立即感知的掉线。

1. 程序能立即感知的掉线

       也就是说客户端一掉线,服务器端的某个读写对应的TCP连接的线程就会抛出异常,这种情况相对容易处理。

2. 程序不能立即感知的掉线(https://blog.csdn.net/kkkkkxiaofei/article/details/12966407?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-5.essearch_pc_relevant&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-5.essearch_pc_relevant

  我们都知道,TCP连接的建立,需要经过三次握手;而TCP连接的断开,需要经过四次挥手。掉线通常没什么大不了的,掉就掉了呗,只要四次挥手顺利完成后,服务器和客户端分别做一些善后处理就可以。

       麻烦的事情在于,连接在没有机会完成4次挥手时已经断开了(比如当客人的电脑系统死机,或客人电脑与服务器之间的某处物理网线断开),而服务端以为客户端还正常在线,而客户端也自以为还正常在线。这种程序对现实状态的错误判断有可能引发诸多悲剧。比如,在此情况下,客户端发一个指令给服务器,服务器因为没有收到而一直处于等待指令的状态;而客户端了,以为服务器已经收到了,也就一直处于等待服务端回复的状态。如果程序的其它部分需要依据当前的状态来做后续的操作,那就可能会出问题,因为程序对当前连接状态的判断是错误的。

       毫无疑问,这种对连接状态错误的判断所持续的时间越久,带来可能的危害就越大。当然,如果我们不做任何额外的处理措施,服务器到最后也能感受到客户端的掉线,但是,这个时间可能已经过去了几分钟甚至几十分钟。对于大多数应用来说,这是不可忍受的。 所以,针对这种不能立即感知掉线的情况,我们要做的补救措施,就是帮助程序尽快地获知tcp连接已断开的信息。

      首先,我们可以在Socket上通过Socket.IOControl方法设置KeepAliveValues,来控制底层TCP的保活机制,比如,设定2秒钟检测一次,超过10秒检测失败时抛出异常。  

       据我们的经验,这种设定可以解决一部分问题,但是仍然会有一些连接在断开后,远远超过10秒才被感知掉。所以,这个补救措施还是远远不够的。我们还需要在应用层加入我们自己的TCP连接状态检测机制,这种机制就是通常所说的“心跳”。

 

使用socket.isConnected()或者socket.isClosed()等方法来判断Socket的实时状态的问题

https://blog.csdn.net/cshichao/article/details/8900446

很多人会说用socket.isConnected()或者socket.isClosed()等方法来判断就行了,但事实上这些方法都是访问socket在内存驻留的状态,当socket和服务器端建立链接后,即使socket链接断掉了,调用上面的方法返回的仍然是链接时的状态,而不是socket的实时链接状态

什么是心跳检测?(https://www.cnblogs.com/havenshen/p/3850167.html)

简单来说就是 客户端和服务器端建立连接后,有一段时间没有相互发送消息,这个时候为了知道对方是否还存活,发送心跳数据,看能否发送成功或对方有无回应,再根据结果执行其他的逻辑(例如重新建立连接)

通过什么实现心跳检测?

底层实现是:keepAlive对TCP连接进行设置

keep alive三个参数:

sk->keepalive_probes:探测次数
sk->keepalive_time 探测的超时
sk->keepalive_intvl 探测间隔

对 于一个已经建立的tcp连接。如果在keepalive_time时间内双方没有任何的数据包传输,则开启keepalive功能的一端将发送 keepalive数据包,若没有收到应答,则每隔keepalive_intvl时间再发送该数据包,发送keepalive_probes次。一直没有 收到应答,则发送rst包关闭连接。若收到应答,则将计时器清零。

 

那如何开启keepalive功能呢?

重点:通过Socket设置keepalive参数,开启keepalive方式来实现;

欸 不是说tcp的keepalive吗?怎么又提到Socket了?(简单来说就是Socket的数据传输方式可以是UDP也可以是TCP)

这就要说到Socket的本质:我们知道网络中进程间通信是利用三元组【ip地址,协议,端口】来实现的,而Socket就是一种技术 或者称之为 网络间通信的中间件工具,并且基本上现在所有的应用程序都采用Socket来进行通信。而Socket这种通信的数据传输方式有两种:

    a、SOCK_STREAM(TCP):表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。
  b、SOCK_DGRAM(UDP):表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为 SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。
也就是说Socket可以通过TCP的数据传输方式实现,也可以通过UDP的数据传输方式实现。前面说了目前所有的应用程序都采用Socket来通信,那么当然,许许多多的语言也支持Socket的使用,接下来就看看在Java中Socket的使用吧~
TCP:
UDP:
因此 回到前面,Socket实现心跳,直接在客户端调用Socket.setKeepalive(true)方法即可(Java语言中)。

那么对于Java中的Socket的Keepalive 我们如何理解呢?

ava socket编程中有个keepalive选项,看到这个选项经常会误解为长连接,不设置则为短连接,实则不然。

socket连接建立之后,只要双方均未主动关闭连接,那这个连接就是会一直保持的,就是持久的连接。keepalive只是为了防止连接的双方发生意外而通知不到对方,导致一方还持有连接,占用资源。

其实这个选项的意思是TCP连接空闲时是否需要向对方发送探测包,实际上是依赖于底层的TCP模块实现的,java中只能设置是否开启,不能设置其详细参数,只能依赖于系统配置。

首先看看源码里面是怎么说的

源码的意思是,如果这个连接上双方任意方向在2小时之内没有发送过数据,那么tcp会自动发送一个探测探测包给对方,这种探测包对方是必须回应的,回应结果有三种:

1.正常ack,继续保持连接;

2.对方响应rst信号,双方重新连接。

3.对方无响应。

这里说的两小时,其实是依赖于系统配置,在linux系统中(windows在注册表中,可以自行查询资料),tcp的keepalive参数

net.ipv4.tcp_keepalive_intvl = 75 (发送探测包的周期,前提是当前连接一直没有数据交互,才会以该频率进行发送探测包,如果中途有数据交互,则会重新计时tcp_keepalive_time,到达规定时间没有数据交互,才会重新以该频率发送探测包)
net.ipv4.tcp_keepalive_probes = 9  (探测失败的重试次数,发送探测包达次数限制对方依旧没有回应,则关闭自己这端的连接)
net.ipv4.tcp_keepalive_time = 7200 (空闲多长时间,则发送探测包)

为了能验证所说的,我们来进行测试一下,本人测试环境是客户端在本地windows上,服务端是在远程linux上,主要测试服务器端向客户端发送探测包(客户端向服务端发送是一样的原理)。

验证链接:https://www.cnblogs.com/xiao-tao/p/9718017.html

 

为什么需要自己在应用层实现Keepalive?

上述只是从TCP底层角度来实现了Keepalive心跳功能,但实际上它是存在Bug的,即:正常情况下,连接的另一端主动调用colse关闭连接,tcp会通知,我们知道了该连接已经关闭。但是如果tcp连接的另一端突然掉线,或者重启断电,这个时候我们并不知道网络已经关闭。而此时,如果有发送数据失败,tcp会自动进行重传。重传包的优先级高于keepalive,那就意味着,我们的keepalive总是不能发送出去。 而此时,我们也并不知道该连接已经出错而中断。在较长时间的重传失败之后,我们才会知道。

 

为了避免这种情况发生,我们要在tcp上层,自行控制。对于此消息,记录发送时间和收到回应的时间。如果长时间没有回应,就可能是网络中断。如果长时间没有发送,就是说,长时间没有进行通信,可以自行发一个包,用于keepalive,以保持该连接的存在。

0 66

上篇博文讲解了有关长连接的概念

接下来该讲解心跳了

为什么会出现心跳?

因为长连接自带的保活机制不太完善,虽然可以通过调参的方式来提升性能,但还是不够用,因此我们引入心跳机制

介绍

  • 长连接
    首先这里所说的连接是指网络传输层的使用TCP协议经过三次握手建立的连接;长连接是指建立的连接长期保持,不管此时有无数据包的发送;有长连接自然也有短连接,短连接是指双方有数据发送时,就建立连接,发送几次请求后,就主动或者被动断开连接。
  • 心跳
    心跳这个名字比较形象,就像人体心跳一样,是用来检测一个系统是否存活或者网络链路是否通畅的一种方式,其一般做法是定时向被检测系统发送心跳包,被检测系统收到心跳包进行回复,收到回复说明对方存活。

心跳和长连接在一起介绍的原因是,心跳能够给长连接提供保活功能,能够检测长连接是否正常(这里所说的保活不能简单的理解为保证活着,具体来说应该是一旦链路死了,不可用了,能够尽快知道,然后做些其他的高可用措施,来保证系统的正常运行)。

优势

长连接的优势

  • 减少连接建立过程的耗时
    大家都知道TCP连接建立需要三次握手,三次握手也就说需要三次交互才能建立一个连接通道,同城的机器之间的大概是ms级别的延时,影响还不大,如果是北京和上海两地机房,走专线一来一回大概需要30ms,如果使用长连接,这个优化还是十分可观的。
  • 方便实现push数据
    数据交互-推模式实现的前提是网络长连接,有了长连接,连接两端很方便的互相push数据,来进行交互。

疑问

  • TCP连接到底是什么?
    所谓的TCP连接不是物理的连接,是为了实现数据的可靠传输由通信双方进行三次握手交互而建立的逻辑上的连接,通信双方都需要维护这样的连接状态信息。比如netstat经常看到连接的状态为ESTABLISHED,表示当前处于连接状态。(这里需要注意的是这个ESTABLISHED的连接状态只是操作系统认为当前还处在连接状态
  • 是不是建立了长连接,就可以高枕无忧了呢?
    建立好长连接,两端的操作系统都维护了连接已经建立的状态,是不是这时向对端发送数据一定能到达呢?
    答案是否定的。
    可能此时链路已经不通,只是TCP层还没有感知到这一信息,操作系统层面显示的状态依然是连接状态而且因为TCP层还认为连接是ESTABLISHED,所以作为应用层自然也就无法感知当前的链路不通。
    这种情况会导致什么问题?
    如果此时有数据想要传输,显然,数据是无法传送到对端,但是TCP协议为了保证可靠性,会重传请求,如果问题只是网线接头松了,导致网络不通,此时如果及时将网线接头接好,数据还是能正常到达对端,且TCP的连接依然是ESTABLISHED,不会有任何变化。但不是任何时候都这么巧,有时就是某段链路瘫痪了,或者主机挂了,系统异常关闭了等。这时候如果应用系统不能感知到,是件很危险的事情。

    个人理解:

建立长连接之后并非高枕无忧

因为操作系统层面显示的状态还是连接状态,操作系统说,我认为连接还没断开

由于TCP是在操作系统之上的(层次来讲)

操作系统告诉TCP层说:我认为连接还没断开
TCP:哦,好的
于是TCP又告诉应用层:连接还没断开呢
应用层:哦,好的

也就是传达了错误信息,如果现在想要传输数据怎么办?
因为事实上已经连接不通了,但操作系统,TCP,应用层都认为还可以传输,那么他们就会进行传输
结果发现,诶,传不过去
这个时候TCP的重传特性出现了
TCP:传不过去,我重新再传呗,再来

那么,如果事实上出现的是网络接线松了,或者网线被拔掉了,此时接上,数据还能正常到达对端,
但是
如果很不巧,是传输中间的链路瘫痪了,或者对端的这个主机挂了,系统异常被入侵了等等
这个时候应用系统不能感知到,那就很可怕了
这个资源的浪费是巨大的

 

  • 长连接怎么保活?
    TCP协议实现中,是有保活机制的,也就是TCP的KeepAlive机制(此机制并不是TCP协议规范中的内容,由操作系统去实现),KeepAlive机制开启后,在一定时间内(一般时间为7200s,参数tcp_keepalive_time)在链路上没有数据传送的情况下,TCP层将发送相应的KeepAlive探针以确定连接可用性,探测失败后重试10(参数tcp_keepalive_probes)次,每次间隔时间75s(参数tcp_keepalive_intvl),所有探测失败后,才认为当前连接已经不可用。这些参数是机器级别,可以调整。
  • 应用层需要做点什么吗?(心跳)
    按照TCP的KeepAlive机制,默认的参数,显然不能满足要求。那是不是调小点就可以了呢?
    调整参数,当然是有用的,但是首先参数的机器级别的,调整起来不太方便,更换机器还得记得调整参数,对系统的使用方来说,未免增加了维护成本,而且很可能忘记;其次由于KeepAlive的保活机制只在链路空闲的情况下才会起到作用,假如此时有数据发送,且物理链路已经不通,操作系统这边的链路状态还是ESTABLISHED,这时会发生什么?自然会走TCP重传机制,要知道默认的TCP超时重传,指数退避算法也是一个相当长的过程。因此,一个可靠的系统,长连接的保活肯定是要依赖应用层的心跳来保证的。
    这里应用层的心跳举个例子,比如客户端每隔3s通过长连接通道发送一个心跳请求到服务端,连续失败5次就断开连接。这样算下来最长15s就能发现连接已经不可用,一旦连接不可用,可以重连,也可以做其他的failover处理,比如请求其他服务器。
    应用层心跳还有个好处,比如某台服务器因为某些原因导致负载超高,CPU飙高,或者线程池打满等等,无法响应任何业务请求,如果使用TCP自身的机制无法发现任何问题,然而对客户端而言,这时的最好选择就是断连后重新连接其他服务器,而不是一直认为当前服务器是可用状态,向当前服务器发送一些必然会失败的请求。

    设计误区

    • 无心跳
      无心跳的设计,也是很常见的,为了省事,长连接断开,TCP传输层有通知,应用程序只要处理这种通知,一旦发现连接异常,就重连。但是此类通知可能来的特别晚,比如在机器奔溃,应用程序异常退出,链路不通等情况下。
    • 被连接方检测心跳
      心跳的实现分为心跳的发送和心跳的检测,心跳由谁来发都可以,也可以双方都发送,但是检测心跳,必须由发起连接的这端进行,才安全。因为只有发起连接的一端检测心跳,知道链路有问题,这时才会去断开连接,进行重连,或者重连到另一台服务器。
      例如,client去连接server,client定时发送心跳到server,server检测心跳,发现一段时间client没有传心跳过来,认为与client的链路已经出了问题或者client自身就已经出了问题。粗看上去貌似没什么问题,但是如果只是client与当前这个server之间的链路出了问题,作为一个高可用的系统,是不是应该还有另一个server作为备选,问题出在短时间内client根本不知道自己和第一个server出了问题,所以也不会主动去连接第二个server。
    • 第三方心跳
      还有一类心跳,使用第三方保活,也就是除了客户端和服务端之外,还有另一台机器,定时发送心跳去探测服务端的存活。这类探活方法使用在检测系统的存活与否的问题上是没有问题的,但是这类设计是无法用来检测客户端和服务端之间链路的好坏。

    参考方案

    • 方案一
      最简单的策略当然是客户端定时n秒发送心跳包,服务端收到心跳包后,回复客户端的心跳,如果客户端连续m秒没有收到心跳包,则主动断开连接,然后重连,将正常的业务请求暂时不发送的该台服务器上。
    • 方案二
      可能有人觉得,这样是不是传送一些无效的数据包有点多,是不是可以优化下,说实话,个人认为其实一点也不多。当然是可以做些优化的,因为心跳就是一种探测请求,业务上的正常请求除了做业务处理外,还可以用作探测的功能,比如此时有请求需要发送到服务端,这个请求就可以当作是一次心跳,服务端收到请求,处理后回复,只要服务端有回复,就表明链路还是通的,如果客户端请求比较空闲的时候,服务端一直没有数据回复,就使用心跳进行探测,这样就有效利用了正常的请求来作为心跳的功能,减少无效的数据传输。

0 67

最近开始学习netty,作为一个大三学生,却刚刚开始接触网络编程实在惭愧,路上遇到许多的新知书点,特此记录(个人理解+转载:https://zhuanlan.zhihu.com/p/224595048

 

补充:

长连接与短连接

TCP 本身并没有长短连接的区别,长短与否,完全取决于我们怎么用它。

  • 短连接:每次通信时,创建 Socket;一次通信结束,调用 socket.close()。这就是一般意义上的短连接,短连接的好处是管理起来比较简单,存在的连接都是可用的连接,不需要额外的控制手段。
  • 长连接:每次通信完毕后,不会关闭连接,这样就可以做到连接的复用。长连接的好处便是省去了创建连接的耗时。

短连接和长连接的优势,分别是对方的劣势。想要图简单,不追求高性能,使用短连接合适,这样我们就不需要操心连接状态的管理;想要追求性能,使用长连接,我们就需要担心各种问题:比如端对端连接的维护,连接的保活。

长连接还常常被用来做数据的推送,我们大多数时候对通信的认知还是 request/response 模型,但 TCP 双工通信的性质决定了它还可以被用来做双向通信。在长连接之下,可以很方便的实现 push 模型。

 

文章目录
一、简介
  1.1、TCP协议简介
  1.2、HTTP协议简介
二、TCP keepalive
  2.1、简介
  2.2、实验
  2.3、扩展
三、HTTP keep-alive
  3.1、简介
  3.2、实验
    3.2.1、实验一:禁用keep-alive的http请求
    3.2.2、实验二:启用keep-alive的http请求
  3.3、扩展
四、总结
五、彩蛋

1、从文中找出我的IP
2、http请求中是客服端还是服务端主动关闭的tcp连接?
请阅读到最后的彩蛋部分

HTTP和TCP都是老生常谈的知识点,本文不进行铺开赘述。我们可能在HTTP和TCP中都听说“长连接”的说法,也听过HTTP中有keep-alive,TCP中有keepalive。那么,HTTP和TCP的长连接有何区别?HTTP中的keep-alive和TCP中keepalive又有什么区别?

Tips:HTTP中是keep-alive,TCP中是keepalive,HTTP中是带中划线的。大小写无所谓。

一、简介

上面是我先前做TCP协议分享时整理的一张表格,从上面可以看出:不管是在OSI七层网络模型还是在TCP/IP五层网络模型中,TCP是传输层的一种协议,而HTTP是应用层的一种协议

HTTP和TCP的理论和实现还是相当复杂的,下面只简单介绍和本文主题相关的知识点。

 

1.1、TCP协议简介

TCP协议也叫传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。使用TCP的两个程序(客户端和服务端)在交换数据前,通过三次握手来建立TCP连接,建立连接后就可以进行基于字节流的双工通讯,由TCP内部实现保证通讯的可靠性,完全通讯完成后,通过四次挥手断开连接。

在客户端和服务端间的网络一切正常、且双方都没主动发起关闭连接的请求时,此TCP连接理论上可以永久保持。但是,网络情况是及其复杂的,在双方长时间未通讯时,如何得知对方还活着?如何得知这个TCP连接是健康且具有通讯能力的?

 

1.2、HTTP协议简介

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写。HTTP是万维网的数据通信的基础。HTTP是一个应用层协议,通常运行在TCP协议之上。它由请求和响应构成,是一个标准的客户端服务器模型(C/S模型)。HTTP是一个无状态的协议。

无状态怎么解释?HTTP协议永远都是客户端发起请求服务器回送响应。每次连接只处理一个请求,当服务器返回本次请求的应答后便立即关闭连接,下次请求客户端再重新建立连接。也就无法实现在客户端没有发起请求的时候,服务器主动将消息推送给客户端

HTTP无状态导致每次请求都需要重新建立TCP连接,接收到服务器相应后再断开TCP连接

HTTP协议运行在TCP协议之上,它无状态会导致客户端的每次请求都需要重新建立TCP连接,接受到服务端响应后,断开TCP连接。对于每次建立、断开TCP连接,还是有相当的性能损耗的。那么,如何才能尽可能的减少性能损耗呢?(建立长连接,HTTP1.1引入,可是建立了长连接,长连接并不是时时刻刻都在进行数据传输,怎么判断对方还活着呢?或者什么时候断开长连接呢?)

二、TCP keepalive

2.1、简介

正如上面提出的问题:在双方长时间未通讯时,如何得知对方还活着?如何得知这个TCP连接是健康且具有通讯能力的?

TCP的保活机制就是用来解决此类问题,这个机制我们也可以称作:keepalive。保活机制默认是关闭的,TCP连接的任何一方都可打开此功能。有三个主要配置参数用来控制保活功能。

如果在一段时间(保活时间:tcp_keepalive_time)内此连接都不活跃,开启保活功能的一端会向对端发送一个保活探测报文。

  • 若对端正常存活,且连接有效,对端必然能收到探测报文并进行响应。此时,发送端收到响应报文则证明TCP连接正常,重置保活时间计数器即可。
  • 若由于网络原因或其他原因导致,发送端无法正常收到保活探测报文的响应。那么在一定探测时间间隔(tcp_keepalive_intvl)后,将继续发送保活探测报文。直到收到对端的响应,或者达到配置的探测循环次数上限(tcp_keepalive_probes)都没有收到对端响应,这时对端会被认为不可达,TCP连接虽存在但已失效,需要将连接做中断处理。

在探测过程中,对端主机会处于以下四种状态之一:

2.2、实验

这里,强烈推荐《TCP/IP详解 卷1:协议》的第二版(这里一定是第二版), 第17章:TCP保活机制。这里建议17章都看,17.1和17.2小节就涵盖了我上面介绍的内容。

17.2.1 小节中还通过实验的方式详细验证了“对端主机会处于以下四种状态”以及对于这四种状态TCP都是如何去处理。

这本书中的实验已经比较通俗易懂了,我暂且没有亲自动手去模拟实践,后续时间充足,会亲自动手进行实验。

2.3、扩展

上面提到了三个参数保活时间:tcp_keepalive_time、探测时间间隔:tcp_keepalive_intvl、探测循环次数:tcp_keepalive_probes

这三个参数,在linux上可以在/proc/sys/net/ipv4/路径下找到,或者通过sysctl -a | grep keepalive命令查看当前内核运行参数。

[root@vm01 ~]# cd /proc/sys/net/ipv4
[root@vm01 ipv4]# pwd
/proc/sys/net/ipv4
[root@vm01 ipv4]# cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
[root@vm01 ipv4]# cat /proc/sys/net/ipv4/tcp_keepalive_probes
9
[root@vm01 ipv4]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
[root@vm01 ipv4]# sysctl -a | grep keepalive
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_intvl = 75
  • 保活时间(tcp_keepalive_time)默认:7200秒
  • 保活时间间隔(tcp_keepalive_intvl)默认:75秒
  • 探测循环次数(tcp_keepalive_probes)默认:9次

也就是默认情况下一条TCP连接在2小时(7200秒)都没有报文交换后,会开始进行保活探测,若再经过9*75秒=11分钟15秒的循环探测都未收到探测响应,即共计:2小时11分钟15秒后会自动断开TCP连接。

别走开,还有一个骚操作

Linux平台下我们还可以借助man命令查看TCP协议的一些描述和参数定义。下面两个命令的效果相同:

  • 命令一:man tcp
  • 命令二:man 7 tcp

数字7的含义是:man命令使用手册共9章,TCP的帮助手册位于第7章。不知道在第几章也无所谓,使用man tcp也可,弹出的手册左上角也有写第几章。(man ls等同于man 1 lsman ip等同于man 8 ip,可以自己尝试使用 )。

下面我们看下man tcp下的和我们本文有关的几个点:

上面介绍的三个参数tcp_keepalive_time、tcp_keepalive_intvl、tcp_keepalive_probes都是系统级别的,针对整个系统生效。下面介绍针对单条Socket连接细粒度设置的三个选项参数:保活时间:TCP_KEEPIDLE、保活探测时间间隔:TCP_KEEPINTVL、探测循环次数:TCP_KEEPCNT

在我们的Netty的框架中可以看到针对Socket选项的配置,如使用epoll的IO模型中EpollSocketChannelConfig类中的配置:

更多细节,等你挖掘。

三、HTTP keep-alive(又叫持久连接 HTTP/1.1)

3.1、简介

HTTP协议简介中提到http协议是一个运行在TCP协议之上的无状态的应用层协议。它的特点是:客户端的每一次请求都要和服务端创建TCP连接,服务器响应后,断开TCP连接。下次客户端再有请求,则重新建立连接。

在早期的http1.0中,默认就是上述介绍的这种“请求-应答”模式。这种方式频繁的创建连接和销毁连接无疑是有一定性能损耗的。

所以引入了keep-alive机制。http1.0默认是关闭的,通过http请求头设置“connection: keep-alive”进行开启;http1.1中默认开启,通过http请求头设置“connection: close”关闭。

keep-alive机制:若开启后,在一次http请求中,服务器进行响应后,不再直接断开TCP连接,而是将TCP连接维持一段时间。在这段时间内,如果同一客户端再次向服务端发起http请求,便可以复用此TCP连接,向服务端发起请求,并重置timeout时间计数器,在接下来一段时间内还可以继续复用。这样无疑省略了反复创建和销毁TCP连接的损耗。

3.2、实验

下面用两组实验证明HTTP keep-alive的存在。

实验工具:Wireshark

客户端IP:*.*.3.52

服务端IP:*.*.17.254

3.2.1、实验一:禁用keep-alive的http请求

从上图请求列表区中,我们可以发现:

  • 106、107、108三个请求是TCP建立连接三次握手的请求
  • 109、110两个请求分别是:http的请求报文和http的响应报文
  • 111、112、120、121这四个请求是TCP断开连接四次挥手的请求

(由于一台机器上网络请求较多,我加了筛选条件,仅显示客户端和服务端通信的网络请求,所以请求的序号是不连续的)

从上图中间的请求数据解析区,可以确定:此次http请求的请求头中有“Connection: close”,即keep-alive是关闭的。

结论:禁用keep-alive的http请求时,会先建立TCP连接,然后发送报文、响应报文、最后断开TCP连接。

3.2.2、实验二:启用keep-alive的http请求

这次实验请求较多,一张图放不下,两张图是连续的,图1的第二块绿色区域和图2的第一块绿色区域是重叠的(注意看第一列的No.编号)

先说下我的操作:

  1. 开启keep-alive前提下发起第一次http请求
  2. 7秒左右时,同样的机器同样的http请求,再重新调用一次

我们根据图中抓包,分析下网络请求:

  • 197、198、199请求:三次握手建立TCP建立连接
  • 200、203请求:http的请求报文和http的响应报文
  • 212请求:可以通过Protocol列看到它是一条TCP报文。我的理解是:在keep-alive这种机制下,客户端收到服务端响应报文后,需要告知服务端“已收到”。由于要复用TCP连接,所以会多一层保障机制,类似TCP的握手和挥手
  • 459-1965请求(图1中的第一块黑色区域中):6秒内(第二列代表Time),每隔1秒,发生一对TCP请求的来回,用来维护TCP连接的可用性。保证和等待该TCP连接被复用
  • 1743、1744、1745、1755请求:其中的1743和1745是我第二次发起http请求的请求报文和响应报文。1744请求是:客户端发起请求时,服务端先回复客户端“已收到,马上处理”。紧接着1745将结果响应给客户端。1755则是客户端收到响应后,回复服务端“已收到响应,多谢”。
  • 2028-3903请求:10秒内,每隔1秒,发生一对TCP请求的来回,用来维护TCP连接的可用性。保证和等待该TCP连接被复用
  • 4127-4131请求:10秒内我没再发起http请求,四次挥手断开TCP连接。长时间没被复用,也没必要一直维持下去,浪费资源,还可能造成网络拥堵。

注意:10秒无请求,TCP连接在断开,10秒也不是默认的,只是环境的配置。是Httpd守护进程,提供的keep-alive timeout时间设置参数。比如nginx的keepalive_timeout,和Apache的KeepAliveTimeout。

3.3、扩展

其实对于HTTP keep-alive机制可以总结为上图所示。

启用HTTP keep-Alive的优缺点:

优点:keep-alive机制避免了频繁建立和销毁连接的开销。 同时,减少服务端TIME_WAIT状态的TCP连接的数量(因为由服务端进程主动关闭连接)

缺点:若keep-alive timeout设置的时间较长,长时间的TCP连接维持,会一定程度的浪费系统资源。

总体而言,HTTP keep-Alive的机制还是利大于弊的,只要合理使用、配置合理的timeout参数。

四、总结

回到文章开头提出的问题:HTTP和TCP的长连接有何区别?HTTP中的keep-alive和TCP中keepalive又有什么区别?

1、TCP连接往往就是我们广义理解上的长连接,因为它具备双端连续收发报文的能力;开启了keep-alive的HTTP连接,也是一种长连接,但是它由于协议本身的限制,服务端无法主动发起应用报文。

2、TCP中的keepalive是用来保鲜、保活的;HTTP中的keep-alive机制主要为了让支撑它的TCP连接活的的更久,所以通常又叫做:HTTP persistent connection(持久连接) 和 HTTP connection reuse(连接重用)。

五、彩蛋

彩蛋一

你能从文中找出我在HTTP keep-alive实验中客户端和服务端的完整IP吗?

如能找出,说明对网络协议的了解已如火纯青。

彩蛋二

在HTTP请求中,到底是「服务端」还是「客户端」主动关闭连接呢?

看到过很多文章,有人说服务端、有人说客户端、有人说分情况(keep-alive的开启与否)既可能是客户端也可能是服务端。你信谁?最后翻来覆去发现各个网站的各种文章基本类似,只有观点,没有论据。

HTTP keep-alive章节的实验结果:无论开启keep-alive与否,最终由服务端主动断开TCP连接。

但是我给出问题的答案是:通常由服务端主动关闭连接。没有写“肯定由服务端主动关闭连接”的原因是,我没遇到客户端主动关闭连接的场景,并不代表没有。网络和协议博大精深,等待我们继续去探索。

这个彩蛋的目的由两个:

1、告诉大家:网上的文章、他人的观点,还是要思辨的看待。

2、我确实想知道什么情况下,客户端主动关闭连接?欢迎大家私信讨论,一定要有真凭实据

彩蛋三

  • Wireshark是一款功能强大的网络封包分析可视化软件。《TCP/IP详解 卷1:协议》第二版相比第一版,书中的抓包工具也将tcpdump改为*Wireshark。*
  • 个人观点:《TCP/IP详解 卷1:协议》第一版和第二版结合起来看效果更好。第一版的TCP阻塞控制将的更通俗易懂,第二版的TCP保活机制讲的更清晰。