上图是一个通用消息处理序列图,我们接下来就将这个原型展开,进行一个比较全面的分析。
消息服务实现
消息服务,它提供了处理和消息相关的一组方法,比如我们发送一个 RPC
(远程过程调用)请求。这里使用 MessageService
来作为抽象名称,我觉得是因为比较容易进行泛化,特定性约束不强,只要和消息相关的操作,都可以以此来展开实现。因为 MTProto
的定义是一个非常强大的类,它能给所有消息相关操作提供它们想要的任何支持,这样的定义好坏是显而易见的。在目前的 MTProtoKit 中,大致提供了以下几个消息服务:
1、MTTimeSyncMessageService - 时间同步
在 Telegram 的协议中,每个消息标识都附带了系统时间信息,收到的消息标识符中是服务端时间,而发送的消息标识符中是客户端时间。客户端生成消息标识符的算法,在 MTSessionInfo 中如下:
- (int64_t)generateClientMessageId:(bool *)monotonityViolated
{
int64_t messageId = (int64_t)([_context globalTime] * 4294967296);
if (messageId < _lastClientMessageId)
{
if (monotonityViolated != NULL)
*monotonityViolated = true;
}
if (messageId == _lastClientMessageId)
messageId = _lastClientMessageId + 1;
while (messageId % 4 != 0)
messageId++;
_lastClientMessageId = messageId;
return messageId;
}
当服务端意识到和客户端时间相差较大,则会忽略掉客户端发送来的消息,而这个服务便是用来和服务端时间进行校准。实现逻辑也比较简单,它会主动向服务端发送若干个消息进行时间采样,最终去除相差最小和相差最大的两个采样来求平均值。
这个时间同步服务,是直接由 MTProto
调用的(requestTimeResync),所以这里的逻辑依赖关系有点紊乱,我们使用类图梳理一下:
这里有个明显的互相依赖,MTTimeSyncMessageService
是 MTProto
的观察者,并使用了它的相应方法;MTProto
亦是 MTTimeSyncMessageService
的观察者,也使用了它的相应方法;这里之所以这么设计,是因为同步服务必须依赖于 MTProto
提供的强有力后盾,但 MTProto
又必须要确保消息时间的准确性,于是乎就造成了这样的格局。
2、MTRequestMessageService - RPC 请求和响应
这是一个使用非常频繁的服务,主要是用来向服务端发送 RPC 请求,并负责处理超时、错误、回执等。
2.1 消息打包
这是一个比较有用的特性,每一个 MTRequest 都会携带一个它需要发送的消息数据,然后添加到 RPC
服务(MTRequestMessageService)中,此时 RPC 服务会请求 MTProto
进行事务传输,但 MTProto
需要进行一些另外的准备和检验操作,所以可能会晚点才能向 RPC
服务要求构建事务,这时候 RPC
服务中可能会积累多个 MTRequest
,于是在构建事务的时候,事务的 payload
里就会有多个消息。同理,MTProto
在请求真正的向外传输时,又有可能会积累多个需要传输的事务,因为底层传输支持也需要做一些其他额外的处理。
针对上诉情况,Telegram 的 MTProto
中有一个消息容器的概念,它可以将多个消息放置到一个容器里,一同发送到服务器,服务器亦会对消息容器里需要响应的消息进行打包响应。这样就减少了网络传输的次数,也提高了响应的及时性(减少了排队请求的可能性)。
2.2 依赖处理
针对上诉的打包特性,它隐性的引入了一个问题,也就是时序问题,有些消息是必须在某些消息前得到处理的。所以,Telegram 增加了消息依赖的特性,它可以指定某个消息必须在另外一个消息前得到执行,这会对并发处理的服务端有很好的提示,但必然的增加了客户端实现的复杂度。
2.3 超时管理
由于消息可能会被打包处理,所以在超时管理上亦会跟一般超时处理不同,首先会在真的进入发送阶段前进行检测,其次是在收到响应时再做检测。值得一提的是,这里超时时钟使用的是 MTAbsoluteSystemTime,它是一个取 CPU
频率计算的高精度时钟,以下是 精准时钟的实现 :
#import <MtProtoKit/MTTime.h>
#import <mach/mach_time.h>
CFAbsoluteTime MTAbsoluteSystemTime()
{
static mach_timebase_info_data_t s_timebase_info;
if (s_timebase_info.denom == 0)
mach_timebase_info(&s_timebase_info);
return ((CFAbsoluteTime)(mach_absolute_time() * s_timebase_info.numer)) / (s_timebase_info.denom * NSEC_PER_SEC);
}
2.4 错误处理
除了响应的 RPCError
之外,MTProto
在对消息进行标识符编码的时候,还会检查标识符的唯一性,因为标识符和系统时间息息相关,所以如果小于上个消息标识符,则说明唯一性被破坏了,亦说明了系统时间有问题。发生这样的情况,MTProto
会重置当前的 Session
,并进行时间同步,也就是使用了 MTTimeSyncMessageService
。而这样的消息,会在本次传输中被抛弃掉,切换完 Session
后,才会继续发送。
3、MTResendMessageService - 消息重传
这算是 MTProto
比较有特性的另一个服务,这里的消息重传,并不是指客户端发送消息出现错误而进行后续的重新请求,而是指当客户端向服务器发出 RPC
请求后,服务端检测到这是一个重复的请求(消息标识符相同),如果响应内容较小,服务端会直接返回结果,而如果响应内容较大,此时服务端会回馈一个 MTMsgDetailedResponseInfoMessage,如果想要取得相应结果,则需要使用该服务,将请求消息标识符重新发送到服务器。
这个服务和时间同步服务一样,是由 MTProto 直接使用的,涉及到的核心代码如下:
- (void)_processIncomingMessage:(MTIncomingMessage *)incomingMessage totalSize:(int)totalSize withTransactionId:(id)transactionId address:(MTDatacenterAddress *)address authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector {
// ... 略
if (shouldRequest)
{
[self requestMessageWithId:detailedInfoMessage.responseMessageId];
if (MTLogEnabled()) {
MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p will request message %" PRId64 "", self, _context, detailedInfoMessage.responseMessageId);
}
MTShortLog(@"[MTProto#%p@%p will request message %" PRId64 "", self, _context, detailedInfoMessage.responseMessageId);
}
else
{
[_sessionInfo scheduleMessageConfirmation:detailedInfoMessage.responseMessageId size:(NSInteger)detailedInfoMessage.responseLength];
[self requestTransportTransaction];
}
// ... 略
}
实例方法 requestMessageWithId :
- (void)requestMessageWithId:(int64_t)messageId
{
bool alreadyRequestingThisMessage = false;
for (id<MTMessageService> messageService in _messageServices)
{
if ([messageService isKindOfClass:[MTResendMessageService class]])
{
if (((MTResendMessageService *)messageService).messageId == messageId)
{
alreadyRequestingThisMessage = true;
break;
}
}
}
if (!alreadyRequestingThisMessage && ![_sessionInfo messageProcessed:messageId])
{
MTResendMessageService *resendService = [[MTResendMessageService alloc] initWithMessageId:messageId];
resendService.delegate = self;
[self addMessageService:resendService];
}
}
4、MTDatacenterAuthMessageService - 数据中心授权
这也是一个非常重要的服务,它和用户授权息息相关,首先我们要清楚什么是 DataCenter,也就是数据中心;可以简单的把一个数据中心就当成一台完整的服务器,我们可以对它进行发送任何合理的请求;Telegram 的数据中心遍布在全球各地,而它们之间的数据同步是对客户端透明的,客户端要做的就是选择一个最适合自身的数据中心;数据中心地址的查找,在 MTProtoKit
中被封装在了 MTDiscoverDatacenterAddressAction 中,而后由全局上下文 MTContext 进行调用。
那么这个授权服务,它所做的便是向特定的数据中心发出授权请求,完成一个授权的全过程;整个授权的加密过程都在这个服务中体现出来,它采用的是基于 nonce
的一个认证体质,在安全领域中,nonce
是指在一个特定的上下文中,仅仅只被使用一次的数。通过使用 nonce
,我们可以防御 Replay attack
(回放攻击)和 Chosen-Plaintext attack
(选择明文攻击);Telegram 同时使用了客户端 nonce
和服务端 nonce
,并且加入了 DH
值校验,所以安全程度是非常高的。大体流程如下图:
在这个服务类的具体实现里,很容易可以看来,它是一个状态机,随着授权环节的推进,当前状态进行相应的推进。而使用这个服务的,是另一个封装类 MTDatacenterAuthAction,和上面说过的那个数据中心查找类类似,它们都采用了 Command 设计模式
,也都是由全局上下文进行管理、调用。
5、MTTransport - 数据传输
这是所有数据传输的基础服务,它的主要职责即是传输和接受数据,并且还监听网络可用性变化。这算得上是一个抽象类,它只保留了 MTTcpTransport 一个子类实现,很显然,是基于特定协议的实现。
MTTransport 的设计也稍显复杂,虽然它是由 MTProto
直接使用的,但却是由全局上下文进行统一管理。在 MTTransport 之上还有另一个更高层级的抽象 MTTransportScheme,这个类是用来描述一种特定的传输格式,并且可以根据这个特定的格式构建出合适的 MTTransport。