电报客户端优化

简化消息送达确认

当服务器为一条外发消息分配了标识符后,该消息即可被视为已发送。通常情况下,客户端会从 `messages.sendMessage`方法的返回结果中得知消息已发送。MTProto 服务器提供了一种“快速确认”机制。收到此类确认后,客户端可以确定服务器已完全接收了对 `sendMessage` 方法的调用并将其放入了处理队列,从而可以通知用户消息已成功送达。但客户端可能永远无法收到服务器的实际响应(例如连接中断;或者应用程序在错误的时间重启)。为了正确处理这些情况,可以使用服务器在调用`updates.getDifference`时生成的特殊类型的通知:`updateMessageID`。处理此通知时,客户端可以使用`random_id`标识符将先前发送的消息与已送达服务器的消息关联起来。如果在对先前发送的消息调用`updates.getDifference`时未收到此类通知,则必须将该消息标记为未送达。

服务器盐

服务器盐值是一个 64 位数字,会添加到每条发送和接收的消息中。目前,单个盐值的有效期为 1 小时,过期后将被视为无效,服务器会对所有包含该盐值的消息返回错误。错误信息中会包含正确的盐值,可以立即用于发送消息。因此,如果客户端连接服务器的频率低于每小时一次,则在收到新的盐值之前总会有一段时间的等待时间。为了提高性能,我们提供了一个特殊的 `get_future_salts`方法,该方法会预先获取在调用后指定时间段内(例如 1 天)有效的盐值列表。每个盐值都指定了开始时间和结束时间。盐值之间有半小时的重叠时间。我们建议始终使用剩余有效期最长的盐值。

从服务器下载文件和上传数据

我们建议为这些任务创建单独的连接和会话。请记住,不再需要时必须删除多余的会话。使用多个连接下载文件(最好使用连接池)是合理的。而上传数据到服务器时,一个连接就足以获得最佳效果。

文件处理 API 的设计理念是分段执行数据操作。在最简单的实现中,将文件上传到服务器的过程如下:发送一个查询,等待响应,发送下一个查询,以此类推。这种方法无法优化网络资源的利用,ping 时间会受到很大影响。当两个或多个查询通过同一个连接连续执行时,上传和下载过程才能达到最佳状态。在这种情况下,上传到服务器的过程如下:

  1. 发送查询 1
  2. 发送查询 2
  3. 等待查询 1 的响应
  4. 发送查询 3
  5. 等待查询 2 的响应
  6. 发送查询 4
  7. ETC。

这将有助于减少 ping 延迟的影响,并最大限度地提高通道工作负载。

批量发送消息

有时客户端需要一次性向服务器发送多个消息方法调用,这些调用可能包含在单个消息中,也可能包含在多个连续消息中。然而,服务器可能会打乱这些请求的顺序(为了提高性能,查询由不同的服务器处理,这会给处理过程带来一定程度的随机性)。因此,在使用函数处理查询时,需要显式声明依赖关系。

invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;

实际上,这意味着在查询的开头填充 32 位数字0xcb9f372d和 64 位消息标识符,该标识符是当前查询所依赖的查询的标识符。

分组更新

生成更新(关于各种服务器事件的通知)并将其传递给客户端是系统的两个不同部分(分别是消息传递 API 和 MTProto)。MTProto 本身无法以任何方式修改发送给客户端的数据,而服务器 API 也无法响应客户端与 MTProto 之间的连接事件。设想这样一种情况:客户端断开连接(或被有意断开网络连接)一段时间。如果在建立新连接之前发生了很多不同的事件(例如联系人上线、发送正在输入的事件消息),那么当连接建立时,客户端将收到包含所有中间事件的大量数据,尽管其中大部分数据已经过时。引入消息分组机制是为了优化这种情况。如果发生新事件,而客户端尚未“收集”之前生成的更新,那么服务器 API 可以将它们合并成一个数据包。

客户端可以控制 MTProto 服务器何时开始判定连接已断开并开始分组(连接断开时越早判定越好)。此功能通过一种特殊的 Ping 消息类型ping_delay_disconnect实现,该消息指定服务器关闭当前连接并开始分组消息的延迟时间。

将ping_delay_disconnect的传输与其他重复性任务(例如更新用户状态 (account.updateStatus))的传输结合起来是有意义的。

设置输入状态

如果联系人不在线,则无需调用messages.setTyping。