上圖是一個通用消息處理序列圖移必,我們接下來就將這個原型展開室谚,進(jìn)行一個比較全面的分析。
消息服務(wù)實現(xiàn)
消息服務(wù)避凝,它提供了處理和消息相關(guān)的一組方法舞萄,比如我們發(fā)送一個 RPC
(遠(yuǎn)程過程調(diào)用)請求。這里使用 MessageService
來作為抽象名稱管削,我覺得是因為比較容易進(jìn)行泛化倒脓,特定性約束不強(qiáng),只要和消息相關(guān)的操作含思,都可以以此來展開實現(xiàn)崎弃。因為 MTProto
的定義是一個非常強(qiáng)大的類甘晤,它能給所有消息相關(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ù)端時間進(jìn)行校準(zhǔn)。實現(xiàn)邏輯也比較簡單仗处,它會主動向服務(wù)端發(fā)送若干個消息進(jìn)行時間采樣眯勾,最終去除相差最小和相差最大的兩個采樣來求平均值。
這個時間同步服務(wù)婆誓,是直接由 MTProto
調(diào)用的(requestTimeResync)吃环,所以這里的邏輯依賴關(guān)系有點(diǎn)紊亂,我們使用類圖梳理一下:
這里有個明顯的互相依賴旷档,MTTimeSyncMessageService
是 MTProto
的觀察者模叙,并使用了它的相應(yīng)方法;MTProto
亦是 MTTimeSyncMessageService
的觀察者鞋屈,也使用了它的相應(yīng)方法;這里之所以這么設(shè)計故觅,是因為同步服務(wù)必須依賴于 MTProto
提供的強(qiáng)有力后盾厂庇,但 MTProto
又必須要確保消息時間的準(zhǔn)確性,于是乎就造成了這樣的格局输吏。
2权旷、MTRequestMessageService - RPC 請求和響應(yīng)
這是一個使用非常頻繁的服務(wù),主要是用來向服務(wù)端發(fā)送 RPC 請求贯溅,并負(fù)責(zé)處理超時拄氯、錯誤、回執(zhí)等它浅。
2.1 消息打包
這是一個比較有用的特性译柏,每一個 MTRequest 都會攜帶一個它需要發(fā)送的消息數(shù)據(jù),然后添加到 RPC
服務(wù)(MTRequestMessageService)中姐霍,此時 RPC 服務(wù)會請求 MTProto
進(jìn)行事務(wù)傳輸鄙麦,但 MTProto
需要進(jìn)行一些另外的準(zhǔn)備和檢驗操作典唇,所以可能會晚點(diǎn)才能向 RPC
服務(wù)要求構(gòu)建事務(wù),這時候 RPC
服務(wù)中可能會積累多個 MTRequest
胯府,于是在構(gòu)建事務(wù)的時候介衔,事務(wù)的 payload
里就會有多個消息。同理骂因,MTProto
在請求真正的向外傳輸時炎咖,又有可能會積累多個需要傳輸?shù)氖聞?wù),因為底層傳輸支持也需要做一些其他額外的處理寒波。
針對上訴情況乘盼,Telegram 的 MTProto
中有一個消息容器的概念,它可以將多個消息放置到一個容器里影所,一同發(fā)送到服務(wù)器蹦肴,服務(wù)器亦會對消息容器里需要響應(yīng)的消息進(jìn)行打包響應(yīng)。這樣就減少了網(wǎng)絡(luò)傳輸?shù)拇螖?shù)猴娩,也提高了響應(yīng)的及時性(減少了排隊請求的可能性)阴幌。
2.2 依賴處理
針對上訴的打包特性,它隱性的引入了一個問題卷中,也就是時序問題矛双,有些消息是必須在某些消息前得到處理的。所以蟆豫,Telegram 增加了消息依賴的特性议忽,它可以指定某個消息必須在另外一個消息前得到執(zhí)行,這會對并發(fā)處理的服務(wù)端有很好的提示十减,但必然的增加了客戶端實現(xiàn)的復(fù)雜度栈幸。
2.3 超時管理
由于消息可能會被打包處理,所以在超時管理上亦會跟一般超時處理不同帮辟,首先會在真的進(jìn)入發(fā)送階段前進(jìn)行檢測速址,其次是在收到響應(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
在對消息進(jìn)行標(biāo)識符編碼的時候并炮,還會檢查標(biāo)識符的唯一性,因為標(biāo)識符和系統(tǒng)時間息息相關(guān)甥郑,所以如果小于上個消息標(biāo)識符逃魄,則說明唯一性被破壞了,亦說明了系統(tǒng)時間有問題壹若。發(fā)生這樣的情況嗅钻,MTProto
會重置當(dāng)前的 Session
皂冰,并進(jìn)行時間同步,也就是使用了 MTTimeSyncMessageService
养篓。而這樣的消息秃流,會在本次傳輸中被拋棄掉,切換完 Session
后柳弄,才會繼續(xù)發(fā)送舶胀。
3、MTResendMessageService - 消息重傳
這算是 MTProto
比較有特性的另一個服務(wù)碧注,這里的消息重傳嚣伐,并不是指客戶端發(fā)送消息出現(xiàn)錯誤而進(jì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ù)器茬高,我們可以對它進(jìn)行發(fā)送任何合理的請求;Telegram 的數(shù)據(jù)中心遍布在全球各地假抄,而它們之間的數(shù)據(jù)同步是對客戶端透明的,客戶端要做的就是選擇一個最適合自身的數(shù)據(jù)中心丽猬;數(shù)據(jù)中心地址的查找宿饱,在 MTProtoKit
中被封裝在了 MTDiscoverDatacenterAddressAction 中,而后由全局上下文 MTContext 進(jìn)行調(diào)用脚祟。
那么這個授權(quán)服務(wù)谬以,它所做的便是向特定的數(shù)據(jù)中心發(fā)出授權(quán)請求,完成一個授權(quán)的全過程由桌;整個授權(quán)的加密過程都在這個服務(wù)中體現(xiàn)出來为黎,它采用的是基于 nonce
的一個認(rèn)證體質(zhì)邮丰,在安全領(lǐng)域中,nonce
是指在一個特定的上下文中铭乾,僅僅只被使用一次的數(shù)剪廉。通過使用 nonce
,我們可以防御 Replay attack
(回放攻擊)和 Chosen-Plaintext attack
(選擇明文攻擊)炕檩;Telegram 同時使用了客戶端 nonce
和服務(wù)端 nonce
斗蒋,并且加入了 DH
值校驗,所以安全程度是非常高的笛质。大體流程如下圖:
在這個服務(wù)類的具體實現(xiàn)里泉沾,很容易可以看來,它是一個狀態(tài)機(jī)妇押,隨著授權(quán)環(huán)節(jié)的推進(jìn)跷究,當(dāng)前狀態(tài)進(jìn)行相應(yīng)的推進(jìn)。而使用這個服務(wù)的敲霍,是另一個封裝類 MTDatacenterAuthAction俊马,和上面說過的那個數(shù)據(jù)中心查找類類似,它們都采用了 Command 設(shè)計模式
色冀,也都是由全局上下文進(jìn)行管理潭袱、調(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
直接使用的卵佛,但卻是由全局上下文進(jìn)行統(tǒng)一管理杨赤。在 MTTransport 之上還有另一個更高層級的抽象 MTTransportScheme,這個類是用來描述一種特定的傳輸格式截汪,并且可以根據(jù)這個特定的格式構(gòu)建出合適的 MTTransport疾牲。