




























很多互联网服务都会开通 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 就有三个身份了。
互联网本来就是数据的传输。传输层是有专门的协议负责数据传输,就是 TCP 它们。
可是应用层 HTTP 怎么和传输层对接呢?就需要很多个接口。而这些接口处于一个库中,没错,这个库就叫做 Socket 库。
Socket 库提供了 write、read 接口用来从缓冲区读取数据,提供了 connect 接口用来建立连接,此外,Scoket 还有很多其他接口如 close、gethostbyname 等。
1 | 这里要特别说明一下gethostbyname接口,因为它太重要了。gethostbyname是用来做域名解析用的,没错,域名转成IP是Scoket库提供的接口完成的。 |
所以 Socket 库,就是一个 Api 接口的实现合集,它连接了应用层和传输层。应用层调用 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 套接字的时候,它保存了数据传输过程中的各种有效参数,也是一个中间件。
说 HTTP 和 Socket 的关系,其实说的就是 HTTP 和 Socket 库的关系。
和 HTTP 直接关联的是 Socket 库,HTTP 要进行域名解析,数据传输,数据接收,断开连接的时候,都需要通过调用 Socket 库的相关接口才能操作下去。
这样说来,其实 HTTP 和 TCP 关联就不大了。为什么这么说呢?如果哪一天,HTTP 说要用 UDP 协议,那么在调用 Socket 库相关接口的时候,传递一个参数告知使用 UDP 就可以了。
实际上,HTTP 使用 TCP 协议的确是这么实现的,HTTP 调用 Socket 库的时候,传递了一个参数告知使用 TCP,然后才会建立 TCP 稳定连接。
说 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 () 接口。
所以即时通讯,就是编写业务层代码 (自己写或者使用 WebSocket 等应用层协议),使用 UDP 或者 TCP 这样的传输层协议,然后通过 Socket 套接字确定对方的 IP 和 Port,最后通过 Socket 库接口双向发送消息。
这也就是 IM 的本质。
HTTP 一直都是单向发送消息,比如客户端登陆或者调服务端 Api。这都是单向的。
其实这个单向是宏观上看的,微观上,客户端请求,服务端返回,这其实也是双向的。
但是 HTTP 因为本身的业务关系,在收到请求后,发起请求的一方就主动关闭了通道,即销毁了双方的 Socket 套接字。所以服务端就没法再发消息给终端了,只能等着下次终端再次发起强求,才能再次返回数据给终端,如此反复。
所以 HTTP 不能做到双向通讯,是由 HTTP 本身协议特性决定的。如果 HTTP 不主动断开,那 HTTP 也能实现即时通讯实现双向发送消息。
1 | !!!HTTP1.0,消息往返后request主动关闭并销毁双方套接字,通道关闭。 |
实际上,WebSocket 就是这样实现的。WebSocket 和 HTTP 一样属于应用层协议,WebSocket 通过 HTTP 建立连接,然后不断开通道,继续使用 TCP 进行双向发送消息。
话再说回头,WebSocket 能作为 IM 协议使用,其实是 WebSocket 协议帮我们做了业务层处理操作,如封包、粘包、调用 Socket 库 Api (send ()/recv ()/connect () 等)。
如果我们不使用 WebSocket,我们就需要自行处理 Socket 库调用,数据收发,和封包粘包的处理等工作。
好在各个平台开发方,都有比较出色等 Socket 三方库使用,它们就是封装了和 Socket 库交互的部分,但是封包粘包还是需要我们处理,因为这个时候没有 HTTP 或者 WebSocket 这样的业务方了,我们自己就是业务方,所以需要我们自己处理这些业务。
说到 IM,就不能不提心跳了。不管使用 TCP、UDP 还是 WebSocket,心跳都是必不可少的。
那么心跳是要解决什么问题呢?
其实心跳总结来看,就是要解决 IM 的一个本质问题:维系长链接,保持高可用。
要分析这个本质,就要先说明 IM 目前遇到了什么问题。总结来看,如下 3 点:
以上 3 个问题,会导致终端和服务端无法正常通讯,导致连接不可维系。
所以心跳,就为了处理上面情况带来的影响,使得终端和服务端可以及时做如下处理:
具体心跳的实现方式也是多样的,有很多种,比如下面这些:
所以心跳,就是为了解决网络波动、中间设备关闭通道、网线插拔、机器断电等等意外导致的链路中断。
性话题,是对人性非常有考验的话题。
产品依靠性话题可以快速提升流量,朋友因为性话题可以快速拉近距离。
经常谈性的人大部分不色,闭口不言的人却又心怀鬼胎。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。