IM和Socket的关系及Heart的必要性

很多互联网服务都会开通IM功能,如好友通讯,用户反馈,在线推送等。在微信未盛行,QQ大行其道的年代,IM还属于高深技术,目前已经进入寻常百姓家了。
其实IM本身整个技术流程从终端角度来看不是一个难以理解的问题。但因为诸多人对网络协议不了解,所以认为IM难以入门。

首先要说IM是一个什么样的形态,它和HTTP/S有什么不同。
HTTP解决了端到端通讯的问题,在互联网大放异彩的原因是浏览器的盛行。冲浪需要网络获取服务端资源,HTTP可以使得终端发起网络请求服务端返回资源结果实现资源互通。
我们的感知里面,终端发起网络请求,服务端回复网络请求,HTTP使命就完成了。所以很多人对于HTTP的认知是:一次性通讯,每次需要获取信息都要重新发起请求。
其实有这样的认知是完全正确的,因为HTTP的确就是这样设计的。这里要着重说到设计这个词,因为HTTP就是为了完成一次性通讯这么一个任务。而像HTTP这样的应用层协议还有非常多,比如FTP、RTMP等等。他们都是为了完成特定任务而实现的协议。所有应用层协议都是被设计过的,为了完成特定的使命。

但是随着社交关系的复杂,如何实现即时通讯又是摆在眼前的问题。HTTP是一次性通讯,请求返回后,终端和服务端两不相欠,也感知不到对方的存在。
如果我们想通过HTTP实现即时通讯,那就需要客户端每隔N秒(比如2s)向服务器发送一个请求,然后服务器返回新消息。这虽然很简陋,但是我就经历过这样的方案设计,我认为,在特定场景、功能、需求的前提下,这样的技术方案设计是完全合理的。
HTTP显然不适合大量用户下的即时通讯场景。在社交网络中,服务端需要在线数据统计,需要消息及时在线推送到终端,需要即使感知到终端连接是否中断好清理服务器资源,而这些,显然HTTP都无能为力。
那如何实现真正的即时通讯呢?真正的即时通讯和HTTP通讯在技术原理层面有哪些区别呢?

Socket是什么

很多人都认为Socket是一套协议。其实根本就没有Socket协议存在。但是话又说回来,Socket本身的确又有其他两个身份。如果有人非要说Socket是协议,那Socket就有三个身份了。

Socket库

互联网本来就是数据的传输。传输层是有专门的协议负责数据传输,就是TCP它们。
可是应用层HTTP怎么和传输层对接呢?就需要很多个接口。而这些接口处于一个库中,没错,这个库就叫做Socket库
Socket库提供了write、read接口用来从缓冲区读取数据,提供了connect接口用来建立连接,此外,Scoket还有很多其他接口如close、gethostbyname等。

1
2
3
这里要特别说明一下gethostbyname接口,因为它太重要了。gethostbyname是用来做域名解析用的,没错,域名转成IP是Scoket库提供的接口完成的。
所有网络请求,不管是局域网还是广域网,都需要知道对方的IP才能够发送消息,这是七层网络模型决定的。但是我们一般都只知道域名,在网络请求的时候,就需要做一次域名和IP的转换。
这个域名解析是操作系统来实现的,操作系统发现请求是域名,就调用gethostbyname接口进行系统中断,然后Socket库经过一系列的递归发送UDP数据报来查询域名对应的IP,最后操作系统返回该IP,应用层恢复系统中断,开始继续执行。

所以Socket库,就是一个Api接口的实现合集,它连接了应用层和传输层。应用层调用Socket库接口,把数据给到传输层,后面数据才能够发出去。

Socket套接字

互联网上两个终端(比如客户端和服务器)如果需要通讯,就需要知道对方的IP和Port(端口)。这是由网络七层模型决定的。
IP通过gethostbyname已经拿到了,Port应用层在请求的时候已经知道,比如HTTP默认80,HTTPS默认443,还有自定义8889等。
那知道对方的IP和Port后存储在哪里呢?就是一个文件里面,更接地气的说,叫套接字,Socket翻译过来也就是套接字。
终端要发送请求到服务端,首先需要在终端建立自己的套接字,存储服务端的IP和Port。服务端要接收终端的消息,也要在服务端建立一个套接字,存储终端的IP和Port。
终端发送HTTP消息给服务端后,服务端经过数据库查询等处理,要把数据返回给终端,就需要读取服务端的套接字拿到终端的IP和Port,数据通过七层模型,才能把消息回复给终端。

套接字除了找到对方的IP和Port,还有其他用途吗?很多用途。套接字主要做信息存储,这也合符文件的本质。所以套接字里面存储了很多东西,通讯过程中需要的信息都会存储在套接字中。下面举两个例子:
一个例子是TCP三次握手。TCP本来就是一个传输通道层协议,在调用Socket库里面的connect接口的时候,开始进行三次握手协商。但是TCP仅仅是一个协议,握手握了几次肯定是需要内存或者磁盘存储的,这里就是把三次握手状态(如连接中,连接完成)存储到了套接字中。
更确切的说,是connect接口把三次握手状态存储到了套接字中。
因为TCP仅仅是一个传输层协议,用来传输数据,它的工作仅仅是更高效率的传输数据而已。而数据所表达的含义,TCP是不管的。握手状态明显是数据含义,相对TCP而言处理数据是业务层的工作,而connect这时就是TCP的业务方。
三次握手的状态写到套接字后,所有的数据传输,都会先从套接字中看一下当前握手状态,发现已经连接完成,那么就可以放心的发送数据了。
还有一个例子是TCP的窗口信息。TCP在稳定连接、顺序传输、丢包控制、流量控制、拥塞控制方面是碾压UDP的。上面的三次握手就是在做稳定连接的工作,而其他四个方面就是通过滑动窗口拥塞窗口实现的。在这两个窗口的实现里面,需要记录已经发送包的序号、对方已接收的包序号、当前还能发送包的量等等各种信息,通过这些信息,TCP实时更新自己的发送策略,做到了稳定性数据传输。
显然,套接字里面能存储很多东西,而且,套接字主要的用途,是为数据传输提供信息判断的能力。

Socket到底是什么?

其实上面Socket库、Socket套接字这两个身份,都不常被人提起,因为这两个身份比较基础,很多人不愿意去理解他们。
但是千万不要理解Socket是一个协议,这是无稽之谈。因为这两个身份很多人不愿意去理解,Socket协议又不存在,所以如果要向别人介绍什么是Socket,我更愿意叫它“Socket中间件”。这是我自己瞎起的名字,但是不会像Socket协议那样让人误解。

Socket库身份的时候,它是连接应用层和传输层的纽带,是一个中间件。
Socket套接字的时候,它保存了数据传输过程中的各种有效参数,也是一个中间件。

HTTP和Socket的关系

说HTTP和Socket的关系,其实说的就是HTTP和Socket库的关系。
和HTTP直接关联的是Socket库,HTTP要进行域名解析,数据传输,数据接收,断开连接的时候,都需要通过调用Socket库的相关接口才能操作下去。
这样说来,其实HTTP和TCP关联就不大了。为什么这么说呢?如果哪一天,HTTP说要用UDP协议,那么在调用Socket库相关接口的时候,传递一个参数告知使用UDP就可以了。
实际上,HTTP使用TCP协议的确是这么实现的,HTTP调用Socket库的时候,传递了一个参数告知使用TCP,然后才会建立TCP稳定连接

TCP、UDP和Socket的关系

说TCP、UDP和Socket的关系,其实说的就是它们和Socket套接字的关系。
因为Socket库把数据传递到传输层后,就不管了,后面都是传输层的事情了。传输层在进行三次握手、窗口策略等过程中,需要不断和Socket套接字进行存储和读取。
TCP和UDP都和Socket有一个共同的关系,那就是连接套接字的建立(UDP可以不建立,下面细说)。
当TCP需要传输数据的时候,就需要通过connect建立一个套接字,套接字里面有IP和Port,然后进行三次握手在套接字里面记录状态。
总体流程为:建立套接字->三次握手记录状态->发送数据流A->接收数据流A’->发送数据流B->接收数据流B’->销毁套接字,释放资源。
当UDP需要传输数据的时候,可以通过connect建立一个套接字,套接字里面有IP和Port,然后直接发送数据。
流程为:Socket()->connect->send(A)->recv(A’)->send(B)->recv(B’)->close。
但是UDP也可以不connect,下面是区别:
流程为:Socket()->sendto(A)->recvfrom(A’)->close->Socket()->sendto(B)->recvfrom(B’)->close。
UDP在connect后,可以建立套接字,这样不用每次都建立连接,发起请求的时候直接去套接字里面拿端口和IP。如果不connect,只能发送消息的时候,自行附带IP,使用sendto()接口。

即时通讯IM到底是啥?

所以即时通讯,就是编写业务层代码(自己写或者使用WebSocket等应用层协议),使用UDP或者TCP这样的传输层协议,然后通过Socket套接字确定对方的IP和Port,最后通过Socket库接口双向发送消息
这也就是IM的本质。
HTTP一直都是单向发送消息,比如客户端登陆或者调服务端Api。这都是单向的。
其实这个单向是宏观上看的,微观上,客户端请求,服务端返回,这其实也是双向的。
但是HTTP因为本身的业务关系,在收到请求后,发起请求的一方就主动关闭了通道,即销毁了双方的Socket套接字。所以服务端就没法再发消息给终端了,只能等着下次终端再次发起强求,才能再次返回数据给终端,如此反复。
所以HTTP不能做到双向通讯,是由HTTP本身协议特性决定的。如果HTTP不主动断开,那HTTP也能实现即时通讯实现双向发送消息

1
2
3
4
!!!HTTP1.0,消息往返后request主动关闭并销毁双方套接字,通道关闭。
!!!HTTP1.1,可以通过开启connection:keepalive,用于保持双方套接字。这个时候,因为通道还在,是可以做即时通讯用途的。
!!!HTTP2.0,在1.1的基础上,在传输层做更大程度优化,如多路复用、数据压缩等。这个时候,还是没有关闭销毁套接字,所以也可以做即时通讯使用。
!!!一般没有人使用HTTP来做即时通讯。但是实际上是可以通过HTTP来实现即时通讯的。

实际上,WebSocket就是这样实现的。WebSocket和HTTP一样属于应用层协议,WebSocket通过HTTP建立连接,然后不断开通道,继续使用TCP进行双向发送消息。

话再说回头,WebSocket能作为IM协议使用,其实是WebSocket协议帮我们做了业务层处理操作,如封包、粘包、调用Socket库Api(send()/recv()/connect()等)。
如果我们不使用WebSocket,我们就需要自行处理Socket库调用,数据收发,和封包粘包的处理等工作

好在各个平台开发方,都有比较出色等Socket三方库使用,它们就是封装了和Socket库交互的部分,但是封包粘包还是需要我们处理,因为这个时候没有HTTP或者WebSocket这样的业务方了,我们自己就是业务方,所以需要我们自己处理这些业务。

Heart要解决什么问题

说到IM,就不能不提心跳了。不管使用TCP、UDP还是WebSocket,心跳都是必不可少的。
那么心跳是要解决什么问题呢?
其实心跳总结来看,就是要解决IM的一个本质问题:维系长链接,保持高可用

要分析这个本质,就要先说明IM目前遇到了什么问题。总结来看,如下3点:

  1. 终端网络波动太大。尤其移动端特别明显,进出电梯时候的网络屏蔽、网络供应商切换时候的IP更换、高速移动过程中基站的变更等等。这些都会导致套接字失效。前面说过套接字是即时通讯通道能够稳定连接的核心。网络波动会导致套接字中IP更换,服务端存储的套接字就失效了,需要销毁老的套接字,并创建新的套接字。
  2. NAT超时后映射会销毁,中断通路。官方已经发布公告,IPv4已经用完。本身IPv4在大陆就很紧缺。所以终端用户上网都会分配局域网IP,通过路由器NAT中转到公网去。此时路由器内部会记录一个IP和Port的映射表。但是运营商为了节省资源和降低网关压力,对一段时间没有数据收发的连接,会清理掉。这时候终端和服务端都是完好的,但是中间的通路却断了。
  3. 企业等防火墙会在一段时间后关闭网络包传输。这个和NAT超时类似,防火墙会定时对一段时间没有数据收发对连接进行关闭,如5分钟。

以上3个问题,会导致终端和服务端无法正常通讯,导致连接不可维系。

所以心跳,就为了处理上面情况带来的影响,使得终端和服务端可以及时做如下处理:

  1. 让客户端尽快重连,保持通路。这个特别重要,如果发现中断,一定要及时重连,不然就收不到消息了。
  2. 可以让服务端尽快感知到连接的变化,清理Socket资源。这个是保持服务端高可用的有效途径。
  3. 告知中间设备,目前连接活跃。因为有心跳在,就有数据包通过,那么NAT就不会被清理,防火墙也不会屏蔽。

具体心跳的实现方式也是多样的,有很多种,比如下面这些:

  1. 应用层发送定时心跳。定时30s、1分钟、2分钟、4分钟这样的固定心率或者动态心率都有。服务器判断长时间没有心跳则销毁Socket等资源
  2. 开启传输层TCP Keepalive。TCP本身自带keep alive,默认关闭状态。keep alive默认每隔2小时发送一次传输层心跳,失败重试9次,超时时间为75s,当然TCP的心跳可以调整。但是TCP keep alive有一个致命点,因为不需要应用层参与,数据包到达传输层即返回,和ping有些类似(ping都不过传输层),所以只能判断服务端内核层是否正常响应,但业务层是否崩溃不可知。如果服务器因为大批量任务处理导致拒绝服务了,TCP keep alive是检测不到的。但是TCP keep alive对于感知网络是否正常非常有用,如上面的NAT和防火墙等。
  3. TCP Keepalive和应用层心跳配合使用,一个用来判断网络,一个用来判断业务服务器。这样可以对Socket中断进行debug,以判断是否是中间通道被关闭导致的连接中断。
  4. 智能心跳。频繁的发起心跳会导致电量、流量等浪费,智能心跳可以平衡 NAT 超时和资源节约。智能心跳根据网络环境自动调整心跳方案如动态心率、通过使用二分法等算法方案逐步逼近NAT超时临界点等。

所以心跳,就是为了解决网络波动、中间设备关闭通道、网线插拔、机器断电等等意外导致的链路中断。


性话题,是对人性非常有考验的话题。
产品依靠性话题可以快速提升流量,朋友因为性话题可以快速拉近距离。
经常谈性的人大部分不色,闭口不言的人却又心怀鬼胎。