
上圖是一個通用消息處理序列圖,我們接下來就將這個原型展開,進行一個比較全面的分析。
消息服務(wù)實現(xiàn)
消息服務(wù),它提供了處理和消息相關(guān)的一組方法,比如我們發(fā)送一個 RPC(遠程過程調(diào)用)請求。這里使用 MessageService 來作為抽象名稱,我覺得是因為比較容易進行泛化,特定性約束不強,只要和消息相關(guān)的操作,都可以以此來展開實現(xiàn)。因為 MTProto 的定義是一個非常強大的類,它能給所有消息相關(guān)操作提供它們想要的任何支持,這樣的定義好壞是顯而易見的。在目前的 MTProtoKit 中,大致提供了以下幾個消息服務(wù):
1、MTTimeSyncMessageService - 時間同步
在 Telegram 的協(xié)議中,每個消息標(biāo)識都附帶了系統(tǒng)時間信息,收到的消息標(biāo)識符中是服務(wù)端時間,而發(fā)送的消息標(biāo)識符中是客戶端時間??蛻舳松上?biāo)識符的算法,在 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;
}
當(dāng)服務(wù)端意識到和客戶端時間相差較大,則會忽略掉客戶端發(fā)送來的消息,而這個服務(wù)便是用來和服務(wù)端時間進行校準(zhǔn)。實現(xiàn)邏輯也比較簡單,它會主動向服務(wù)端發(fā)送若干個消息進行時間采樣,最終去除相差最小和相差最大的兩個采樣來求平均值。
這個時間同步服務(wù),是直接由 MTProto 調(diào)用的(requestTimeResync),所以這里的邏輯依賴關(guān)系有點紊亂,我們使用類圖梳理一下:

這里有個明顯的互相依賴,MTTimeSyncMessageService 是 MTProto 的觀察者,并使用了它的相應(yīng)方法;MTProto 亦是 MTTimeSyncMessageService 的觀察者,也使用了它的相應(yīng)方法;這里之所以這么設(shè)計,是因為同步服務(wù)必須依賴于 MTProto 提供的強有力后盾,但 MTProto 又必須要確保消息時間的準(zhǔn)確性,于是乎就造成了這樣的格局。
2、MTRequestMessageService - RPC 請求和響應(yīng)
這是一個使用非常頻繁的服務(wù),主要是用來向服務(wù)端發(fā)送 RPC 請求,并負責(zé)處理超時、錯誤、回執(zhí)等。
2.1 消息打包
這是一個比較有用的特性,每一個 MTRequest 都會攜帶一個它需要發(fā)送的消息數(shù)據(jù),然后添加到 RPC 服務(wù)(MTRequestMessageService)中,此時 RPC 服務(wù)會請求 MTProto 進行事務(wù)傳輸,但 MTProto 需要進行一些另外的準(zhǔn)備和檢驗操作,所以可能會晚點才能向 RPC 服務(wù)要求構(gòu)建事務(wù),這時候 RPC 服務(wù)中可能會積累多個 MTRequest,于是在構(gòu)建事務(wù)的時候,事務(wù)的 payload 里就會有多個消息。同理,MTProto 在請求真正的向外傳輸時,又有可能會積累多個需要傳輸?shù)氖聞?wù),因為底層傳輸支持也需要做一些其他額外的處理。
針對上訴情況,Telegram 的 MTProto 中有一個消息容器的概念,它可以將多個消息放置到一個容器里,一同發(fā)送到服務(wù)器,服務(wù)器亦會對消息容器里需要響應(yīng)的消息進行打包響應(yīng)。這樣就減少了網(wǎng)絡(luò)傳輸?shù)拇螖?shù),也提高了響應(yīng)的及時性(減少了排隊請求的可能性)。
2.2 依賴處理
針對上訴的打包特性,它隱性的引入了一個問題,也就是時序問題,有些消息是必須在某些消息前得到處理的。所以,Telegram 增加了消息依賴的特性,它可以指定某個消息必須在另外一個消息前得到執(zhí)行,這會對并發(fā)處理的服務(wù)端有很好的提示,但必然的增加了客戶端實現(xiàn)的復(fù)雜度。
2.3 超時管理
由于消息可能會被打包處理,所以在超時管理上亦會跟一般超時處理不同,首先會在真的進入發(fā)送階段前進行檢測,其次是在收到響應(yīng)時再做檢測。值得一提的是,這里超時時鐘使用的是 MTAbsoluteSystemTime,它是一個取 CPU 頻率計算的高精度時鐘,以下是 精準(zhǔn)時鐘的實現(xiàn) :
#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 錯誤處理
除了響應(yīng)的 RPCError 之外,MTProto 在對消息進行標(biāo)識符編碼的時候,還會檢查標(biāo)識符的唯一性,因為標(biāo)識符和系統(tǒng)時間息息相關(guān),所以如果小于上個消息標(biāo)識符,則說明唯一性被破壞了,亦說明了系統(tǒng)時間有問題。發(fā)生這樣的情況,MTProto 會重置當(dāng)前的 Session,并進行時間同步,也就是使用了 MTTimeSyncMessageService。而這樣的消息,會在本次傳輸中被拋棄掉,切換完 Session 后,才會繼續(xù)發(fā)送。
3、MTResendMessageService - 消息重傳
這算是 MTProto 比較有特性的另一個服務(wù),這里的消息重傳,并不是指客戶端發(fā)送消息出現(xiàn)錯誤而進行后續(xù)的重新請求,而是指當(dāng)客戶端向服務(wù)器發(fā)出 RPC 請求后,服務(wù)端檢測到這是一個重復(fù)的請求(消息標(biāo)識符相同),如果響應(yīng)內(nèi)容較小,服務(wù)端會直接返回結(jié)果,而如果響應(yīng)內(nèi)容較大,此時服務(wù)端會回饋一個 MTMsgDetailedResponseInfoMessage,如果想要取得相應(yīng)結(jié)果,則需要使用該服務(wù),將請求消息標(biāo)識符重新發(fā)送到服務(wù)器。
這個服務(wù)和時間同步服務(wù)一樣,是由 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 - 數(shù)據(jù)中心授權(quán)
這也是一個非常重要的服務(wù),它和用戶授權(quán)息息相關(guān),首先我們要清楚什么是 DataCenter,也就是數(shù)據(jù)中心;可以簡單的把一個數(shù)據(jù)中心就當(dāng)成一臺完整的服務(wù)器,我們可以對它進行發(fā)送任何合理的請求;Telegram 的數(shù)據(jù)中心遍布在全球各地,而它們之間的數(shù)據(jù)同步是對客戶端透明的,客戶端要做的就是選擇一個最適合自身的數(shù)據(jù)中心;數(shù)據(jù)中心地址的查找,在 MTProtoKit 中被封裝在了 MTDiscoverDatacenterAddressAction 中,而后由全局上下文 MTContext 進行調(diào)用。
那么這個授權(quán)服務(wù),它所做的便是向特定的數(shù)據(jù)中心發(fā)出授權(quán)請求,完成一個授權(quán)的全過程;整個授權(quán)的加密過程都在這個服務(wù)中體現(xiàn)出來,它采用的是基于 nonce 的一個認證體質(zhì),在安全領(lǐng)域中,nonce 是指在一個特定的上下文中,僅僅只被使用一次的數(shù)。通過使用 nonce,我們可以防御 Replay attack(回放攻擊)和 Chosen-Plaintext attack(選擇明文攻擊);Telegram 同時使用了客戶端 nonce 和服務(wù)端 nonce,并且加入了 DH 值校驗,所以安全程度是非常高的。大體流程如下圖:

在這個服務(wù)類的具體實現(xiàn)里,很容易可以看來,它是一個狀態(tài)機,隨著授權(quán)環(huán)節(jié)的推進,當(dāng)前狀態(tài)進行相應(yīng)的推進。而使用這個服務(wù)的,是另一個封裝類 MTDatacenterAuthAction,和上面說過的那個數(shù)據(jù)中心查找類類似,它們都采用了 Command 設(shè)計模式,也都是由全局上下文進行管理、調(diào)用。
5、MTTransport - 數(shù)據(jù)傳輸
這是所有數(shù)據(jù)傳輸?shù)幕A(chǔ)服務(wù),它的主要職責(zé)即是傳輸和接受數(shù)據(jù),并且還監(jiān)聽網(wǎng)絡(luò)可用性變化。這算得上是一個抽象類,它只保留了 MTTcpTransport 一個子類實現(xiàn),很顯然,是基于特定協(xié)議的實現(xiàn)。
MTTransport 的設(shè)計也稍顯復(fù)雜,雖然它是由 MTProto 直接使用的,但卻是由全局上下文進行統(tǒng)一管理。在 MTTransport 之上還有另一個更高層級的抽象 MTTransportScheme,這個類是用來描述一種特定的傳輸格式,并且可以根據(jù)這個特定的格式構(gòu)建出合適的 MTTransport。