基于Telegram二次開發(fā) --- MTProtoKit 消息事務(wù)

image.png

上圖是一個通用消息處理序列圖,我們接下來就將這個原型展開,進行一個比較全面的分析。

消息服務(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)系有點紊亂,我們使用類圖梳理一下:

image.png

這里有個明顯的互相依賴,MTTimeSyncMessageServiceMTProto 的觀察者,并使用了它的相應(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 值校驗,所以安全程度是非常高的。大體流程如下圖:

image.png

在這個服務(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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容