电报 MTProto 服务消息
对 RPC 查询的响应
对 RPC 查询的响应通常按如下方式封装:
rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult;
此处的 req_msg_id 是对方发送的包含 RPC 查询的消息的标识符。这样,接收方就知道结果是对特定 RPC 查询的响应。同时,此响应也作为对方已收到 req_msg_id 消息的确认。
请注意,对 RPC 查询的响应也必须得到确认。通常情况下,这与下一条消息的传输同时进行(该消息可能附带一个容器,其中包含带有确认信息的服务消息)。
RPC 错误
响应任何 RPC 查询而返回的结果字段也可能包含以下格式的错误消息:
rpc_error#2144ca19 error_code:int error_message:string = RpcError;
取消 RPC 查询
在某些情况下,客户端不希望接收已发送的 RPC 查询的响应,例如,响应过长导致链路容量不足,客户端决定放弃接收。简单地中断 TCP 连接并不会奏效,因为服务器会在第一时间重新发送缺失的响应。因此,客户端需要一种方法来取消接收 RPC 响应消息,即在实际接收之前确认已收到,这样可以稳定服务器并防止其重新发送响应。然而,客户端在收到响应之前并不知道 RPC 响应的 msg_id;它唯一知道的是 req_msg_id,即相关 RPC 查询的 msg_id。因此,需要使用一种特殊的查询:
rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;
该查询的响应将以 rpc_result 包裹的消息形式返回,并需要确认:
rpc_answer_unknown#5e2ad36e = RpcDropAnswer; rpc_answer_dropped_running#cd78e586 = RpcDropAnswer; rpc_answer_dropped#a43ad8b7 msg_id:long seq_no:int bytes:int = RpcDropAnswer;
如果服务器对传入的 req_msg_id 没有记忆(例如,该请求已被响应),则使用第一个版本的响应。如果在 RPC 查询处理过程中取消了响应(此时 RPC 查询本身仍在完全处理中),则使用第二个版本的响应;在这种情况下,也会返回相同的 rpc_answer_dropped_running 响应以响应原始查询,并且这两个响应都需要客户端确认。最后一个版本表示 RPC 响应已从服务器的传出队列中移除,并且其 msg_id、seq_no 和字节长度已发送给客户端。
请注意,rpc_answer_dropped_running 和 rpc_answer_dropped 是对服务器已收到原始查询(即我们希望忽略的响应)的确认。此外,与任何 RPC 查询一样,对 rpc_drop_answer 的任何响应都是对 rpc_drop_answer 本身的确认。
除了使用 rpc_drop_answer 之外,还可以在连接重置后创建一个新会话,并通过 destroy_session 删除旧会话。
与查询、更改和接收其他消息的状态相关的消息
请参阅移动协议:关于消息的服务消息
请求提供几种未来的盐
客户端可以随时向服务器请求若干(1 到 64 个)未来的服务器盐值及其有效期。服务器盐值存储在持久内存中后,即使客户端切换会话,也可以使用它们在未来发送消息(服务器盐值附加到授权密钥,而不是特定于会话)。
get_future_salts#b921bd04 num:int = FutureSalts; future_salt#0949d9dc valid_since:int valid_until:int salt:long = FutureSalt; future_salts#ae500895 req_msg_id:long now:int salts:vector<future_salt> = FutureSalts;
客户端必须检查响应中的 req_msg_id 是否与 get_future_salts 查询的 msg_id 一致。服务器最多返回 num 个未来服务器盐值(可能少于 num 个)。此响应是对查询的确认,本身无需客户端确认。
Ping 消息(PING/PONG)
ping#7abe77ec ping_id:long = Pong;
响应通常会返回到同一连接:
pong#347773c5 msg_id:long ping_id:long = Pong;
这些信息不需要确认。Pong 消息仅在收到 Ping 消息后才会发送,而 Ping 消息可以由任何一方发起。
延迟连接关闭 + PING
ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;
其工作原理类似于 ping 操作。此外,服务器收到此消息后会启动一个计时器,该计时器会在 disconnect_delay 秒后关闭当前连接,除非收到相同类型的新消息,此时所有之前的计时器都会自动重置。例如,如果客户端每 60 秒发送一次 ping 消息,则可以将 disconnect_delay 设置为 75 秒。
请求销毁会话
客户端使用此参数通知服务器,它可能会忘记属于同一用户(即具有相同 auth_key_id)的不同会话的数据。此参数应用于当前会话的结果未定义。
destroy_session_ok#e22045fc session_id:long = DestroySessionRes; destroy_session_none#62d350c9 session_id:long = DestroySessionRes; ---functions--- destroy_session#e7512126 session_id:long = DestroySessionRes;
新会话创建通知
服务器会通知客户端,为了处理客户端消息,需要创建一个新的会话(从服务器的角度来看)。如果之后服务器在同一会话中收到 msg_id 更小的消息,也会针对该 msg_id 生成类似的通知。对于较大的 msg_id 值,则不会生成此类通知。
new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = NewSession
unique_id 参数由服务器在每次创建(重新)会话时生成。
客户端必须确认收到此通知。例如,客户端需要了解从服务器接收的长轮询通知流中实际上存在“中断”(用户可能在一段时间内未能收到通知)。
请注意,服务器可能会单方面销毁(关闭)所有现有的客户端会话及其所有待处理的消息和通知,且不会发送任何通知。例如,如果会话长时间处于非活动状态,导致服务器内存不足,就会发生这种情况。如果客户端之后决定使用已被服务器遗忘的旧会话向服务器发送新消息,则会生成“已创建新会话”的通知。客户端应妥善处理此类情况。
容器
容器是包含多个其他消息的消息。它用于同时传输多个 RPC 查询和/或服务消息,可以使用 HTTP、TCP 甚至 UDP 协议。容器只能由另一方整体接受或拒绝。
简单容器
一个简单的容器可以承载如下多条消息:
msg_container#73f1f8dc messages:vector message = MessageContainer;
这里的消息指的是任何消息,包括其长度和 msg_id:
message msg_id:long seqno:int bytes:int body:Object = Message;
bytes是消息体序列化所占的字节数。容器内所有消息的 msg_id 必须小于容器自身的 msg_id。容器不需要确认,且不能包含其他简单容器。消息重发时,可以以不同的方式组合成容器,也可以单独发送。
MTProto 容器最多可以包含10248192 条消息。客户端应将确认、状态请求和消息重发请求分组到三个独立的msgs_ack »、msgs_state_req »、msg_resend_req »服务消息中,每个消息最多包含 8192 个 ID。
也允许使用空容器。例如,当 http_wait 中指定的超时时间到期,且没有消息要传输时,服务器会使用空容器来响应 HTTP 请求。
邮件副本
在某些情况下,需要重新发送 msg_id 已失效的旧邮件。这时,它会被包装在一个副本容器中:
msg_copy#e06046b2 orig_message:Message = MessageCopy;
一旦收到消息,处理方式如同包装器不存在一样。但是,如果确定已收到消息 orig_message.msg_id,则不会处理新消息(同时,系统会确认新消息和 orig_message.msg_id)。orig_message.msg_id 的值必须小于容器的 msg_id。
目前还没有使用这种方法,因为可以将旧消息包装在一个简单的容器中,就能达到同样的效果。
打包对象
用于将任何其他对象(或者更确切地说,是其序列化版本)替换为其已归档(gzip 压缩)的表示形式:
gzip_packed#3072cfa1 packed_data:string = Object;
目前,它支持在 RPC 响应正文中传递(即作为 rpc_result 中的结果),并由服务器针对有限数量的高级查询生成。此外,它还可用于在客户端和服务器之间传输非服务消息(即 RPC 查询)。
HTTP 等待/长轮询
以下这种无需确认的特殊服务查询(必须仅通过 HTTP 连接传输)用于使服务器将来能够使用 HTTP 协议向客户端发送消息:
http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait;
当收到此类消息(或包含此类消息的容器)时,服务器会等待max_delay几毫秒,然后将所有持有的消息转发给客户端(如果会话中至少有一条消息排队,则可将其放入一个容器中,并可添加确认信息);否则,服务器会等待不超过max_wait几毫秒,直到收到此类消息。如果始终没有收到消息,则发送一个空容器。
该max_delay参数表示从本次会话的第一条消息发出到发送 HTTP 响应之间经过的最大毫秒数。该wait_after参数的工作原理如下:服务器在收到特定会话的最新消息后,会再等待wait_after几毫秒,以防有更多消息。如果没有其他消息,则发送结果(包含所有消息的容器)。如果有更多消息出现,则wait_after重置计时器。
同时,该max_delay参数的优先级高于wait_after,并且该参数max_wait的优先级高于max_delay。
此消息不需要响应或确认。如果通过 HTTP 传输的容器包含多个此类消息,则行为未定义(实际上,将使用最后一个参数)。
如果http_wait容器中不存在,则使用默认值max_delay=0(毫秒)、wait_after=0(毫秒)和(毫秒)。max_wait=25000
如果客户端 ping 服务器需要很长时间,那么将其设置max_delay为与 ping 时间相当的值可能就有意义了。
销毁永久授权密钥
destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes; destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes; destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes; ---functions--- destroy_auth_key#d1435160 = DestroyAuthKeyRes;
destroy_auth_key当不再需要永久身份验证密钥时,例如用户注销后,就应该调用此方法。