与更新合作
当客户端被主动使用时,会发生影响当前用户的事件,用户必须尽快了解,例如接收到新消息时。为了消除客户端定期下载这些事件的需求,存在一种更新传递机制,服务器通过其与客户端的可用连接向用户发送通知。
订阅更新
更新事件会发送给授权用户,连接到最后一个活跃连接(下载/上传文件所需的连接除外)。
因此,要开始接收更新,客户端需要启动连接并调用 API 方法,例如获取当前状态。
确保始终忽略来自未加密连接的更新(即握手完成前)。
如果连接已加密,但会话尚未登录或已被登出,则只能处理以下更新:
- updateLoginToken - 用于二维码登录
- 更新SentPhoneCode - 用于付费短信码登录
- updateDcOptions - 必须应用的数据中心连接选项更改
- updateConfig - 服务器端配置发生了变化;客户端应使用 help.getConfig 和 help.getAppConfig 重新获取配置。
- 更新LangPackTooLong,更新LangPack - 本地化包更新
事件序列
所有事件都以 TL 序列化的 Updates 对象的形式从套接字接收,这些对象可选择性地以 gzip 压缩方式进行,类似于对查询的响应。
每个更新对象可以包含单个或多个更新对象,代表不同的事件发生。
为了准确地应用所有更新,并确保没有漏接或重复应用更新,Update构造函数中有属性,更新构造函数中有属性(带有)或属性。客户端必须结合本地存储状态使用这些属性值,正确应用新更新。seqptspts_countqts
当更新序列出现缺口时,必须通过调用API方法来填补。更多内容如下 »
更新顺序
如前所述,每个带更新的有效载荷都有一个TL类型Updates。从下面的模式可以看出,这种类型有多个构造函数。
updatesTooLong#e317af7e = Updates; updateShort#78d4dec1 update:Update date:int = Updates; updateShortMessage#313bc7f8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:long message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates; updateShortChatMessage#4d6deea5 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:long chat_id:long message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates; updateShortSentMessage#9015e101 flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates; updatesCombined#725b04c3 updates:Vector<Update> users:Vector<User> chats:Vector<Chat> date:int seq_start:int seq:int = Updates; updates#74ae4240 updates:Vector<Update> users:Vector<User> chats:Vector<Chat> date:int seq:int = Updates;updatesTooLong 表示待处理事件太多,无法推送到客户端,因此需要手动获取。
updateShort 构造子中的事件通常优先级较低,且会向大量用户广播,例如其中一位聊天参与者开始在一场大型对话中输入文本(updateChatUserTyping)。
updateShortMessage、updateShortSentMessage 和 updateShortChatMessage 构造符虽然多余,但能显著减少 90% 更新的传输消息大小。收到后应转换为 updateShort。
剩余的两个构造函数更新和更新Combined 是更新序列的一部分。它们都带有属性,表示生成更新后的远程更新状态,表示数据包中第一个更新生成后的远程更新状态。对于更新,该属性省略,因为假设它始终等于。seqseq_startseq_startseq
消息相关事件序列
每个与消息框相关的事件(消息创建、编辑消息、删除消息等)都通过独特的自动递增标识,或者在秘密聊天更新的情况下,某些机器人更新等。ptsqts
每个消息框都可以被视为某个服务器端数据库表,用于存储与之相关的消息和事件。 所有盒子完全独立,每个点的序列只绑定在一个盒子上(见下文)。
Update 对象可能包含多个事件的信息(例如 updateDeleteMessages)。 这就是为什么所有单次更新可能都有参数表示收到的更新中包含的事件数(有些例外,在这种情况下,被视为)。pts_countpts_count0
每个信道和超群都有其消息框和事件序列;私密聊天和单个用户的基本群组还有另一个常见的事件序列。
秘密聊天、某些机器人事件和其他类型的更新都有另一个常见的次要事件序列。
总结一下,客户端必须确保以下序列的完整性,以便正确处理更新:
-
更新序列(序列)
- 通用消息框序列(pts)
- 次级事件序列(QTS)
- 信道消息框序列1(点数)
- 信道消息框序列2(点数)
- 信道消息框序列3(点数)
- 诸如此类......
取物状态
公共更新状态由更新表示。州级制造商。 用户首次登录时,需要调用 updates.getState 以存储最新的更新状态(这不是绝对的初始状态,只是当前时间的最新状态)。 常见的更新状态也可以从 updates.differenceTooLong 获取。
信道更新状态简单地表示为事件序列的 :首次登录时,初始信道状态可以通过获取对话构造器获取对话,也可以从完整信道信息中获得,或者作为 updateChannelTooLong 的更新接收。pts
次级更新状态由秘密事件序列的 表示,包含在更新中。公共更新状态的状态。qts
更新序列状态由 和 表示,该序列包含在更新中。公共更新状态的状态。dateseq
更新处理
Telegram客户端的更新处理包括接收事件,确保无漏洞和事件遗漏,基于对应事件序列的本地存储状态,然后根据接收到的参数更新本地存储状态。
当客户端收到带有序列化更新的有效载荷时,首先需要遍历所有嵌套的 Update 对象,检查它们是否属于任何消息框序列(have 或参数)。这些更新需要根据对应的本地状态和新 / 值分别处理。详情见下文 »ptsqtsptsqts
在处理完消息框更新后,如果还有其他更新,客户端需要针对 来处理。详情见下文 »seq
pts:检查并应用
这里,将是本地州,是偏远州,是更新中的事件数量。local_ptsptspts_count
- 如果 ,则可以应用更新。local_pts + pts_count === pts
- 如果 ,更新已经应用,必须忽略。local_pts + pts_count > pts
- 如果是,那就有更新缺口需要填补。local_pts + pts_count < pts
例如,假设客户端的信道本地状态如下:123456789
local_pts = 131现在假设来自频道的更新NewChannelMessage被接收到,且 。 由于 ,自上次存储状态以来的事件总数实际上等于 :这意味着可以安全地接受更新并应用远程:123456789pts = 132pts_count=1local_pts + pts_count === ptspts_countpts
local_pts = 132自从:
- pts表示新信道消息事件生成后的服务器状态
- pts_count表示新频道更新中的事件数量
- 在新信道消息事件生成前的服务器状态必须是:,实际上等于我们的本地状态。pts_before = pts - pts_count = 131
现在假设来自频道的更新NewChannelMessage被接收到,且 。 由于(),更新被跳过,因为我们已经处理过这次更新(事实上,当前更新也是同一更新设置的,但由于网络问题或其他原因被重新发送了两次)。123456789pts = 132pts_count=1local_pts + pts_count > pts133 > 132local_pts
现在假设从通道收到的更新删除ChannelMessages为 和 。 由于(),这意味着错过了更新,必须恢复该间隙。123456789pts = 140pts_count=5local_pts + pts_count < pts137 < 140
秘密聊天与机器人
整个过程在秘密聊天和某些机器人更新中非常相似,但用一个代替了事件,事件从未被分组,因此假设总是等于1。qtsptsqts_count
seq:检查并应用
在处理接收到的更新和更新组合时,顶层有四种可能的情况:
- 如果 ,可以应用更新:这是针对未排序的更新的特殊情况,应立即应用。seq_start === 0
- 如果 ,则可以应用更新。local_seq + 1 === seq_start
- 如果,更新已经应用,必须忽略。local_seq + 1 > seq_start
- 如果 ,则存在更新缺口必须填补(updates.getDifference 必须像使用常见和秘密事件序列一样)。local_seq + 1 < seq_start
如果应用了更新,本地更新状态必须通过(除非是0)和构造函数来更新。seqdate
对于其他所有更新类型的构造子,无需检查或更改本地状态。seq
恢复间隙
为此,必须调用 updates.getDifference(公共/秘密状态)或 updates.getChannelDifference(通道状态),并调用相应的本地状态。
在以下情况下,需要通过上述方法手动获取更新:
-
启动时,只需调用 updates.getDifference,以获取客户端离线时收到的更新(最好带有一些标志以降低服务器负载,详见方法文档)。
updates.getChannelDifference 启动时不需要手动调用所有频道。
相反,updates.getChannelDifference 将由一组 updateChannelTooLong 更新自动触发(仅针对需要补上的频道),这些更新由 update.getDifference 调用返回。 - 同步丧失:序列/点/量子(如上所述)出现缺口。在这种情况下,等待最多0.5秒并中止同步,以防有新更新来填补空缺,可能会很有用。
- 服务器端会话丢失:客户端收到新的会话创建通知。这可能是由于MTProto服务器的垃圾回收或服务器重启引起的。
- 错误更新:客户端无法反序列化接收到的数据。
- 不完整更新:客户端缺少来自某个缩短构造函数(如 updateShortChatMessage 等)关于聊天/用户的数据。
- 长时间无更新:15分钟或更长时间没有更新。
- 服务器请求客户端通过 updateChannelTooLong 或 updatesTooLong 获取差额。
当调用 updates.getDifference 如果响应 updates.differenceSlice 构造函数时,整个差异无法一次性接收。中间状态intermediate_state必须保存在客户端,并且必须重复查询,使用中间状态作为当前状态。
要获取通道的更新差值,可以使用 updates.getChannelDifference。
如果差异过大无法一次性接收,则不设置结果标志(参见文档)。
中间状态(由pts表示)必须保存在客户端,并且必须重复查询,并以中间状态为当前状态。final
为了性能和更好的用户体验,客户端可以设置最大间隙大小以填充:可以使用参数 of updates.getDifference 和参数 for updates.getChannelDifference。pts_total_limitlimit
如果间隙过大且需要获取的更新过多,则会返回一个构造函数。此时客户端必须重新获取该状态,重新开始从该状态获取更新,并按照此处的指示操作。*TooLong
建议对通道及其他用途使用限制。10-1001000-10000
如果返回的差异为 ,请不要重新调用 updates.getChannelDifference,除非用户已打开通道/超级组。final
订阅频道/超级群组更新
API 会自动为用户/机器人所属的通道/超级组发送被动更新(即作为套接字中的独立 Updates 构造子)。
然而,客户端(仅限用户账户)还应定期调用 updates.getChannelDifference,针对用户当前正在观看的频道和超级群组(即在一个或多个标签页/窗口中明确打开的频道/超级组)。
如果返回的差值为非-,则应立即调用该方法,并使用新参数。final
如果返回的差值为 ,且用户仍在查看超级组/信道的消息(即通过不同的标签页/窗口),则应在几秒后(如果指定了标志,否则在1秒后)重新调用。finaltimeout
该机制也可用于被动接收来自我们非成员的频道或超级群组的更新:如果指定的频道或超级群是公开的,或者因 chatInvitePeek 而暂时私密但暂时可用,API 将开始被动发送更新(即作为独立更新)在套接字中,正如我们已经加入的普通通道/超组一样,构造子对所有已登录的会话进行,只要任何会话每秒周期性调用更新。getChannelDifference(方法每秒返回一次,或者如果返回值中缺少标志则每秒返回一次,如果返回的差异为非-则立即调用新参数)。timeouttimeoutfinal
客户端应停止在用户关闭通道/超级组后进行 updates.getChannelDifference 轮询:只有当用户是通道/超级组成员时,API 才会继续发送被动更新。
客户端还应将使用上述机制短轮询的信道/超群最多限制为10个(即用户在11个不同信道上打开11个窗口,只需用updates.getChannelDifference短轮询前10个)。
示例实现
实现还必须注意延迟通过套接字接收的更新,同时填补事件序列和更新序列中的空隙,同时避免在同一序列中填补空隙。
示例实现:tdlib,MadelineProto。
一个有趣且简单的实现方式是运行后台循环,比如在 MadelineProto »。
关于更新的推送通知
如果客户端在事件发生时没有活跃连接,推送通知也会非常有用。