惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
雷峰网
雷峰网
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Spread Privacy
Spread Privacy
H
Hacker News: Front Page
PCI Perspectives
PCI Perspectives
Webroot Blog
Webroot Blog
罗磊的独立博客
H
Heimdal Security Blog
TaoSecurity Blog
TaoSecurity Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Last Week in AI
Last Week in AI
美团技术团队
Help Net Security
Help Net Security
The Hacker News
The Hacker News
C
Cisco Blogs
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
The Register - Security
The Register - Security
IT之家
IT之家
WordPress大学
WordPress大学
Jina AI
Jina AI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
H
Help Net Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Threat Research - Cisco Blogs
P
Proofpoint News Feed
NISL@THU
NISL@THU
爱范儿
爱范儿
The GitHub Blog
The GitHub Blog
Scott Helme
Scott Helme
V
Vulnerabilities – Threatpost
B
Blog
T
Tenable Blog
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
S
Security Affairs
小众软件
小众软件
Hacker News: Ask HN
Hacker News: Ask HN
Security Latest
Security Latest
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
W
WeLiveSecurity
A
Arctic Wolf
L
LINUX DO - 热门话题
Google DeepMind News
Google DeepMind News
M
MIT News - Artificial intelligence

一个工匠

AI 发展临界点 - 快上车 流式响应(Chat):HTTP 响应体字节流的读取与解析 iPhone 截屏高效翻译 Flash Open Terminal iPhone 侧载安装 Xcode Symbolic Debug AI 指北 投资风险收益评估 Mac 快速修改系统快捷键 Git Record passkey How to pay apple frameworks Mac - Command Line Tools DNS 的 CNAME 是如何工作的 WebRTC 的一些解释 SSE 指南 1.1.1.1、WARP 和 MASQUE 移动端换端方案与场景还原 设备发现 分辨率 - 像素密度 - HiDPI Mac 字体 ESP32 trump - 川普 币圈 - 虚拟货币 【旅游】富士山 【旅游】sakura(樱花) 【旅游】千岛湖 我的 2023 【Swift】中文词语纠错 计算机字符编码与内存编码 - Unicode、UTF8、String 从汇编角度理解 “函数调用栈” 和 “有栈协程” 在 Xcode 中更好的使用 Swift 通过 Swift Package 制作二进制库 Arc 浏览器 - 意外的惊喜 【Swift 三方源码 1】SwiftShell 高效的命令行工具 Swift 脚本开发环境搭建 rime - 小鹤双拼 【旅游】沿海旅游 (威海、青岛) 好记性远远不如烂笔头 高级语言小记 又一年高考 Vision 未来已来 致敬耗子哥 Socket 与 ICMP 对互联网隐私的一些想法 网络代理是如何工作的 (致敬 Surge) 又十年 - 至三十而立 双拼 - 少有的高回报效率技能 Vim 技巧 我的 2022 三年疫情五味杂陈 - 下 老梗 - 浏览器按回车后发生了什么 三年疫情五味杂陈 生活推荐清单 Shell 和进程 有计划的努力 内卷 不确定性 数学可以保障逻辑自洽 这些年我的软件清单 决定做一些改变 函数调用栈之彻底理解 锁 - 共享数据安全指↑ 江苏女有感 碎碎念 - 2022 碎碎念 - 2021 打算记录一些碎碎念 多重性格就是逆天改命 从 Core Foundation 看更大世界 TCP 数据传输过程分析 iOS main 文件中的 @autoreleasepool 的真实用途 多线程的难点在哪里 Objective-C 和 Runtime 早起打卡计划 ing 打比方 如果我的人生突然只剩下 8 小时... 胡说八道之雪粉 Array 和 LinkedList 的深刻认知 Swift struct (值类型) 与 class (引用类型) 区别及三种传参机制深刻关系和使用 逃离信用卡和花呗 Swift_let_var_struct_class 网聚人的力量 - 网易 绿皮火车 喝酒十之八九会误事 HKong 胡说八道之云服务 互联网网络传输协议理解坑点 CPU 的制作 我心里一直有一个念想 网络基础协议 为什么 32 位操作系统只能用 4G 内存空间 努力和天赋 洗牙 RAC-Api-Summer iOS 内存原理 Block 闭包简查记录 《白夜行》 谈谈事件驱动 老博新开 祭首
IM 和 Socket 的关系及 Heart 的必要性
海驴 · 2020-02-04 · via 一个工匠

很多互联网服务都会开通 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 超时临界点等。

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


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