凤城王力宏音乐爱好组

基于心跳的socket长连接怎么做的呢?什么叫心跳技术?

币鉴bitapp 2018-12-05 22:47:26

心跳

        IM基本的底层逻辑中有“心跳”概念,即客户端定时向Server发一个信令包,表示客户端还“活”着。注意,是客户端发起的。心跳是一个拟人的比喻,跟人的心跳相似。那么心跳终止了会发生什么事情呢?分为两种情况:Server主动断开socket,客户端主动断开socket。

    1. Server主动断开socket

         Server只是接收客户端发起的心跳。假如,Server长时间没有收到客户端的心跳,Server认为客户端已经“死了”,主动断开这个连接。此时客户端可能就是假在线了。

    2. 客户端断开socket

        客户端对待心跳,要比Server麻烦一些。客户端要关注两个值:

        心跳间隔值,即客户端多长时间发一次心跳?

        心跳的超时时间。客户端发送一次心跳,如果长时间得不到Server应答,代表网络糟糕。客户端需要断开socket,主动离线。


      很明显,第二点就是客户端主动断开的情况,一般情况下,超时时间为60秒。

      网上也有争论:到底是否需要心跳,微信是没有心跳的,qq和飞信有心跳。也有专家说心跳包已经影响到移动网络,因为心跳是定时频繁发送。


心跳失败的断线重连


        互联网应用的心跳包除了宣告终端在线外,还有一项重要的任务,就是提供终端的即时地址,方便应用服务器的寻址。
        有了互联网应用的心跳机制,应用服务器可以及时下发(Push)用户相关的信息,比如微信中的短消息、图片或者语音等。心跳包也会带来很多副作用,比如终端更为费电,还可能给移动通信网络带来信令风暴。

        看起来很完美的心跳机制,为什么会给移动网络带来信令风暴呢?原来,移动通信网络中由于用户众多、资源稀缺,每个用户都是动态占用资源,比如IP地址以及无线信道。每次发送心跳包,都需要移动通信网络为用户分配资源,分配的过程体现在信令的发送和接收上。一次心跳包的发送过程,牵涉的信令多达几十条。
随着互联网APP的普及,大量的终端周期性地发送心跳包,效果类似于IP网络中的DDOS,必然对移动通信网络设备带来冲击,造成拥塞等情况,这种现象就是信令风暴。

二、安卓和ios的长连接+心跳

    互联网推送消息的方式很常见,特别是移动互联网上,手机每天都能收到好多推送消息,经过研究发现,这些推送服务的原理都是维护一个长连接(要不不可能达到实时效果),但普通的socket连接对服务器的消耗太大了,所以才会出现像MQTT这种轻量级低消耗的协议来维护长连接,那么要如何维护长连接呢?

    在写之前,我们首先了解一下为什么Android维护长连接需要心跳机制,首先我们知道,维护任何一个长连接都需要心跳机制,客户端发送一个心跳给服务器,服务器给客户端一个心跳应答,这样就形成客户端服务器的一次完整的握手,这个握手是让双方都知道他们之间的连接是没有断开,客户端是在线的。如果超过一个时间的阈值,客户端没有收到服务器的应答,或者服务器没有收到客户端的心跳,那么对客户端来说则断开与服务器的连接重新建立一个连接,对服务器来说只要断开这个连接即可。那么在智能手机上的长连接心跳和在Internet上的长连接心跳有什么不同的目的呢?原因就在于智能手机使用的是移动无线网络,那么我们在讲长连接之前我们首先要了解无线移动网络的特点。

1.无线移动网络的特点:

    当一台智能手机连上移动网络时,其实并没有真正连接上Internet,运营商分配给手机的IP其实是运营商的内网IP,手机终端要连接上Internet还必须通过运营商的网关进行IP地址的转换,这个网关简称为NAT(NetWork Address Translation),简单来说就是手机终端连接Internet 其实就是移动内网IP,端口,外网IP之间相互映射。相当于在手机终端在移动无线网络这堵墙上打个洞与外面的Internet相连。


心跳机制:
心跳机制的原理很简单:客户端每隔N秒向服务端发送一个心跳消息,服务端收到心跳消息后,回复同样的心跳消息给客户端。如果服务端或客户端在M秒(M>N)内都没有收到包括心跳消息在内的任何消息,即心跳超时,我们就认为目标TCP连接已经断开了。

轮询:
  短轮询:浏览器发起一个“询问”请求,服务器无论有无新数据,都立即响应(有就返回新数据,没有就返回一个表示’空’的自定义数据格式),一个HTTP连接结束。
  长轮询:长轮询的经典实现 —— Comet:基于 HTTP 长连接的“服务器推”技术
浏览器发起一个“询问”请求,当没有新数据时,服务器端并不立即响应,而是等待数据,当有新数据产生时,才向浏览器响应,一个HTTP连接结束。

长连接:
  服务器端发送完新数据也不断开连接,继续等待下一份新数据,除非超过了一定时限(即自定义的最大连接空闲时长,浏览器可以超时重连)

       然而改进的过程却不像我起初设想的那般容易,可以说一波三折,原因主要是nio读写都是字节流,LZ一开始依然通过ObjectOutputStream.writeObject直接向Socket服务端发送数据,然而问题出现了,每次从ByteBuffer解析出来字节流都不一样,LZ使出浑身解数,一个字节一个字节的读取啊,问题没有了,可是由于是长连接,数据怎么解析啊,查资料,找大神,最后一个网友说有可能是粘包和分包的问题,一时晕菜,LZ网络可是渣渣啊,行吧,恶补一番,想了解的童鞋可以看看这个。

       实现原理就像很多协议那样,自定义一套传输协议,比如消息长度(int型,4个字节)+消息体的方式,根据解析的消息长度定长解析消息内容,虽然最后证明LZ的问题不是由于粘包和分包造成的,但是LZ就这样歪打正着,给实现了!!!数据不正常的问题后来通过DataOutputStream和DataInputStream的方式也得到了解决。

      

有一个问题不知道大家注意过没有——为什么socket客户端在非正常断开后,服务器在很长一段时间内不会显示客户端断开?没有的话,大可以试试-。-


原因:

       如果是正常终止客户端进程,系统会对进程正在占用的资源进行回收。此时客户端所占用的socket端口会被释放,服务器端也会被告知对方断开socket连接了,因此终止与该客户端的连接。而如果客户端非正常断开(比如客户端主机突然断电),则客户端的操作系统会在第一时间产生中断,保护操作系统。哪个操作系统还会特意耗费时间去回收socket资源←_←。所以即便客户端明明已经断开了连接,但服务器却迟迟没办法知道客户端断开的消息。因此会在较长一段时间内不会告诉编程者“某客户端已断开”,即便你写了在客户端断开后立刻回收资源的代码。


解决:

       相信不少人都听说过“心跳包”吧。保持客户端与服务器长连接的话,心跳包是必不可少的。设定阈值n,心跳间隔时间T,当服务器在n*T的时间内没有听到客户端的心跳,那么就可以判定客户端“死亡”了,主动与它断开连接,回收资源。至于n和T怎么设置,纯看项目的需求和编程者的心情了。当然,心跳包的写法很多种,可以是服务器向客户端发心跳要求回应,也可以是客户端直接向服务器发心跳。不同的应用场景可能都不同。


血的经历:

       当网络延迟高于n*T的时候,唉……(曾经设置n=2,T=8s,然而网络延迟——20000ms……别问我这种奇迹怎么发生,你在局域网边上开上百个路由器就知道了)

TCP连接简介
当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,
当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,
连接的建立是需要三次握手的,而释放则需要4次握手,
所以说每个连接的建立都是需要资源消耗和时间消耗的


一、长连接与短连接
长连接: 指在一个TCP连接上可以连续发送多个数据包,
        在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接;
        一般需要自己做在线维持。 
短连接: 指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接;
        一般银行都使用短连接。 
        它的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段 

比如http的,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。 
其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态。

长连接与短连接的操作过程 
通常的短连接操作步骤是: 
  连接→数据传输→关闭连接;

而长连接通常就是: 
  连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接; 

这就要求长连接在没有数据通信时,定时发送数据包(心跳),以维持连接状态,
短连接在没有数据传输时直接关闭就行了

什么时候用长连接,短连接?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。
每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,
所以每个操作完后都不断开,下次次处理时直接发送数据包就OK了,不用建立TCP连接。

例如:数据库的连接用长连接, 
如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。

二、发送接收方式
1、异步 
报文发送和接收是分开的,相互独立的,互不影响。这种方式又分两种情况: 
(1)异步双工:接收和发送在同一个程序中,由两个不同的子进程分别负责发送和接收 
(2)异步单工:接收和发送是用两个不同的程序来完成。 

2、同步 
报文发送和接收是同步进行,既报文发送后等待接收返回报文。 
同步方式一般需要考虑超时问题,即报文发出去后不能无限等待,需要设定超时时间,
超过该时间发送方不再等待读返回报文,直接通知超时返回。
 
在长连接中一般是没有条件能够判断读写什么时候结束,所以必须要加长度报文头。
读函数先是读取报文头的长度,再根据这个长度去读相应长度的报文。

三. 单工、半双工和全双工
根据通信双方的分工和信号传输方向可将通信分为三种方式:
单工、
半双工、
全双工。

在计算机网络中主要采用双工方式,其中:
局域网采用半双工方式,
城域网和广域网采用全双年方式。   

1. 单工(Simplex)方式:
通信双方设备中发送器与接收器分工明确,只能在由发送器向接收器的单一固定方向上传送数据。
采用单工通信的典型发送设备如早期计算机的读卡器,典型的接收设备如打印机。   

2. 半双工(Half Duplex)方式:
通信双方设备既是发送器,也是接收器,两台设备可以相互传送数据,但某一时刻则只能向一个方向传送数据。
例如,步话机是半双工设备,因为在一个时刻只能有一方说话。   

3. 全双工(Full Duplex)方式:
通信双方设备既是发送器,也是接收器,两台设备可以同时在两个方向上传送数据。
例如,电话是全双工设备,因为双方可同时说话。

而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,
而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,
如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。
所以并发量大,但每个用户无需频繁操作情况下需用短连好。

总之,长连接和短连接的选择要视情况而定。

心跳包机制

   跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。
    在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项:SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。
    心跳包一般来说都是在逻辑层发送空的echo包来实现的。下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。
    其实,要判定掉线,只需要send或者recv一下,如果结果为零,则为掉线。但是,在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。
    在获知了断线之后,服务器逻辑可能需要做一些事情,比如断线后的数据清理呀,重新连接呀……当然,这个自然是要由逻辑层根据需求去做了。
    总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。


心跳检测步骤
1 客户端每隔一个时间间隔发生一个探测包给服务器
2 客户端发包时启动一个超时定时器
3 服务器端接收到检测包,应该回应一个包
4 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
5 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了