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

image.png

上圖是一個通用消息處理序列圖移必,我們接下來就將這個原型展開室谚,進(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)紊亂,我們使用類圖梳理一下:

image.png

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

image.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市衙解,隨后出現(xiàn)的幾起案子阳柔,更是在濱河造成了極大的恐慌,老刑警劉巖蚓峦,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舌剂,死亡現(xiàn)場離奇詭異济锄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)霍转,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門荐绝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谴忧,你說我怎么就攤上這事很泊。” “怎么了沾谓?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵委造,是天一觀的道長。 經(jīng)常有香客問我均驶,道長昏兆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任妇穴,我火速辦了婚禮爬虱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腾它。我一直安慰自己跑筝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布瞒滴。 她就那樣靜靜地躺著曲梗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妓忍。 梳的紋絲不亂的頭發(fā)上虏两,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音世剖,去河邊找鬼定罢。 笑死,一個胖子當(dāng)著我的面吹牛旁瘫,可吹牛的內(nèi)容都是我干的祖凫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼酬凳,長吁一口氣:“原來是場噩夢啊……” “哼蝙场!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粱年,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎罚拟,沒想到半個月后台诗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體完箩,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年拉队,在試婚紗的時候發(fā)現(xiàn)自己被綠了弊知。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡粱快,死狀恐怖秩彤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情事哭,我是刑警寧澤漫雷,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站鳍咱,受9級特大地震影響降盹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谤辜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一蓄坏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丑念,春花似錦涡戳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挠将,卻和暖如春胳岂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舔稀。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工乳丰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人内贮。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓产园,卻偏偏與公主長得像,于是被迫代替她去往敵國和親夜郁。 傳聞我的和親對象是個殘疾皇子什燕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容