美文网首页
Telegram-iOS 源码分析:第四部分(MTProto)

Telegram-iOS 源码分析:第四部分(MTProto)

作者: 灰原丶逗 | 来源:发表于2020-11-16 16:30 被阅读0次
    版权声明
    本文内容均为搬运,目的只为更方便的学习Telegram编码思维。
    

    如需查阅原作者文章,附赠原文章机票

    正如在上一篇文章中所阐述的,TCP是Telegram-iOS上仅存的MTProto传输。让我们继续分析MTProto连接管理的实现细节。

    网络部分的代码主要位于模块TelegramCoreMTProtoKit中。在正文开始前,我们先来看一个简单的问题:

    在首次登录过程中MTProtoKit使用了多少个连接?

    结果让我感到惊讶:20个TCP连接到Telegram的数据中心以及8个HTTPS拓展服务请求。常见的最佳做法是使用尽可能少的连接。

    在深入研究代码以揭示谜题之前,先介绍一些重要的概念。

    1.连接的基本概念

    数据中心

    Telegram 将其后台服务器分为5个数据中心。每个数据中心都有自己的ID和别名。别名用于撰写用于HTTP传输的URI,iOS应用程序中未使用到。Telegram后端将每一个注册帐户都关联到一个主要到数据中心。它要求客户端使用最合适的主数据中心来访问用户数据,并且可能使用其他数据中心来下载图片,文件等。

    DC 1, pluto
    DC 2, venus
    DC 3, aurora
    DC 4, vesta
    DC 5, flora
    

    每个数据中心可以通过多个IP地址连接。不直接使用域名通常有以下几个原因:

    • 系统DNS服务可能不稳定,甚至不可信
    • IP地址和端口需要经常更改以应对网络问题。静态IP在某些地区可能无法访问,可以部署弹性IP来代理数据中心的流量。应用程序能够及时更新其端点配置。
    • Geo DNS之类的解决方案非常适合粗粒度的IP选择,后台直接控制比较好。

    Telegram-iOS内置了几个用于冷启动的种子地址:

    let seedAddressList: [Int: [String]]
    seedAddressList = [
        1: ["149.154.175.50", "2001:b28:f23d:f001::a"],   //AS59930
        2: ["149.154.167.50", "2001:67c:4e8:f002::a"],    //AS62041
        3: ["149.154.175.100", "2001:b28:f23d:f003::a"],  //AS59930
        4: ["149.154.167.91", "2001:67c:4e8:f004::a"],    //AS62041
        5: ["149.154.171.5", "2001:b28:f23f:f005::a"]     //AS62014
    ]
    

    Telegram拥有四个用于发布IP的AS号:AS62014AS62041AS59930AS44907(如果您感兴趣,可以通过搜索AS号找到更多静态IP)。

    端点发现

    Telegram-iOS可以通过内部和外部服务更新端点。这些方法作为其他方法的补充,以最大限度地提高更新的成功率。结果通过keydatacenterAddressSetById保存在Keychain中。

    • 通过Google Public DNSJSON APIHTTPS上进行DNS-over-HTTPStapv3.stel.com是要解析的主机名,并且已设置random_padding,该参数以防止可能的旁通道攻击。请求和响应的示例如下:
    // https://dns.google.com/resolve?name=apv3.stel.com&type=16&random_padding=Fw8ZQonqP0qOqoa
    {
      "Status": 0,
      "Question": [
        {
          "name": "apv3.stel.com.",
          "type": 16
        }
      ],
      "Answer": [
        {
          "name": "apv3.stel.com.",
          "type": 16,
          "data": "\"vEB1g6iW/a5RtZI/Rx33SEzLmRhz+vNenoY7iqAHW35plgToLfkNRVfvlaBsztOTeYSRqFko73rr2lumKmGax2biMcSQ==\""
        },
        {
          "name": "apv3.stel.com.",
          "type": 16,
          "data": "\"pEI+NHncHJCj9S0XzxhhTd3bkPteVxE5UQ8T06KCz0nP591un4Un82id0FyCEDF0BVmxMp+t673l3HAGD+fzR/qaJ1XpQ6KWxNpRLqA74m2UFTI1REP7ZczU2hmbURzSQvWQTxfp9tnGc1EnyqpUYphFb/Vi+sV83iaw6dTGOcKW1Kp/PW2xV99mmSFLBsspQRdUbKWvbrSpmXHbPbkSRZV61NvtaEiODG1We29nG58DUBqdW7m68ae11w\""
        }
      ]
    }
    

    Google service以多个DNS TXT内容作为响应,这些内容可以合并并转换为有效的base64字符串。客户端有RSA公钥解码数据并将其反序列化MTBackupDatacenterAddress的列表。

    代码内部有一个小窍门。除了正常的请求"dns.google.com"外,还会将Host header设置为另一个发送"https://www.google.com/resolve""dns.google.com"的请求,看起来好像是在将域前置到Google的一个子域,这使得DNS请求被伪装成像是正在访问谷歌搜索。Google于2018年4月宣布禁用域前置

    • Cloudflare。通过HTTPS进行DNS传输,实现类似于Google的解决方案。

    • CloudKit数据。对于登录用户,可以根据电话号码从CloudKit中获取相同的加密数据。

    • MTProto中的help.getConfig方法。如果客户端能够连接到任何数据中心,则此RPC请求可以获取包含DcOption列表的配置。

    • iOS PushKitUserNotifications。远程通知中的payload可以包括数据中心的一个端点数据

    MTProto代理

    除了由Telegram的工程团队操作的普通端点之外,Telegram还构建了一个代理系统,该系统允许第三方服务器代理其流量。作为交换,代理提供者需要将推广频道提供给其他用户。官方的代理代码是开源的
    作为不引入其他协议更改的反向代理,从客户端的角度来看,它与官方端点基本没有区别。

    加密连接

    除数据中心地址的IP和端口外,还提供一个可选参数secret,以指示客户端如何加密TCP连接。请注意,它与MTProto消息加密是两个不同的概念。它旨在混淆网络流量,这有助于应对DPI(深度数据包检测)

    加密有四种可能的类型:

    • nil。没有应用特殊的混淆。
    • MTProxySecretType0。像随机数据一样传输数据包是16字节的秘密。尽管数据包结构是隐藏的,但仍有一些DPI可以检测到的统计模式。
    • MTProxySecretType1。它是从17个字节的数据中解码出来的。第一个字节始终为0xdd,其他16个字节为机密。padded intermediate format被用来隐藏数据包模式。
    • MTProxySecretType2。启用了fake-TLS,从而使Telegram连接看起来像TLS v1.2连接。数据以字节0xee开头。接着16个字节是机密数据。其余的字节是一个UTF-8编码的字符串,它是在TLS握手期间使用的SNI域。

    让我们以MTProto代理的共享URL为例。字符串以开头ee表明其加密类型是MTProxySecretType2。机密数据用零填充,伪造的域为itunes.apple.com

    https://t.me/proxy?server=0.0.0.0
        &port=8080
        &secret=ee000000000000000000000000000000006974756e65732e6170706c652e636f6d
    # 6974...6f6d can be decoded to "itunes.apple.com"
    

    连接选择

    由于一个数据中心可以具有一组地址,因此MTContext实施选择策略以选择具有最早故障时间戳的地址。

    数据中心授权

    在将MTProto消息发送到数据中心之前,需要完成目标DC的(p,q)授权AuthInfo在代码中称为数据中心身份验证信息

    用户授权

    通过SMS代码或其他方法成功验证后,主数据中心将用户帐户与客户端的帐户相关联auth_key_id,从而授权以用户身份访问数据中心。如果客户端要使用相同的用户帐户访问其他数据中心,则需要提前转移授权

    回顾一下

    根据概念,可知以下内容是客户端与后端交互的要求:

    • 客户端需要知道数据中心ID及其地址。
    • 地址可以通过端点发现来更新。
    • Telegram支持MTProto的特殊反向代理。
    • 如果地址可以访问,客户端需要在完成其他数据传输之前完成数据中心授权。
    • 客户端应通过其主数据中心完成用户授权。
    • 如果客户端需要使用用户帐户访问其他数据中心,则需要授权转移。

    2.代码结构

    让我们看一下Telegram-iOS如何构建代码的。如下图所示:


    part-4-network_modules.png
    • 有一个依赖关系链,使UI控制器可以访问网络模块。大多数控制器依赖于一个帐户的数据模型,它要么实例AccountUnauthorizedAccount
    • 帐户类公开其Network实例字段,以供控制器发送请求。
    public class UnauthorizedAccount {
        ...
        public let network: Network
        ...
    }
    
    public class Account {
        ...
        public let network: Network
        ...
    }
    
    • Network封装与MTProtoKit模块的所有交互,并将RPC请求-响应对建模为Signals
    /* Code snippets from Network.swift */
    public final class Network: NSObject, MTRequestMessageServiceDelegate {
        ...
        func background() -> Signal<Download, NoError>
        
        public func request<T>(
            _ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), 
            tag: NetworkRequestDependencyTag? = nil, 
            automaticFloodWait: Bool = true
            ) -> Signal<T, MTRpcError>
        ...
    }
    
    // a Signal operator to retry a RPC
    public func retryRequest<T>(signal: Signal<T, MTRpcError>) -> Signal<T, NoError>
    
    /* A code snippet to request login code from `Authorization.swift` */
    // construct an MTProto API object
    let sendCode = 
        Api.functions.auth.sendCode(
            flags: 0, 
            phoneNumber: phoneNumber, 
            currentNumber: nil, 
            apiId: apiId, 
            apiHash: apiHash)
    // send the API via `network`
    account.network.request(sendCode, automaticFloodWait: false)
    
    • MTProtoKit模块实现了端点信息,授权数据,连接生命周期,连接和协议加密等所有复杂逻辑

    核心MTProtoKit类

    • MTContext。通过共享和维护所有数据中心的重要数据(例如地址,身份验证信息等),这是大多数MTProtoKit类的上下文。与当前设计相比,这不是一个单例,并且可能有多个实例。
    • MTProto。它是发送到特定数据中心的消息的核心管理器。
    • MTTcpTransport。它管理TCP连接。一个MTProto实例最多可以有一个传输活动。
    • MTMessageService。这是一个Objective-C协议,它定义了处理RPC的方法。一个MTProto实例可以有多个MTMessageService实例。

    3.在首次登录过程中

    典型的首次登录过程分为四个阶段:欢迎界面电话号码界面验证码界面主界面。让我们看下每个阶段触发的连接数。

    part-4-login-flow.png

    提供了几张图来说明每个阶段的简化工作流程。有关图的一些注意事项:

    • 为了简化,省略了许多中间信号和类。
    • 橙色节点是Swift中的代码。
    • 绿色节点是Objective-C中的代码。
    • 蓝色边缘表示在此阶段创建的连接,而虚线表示请求数据中心身份验证信息的连接,而粗体表示表示RPC的连接。
    • 红色边缘表示H​​TTP请求。
    • 粉色的数据中心节点是通过端点发现更新的新IP地址,黑色的是种子地址。

    首次启动

    part-4-onboarding.png

    首次启动该应用程序时,每个数据中心都没有帐户数据,也没有身份验证信息。一个新的UnAuthorizedAccountAsk实例MTContext从DC 1、2和4获取身份验证信息,这将创建TCP连接①②③。身份验证操作完成后,连接将关闭。

    // UnauthorizedAccount.swift
    public class UnauthorizedAccount {
        ...
        init(networkArguments: NetworkInitializationArguments, id: AccountRecordId, rootPath: String, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, shouldKeepAutoConnection: Bool = true) {
            ...
            network.context.performBatchUpdates({
                var datacenterIds: [Int] = [1, 2]
                if !testingEnvironment {
                    datacenterIds.append(contentsOf: [4])
                }
                for id in datacenterIds {
                    if network.context.authInfoForDatacenter(withId: id) == nil {
                        network.context.authInfoForDatacenter(withIdRequired: id, isCdn: false)
                    }
                }
                network.context.beginExplicitBackupAddressDiscovery()
            })
        }
    }
    
    // Network.swift
    context.setDiscoverBackupAddressListSignal(
        MTBackupAddressSignals.fetchBackupIps(
            testingEnvironment, 
            currentContext: context, 
            additionalSource: wrappedAdditionalSource, 
            phoneNumber: phoneNumber))
    

    它还会调用beginExplicitBackupAddressDiscovery并启动在Network中设置的信号。fetchBackupIps结合了不同的发现信号,并且只需要第一个响应(多个请求,只要有一个请求有响应就行)fetchConfigFromAddress。如概念部分所述,它启动4个HTTP请求:①②向Google,③到Cloudflare和④到CloudKit。

    + (MTSignal * _Nonnull)fetchBackupIps:(bool)isTestingEnvironment currentContext:(MTContext * _Nonnull)currentContext additionalSource:(MTSignal * _Nullable)additionalSource phoneNumber:(NSString * _Nullable)phoneNumber {
        ...
        NSMutableArray *signals = [[NSMutableArray alloc] init];
        [signals addObject:[self fetchBackupIpsResolveGoogle:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext addressOverride:currentContext.apiEnvironment.accessHostOverride]];
        [signals addObject:[self fetchBackupIpsResolveCloudflare:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext addressOverride:currentContext.apiEnvironment.accessHostOverride]];
        [signals addObject:[additionalSource mapToSignal:^MTSignal *(MTBackupDatacenterData *datacenterData) {
                ...
            }]];
        return [[[MTSignal mergeSignals:signals] take:1] mapToSignal:^MTSignal *(MTBackupDatacenterData *data) {
            NSMutableArray *signals = [[NSMutableArray alloc] init];
            NSTimeInterval delay = 0.0;
            for (MTBackupDatacenterAddress *address in data.addressList) {
                MTSignal *signal = [self fetchConfigFromAddress:address currentContext:currentContext];
                if (delay > DBL_EPSILON) {
                    signal = [signal delay:delay onQueue:[[MTQueue alloc] init]];
                }
                [signals addObject:signal];
                delay += 5.0;
            }
            return [[MTSignal mergeSignals:signals] take:1];
        };
    }
    

    现在,DC 2的身份验证信息已准备就绪,可以进行连接②,因为DC 2被编码为默认的主数据中心ID,所以将使用身份验证信息重新创建新的TCP连接④:

    // Account.swift
    public func accountWithId(accountManager: AccountManager, networkArguments: NetworkInitializationArguments, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, supplementary: Bool, rootPath: String, beginWithTestingEnvironment: Bool, backupData: AccountBackupData?, auxiliaryMethods: 
        ...
        return initializedNetwork(
            arguments: networkArguments, 
            supplementary: supplementary, 
            datacenterId: 2,  // use DC 2 for unauthrized account
            keychain: keychain, 
            basePath: path, 
            testingEnvironment: beginWithTestingEnvironment, 
            languageCode: localizationSettings?.primaryComponent.languageCode, 
            proxySettings: proxySettings, 
            networkSettings: networkSettings, phoneNumber: nil)
                            |> map { network -> AccountResult in
                                return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
                            }
    }
    

    同时,Google DoH返回196.55.216.85DC 4的新地址。对于任何新发现的端点,fetchConfigFromAddress调用requestDatacenterAddress以发送RPChelp.getConfig进行新配置。

    作为一种新MTContext不复制内部数据创建,它不知道原来的情况下可能有DC 4的身份验证信息,具有创造DC 4的身份验证信息的TCP连接⑤,断开连接,然后重新连接到通过另一个连接⑥发送RPC。

    从DC 4返回的配置内部的数据中心地址被提取并解码为MTDatacenterAddressListData。然后关闭连接⑥。

    // Addresses in Config.config from DC 4
    MTDatacenterAddressListData({
        1 =     (
            "149.154.175.51:443#(media no, cdn no, preferForProxy no, secret )",
            "149.154.175.50:443#(media no, cdn no, preferForProxy yes, secret )",
            "2001:0b28:f23d:f001:0000:0000:0000:000a:443#(media no, cdn no, preferForProxy no, secret )"
        );
        2 =     (
            "149.154.167.50:443#(media no, cdn no, preferForProxy no, secret )",
            "149.154.167.51:443#(media no, cdn no, preferForProxy yes, secret )",
            "149.154.167.151:443#(media yes, cdn no, preferForProxy no, secret )",
            "2001:067c:04e8:f002:0000:0000:0000:000a:443#(media no, cdn no, preferForProxy no, secret )",
            "2001:067c:04e8:f002:0000:0000:0000:000b:443#(media yes, cdn no, preferForProxy no, secret )"
        );
        3 =     (
            "149.154.175.100:443#(media no, cdn no, preferForProxy no, secret )",
            "149.154.175.100:443#(media no, cdn no, preferForProxy yes, secret )",
            "2001:0b28:f23d:f003:0000:0000:0000:000a:443#(media no, cdn no, preferForProxy no, secret )"
        );
        4 =     (
            "149.154.167.92:443#(media no, cdn no, preferForProxy no, secret )",
            "149.154.167.92:443#(media no, cdn no, preferForProxy yes, secret )",
            "149.154.165.96:443#(media yes, cdn no, preferForProxy no, secret )",
            "2001:067c:04e8:f004:0000:0000:0000:000b:443#(media yes, cdn no, preferForProxy no, secret )",
            "2001:067c:04e8:f004:0000:0000:0000:000a:443#(media no, cdn no, preferForProxy no, secret )"
        );
        5 =     (
            "91.108.56.143:443#(media no, cdn no, preferForProxy no, secret )",
            "91.108.56.143:443#(media no, cdn no, preferForProxy yes, secret )",
            "2001:0b28:f23f:f005:0000:0000:0000:000a:443#(media no, cdn no, preferForProxy no, secret )"
        );
    })
    

    如果新数据不同,则原始上下文将使用新配置替换其种子数据中心地址集,并将其保存到钥匙串中。更改也分派给所有监听者。原文作者认为,代码内部存在一个错误fetchConfigFromAddresscurrentAddressSet是从错误的上下文中提取的,并且始终为 nil

    // MTBackupAddressSignals.m, fetchConfigFromAddress
    __strong MTContext *strongCurrentContext = weakCurrentContext;
    [result.addressList enumerateKeysAndObjectsUsingBlock:^(NSNumber *nDatacenterId, NSArray *list, __unused BOOL *stop) {
      MTDatacenterAddressSet *addressSet = [[MTDatacenterAddressSet alloc] initWithAddressList:list];
      // Bug here, should use `strongCurrentContext` instead of `context`
      MTDatacenterAddressSet *currentAddressSet = [context addressSetForDatacenterWithId:[nDatacenterId integerValue]];
      // It's always true as `currentAddressSet` is always nil
      if (currentAddressSet == nil || ![addressSet isEqual:currentAddressSet])
      {
          [strongCurrentContext 
              updateAddressSetForDatacenterWithId:[nDatacenterId integerValue] 
                                       addressSet:addressSet 
                               forceUpdateSchemes:true];
          ...
      }
    }];
    
    // MTContext.m
    - (void)updateAddressSetForDatacenterWithId:(NSInteger)datacenterId 
       addressSet:(MTDatacenterAddressSet *)addressSet 
       forceUpdateSchemes:(bool)updateSchemes {
       ...
       // replace the address set and save it the Keychain
       _datacenterAddressSetById[@(datacenterId)] = addressSet;
       [_keychain setObject:_datacenterAddressSetById 
                     forKey:@"datacenterAddressSetById" 
                      group:@"persistent"];
       ...
       bool shouldReset = previousAddressSetWasEmpty || updateSchemes;
       ...
       // broadcast the change event. `shouldReset` is True if the callee is fetchConfigFromAddress 
       for (id<MTContextChangeListener> listener in currentListeners) {
           [listener 
               contextDatacenterTransportSchemesUpdated:self 
                                           datacenterId:datacenterId 
                                            shouldReset:shouldReset];
       }
    }
    
    // MTProto.m
    - (void)contextDatacenterTransportSchemesUpdated:(MTContext *)context 
                                       datacenterId:(NSInteger)datacenterId 
                                        shouldReset:(bool)shouldReset {
       ...        
       if (resolvedShouldReset) {
           // reset the current transport
           [self resetTransport];
           [self requestTransportTransaction];
       }
       ...
    }
    

    监听者之一是MTProto,它保持与DC 2的有效连接④。它被命令重置其传输并创建与149.154.167.51DC 2的连接⑦ 。

    到目前为止,还没有用户交互,让我们刷新状态:

    创建了7个TCP连接和4个HTTP请求。与DC 2的连接⑦处于活动状态,而其他关闭。
    客户端已经收集了DC 1、2、4的认证信息。
    数据中心地址集已更新。

    输入电话号码

    part-4-phonenumber.png

    输入电话号码并点击下一步后,auth.sendCode将通过活动连接将RPC发送到DC2。它将PHONE_MIGRATE_5作为帐户属于DC 5进行响应。

    // Authorization.swift
    public func sendAuthorizationCode(accountManager: AccountManager, account: UnauthorizedAccount, phoneNumber: String, apiId: Int32, apiHash: String, syncContacts: Bool) -> Signal<UnauthorizedAccount, AuthorizationCodeRequestError> {
       ...
       switch (error.errorDescription ?? "") {
           case Regex("(PHONE_|USER_|NETWORK_)MIGRATE_(\\d+)"):
               let range = error.errorDescription.range(of: "MIGRATE_")!
               // extract data center id from error description
               let updatedMasterDatacenterId = Int32(error.errorDescription[range.upperBound ..< error.errorDescription.endIndex])!
               let updatedAccount = account.changedMasterDatacenterId(accountManager: accountManager, masterDatacenterId: updatedMasterDatacenterId)
               return updatedAccount
               |> mapToSignalPromotingError { updatedAccount -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in
                   return updatedAccount.network.request(sendCode, automaticFloodWait: false)
                   |> map { sentCode in
                       return (sentCode, updatedAccount)
                   }
               }
       }
       ...
    }
    
    // Account.swift, class Account
    public func changedMasterDatacenterId(accountManager: AccountManager, masterDatacenterId: Int32) -> Signal<UnauthorizedAccount, NoError> {
       ...
       return accountManager.transaction { ... }
       |> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?), NoError> in
           ...
       }
       |> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal<UnauthorizedAccount, NoError> in
           return initializedNetwork(arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil)
           |> map { network in
               let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
               updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
               return updated
           }
       }
    }
    

    客户端被告知,DC 5是主数据中心,而不是DC 2.调用initializedNetwork并创建实例UnauthorizaedAccount来重发再次auth.sendAuth到DC 5的内部initializedNetwork,新MTContext创建以支持后续业务。

    由于客户端还没有DC 5的身份验证信息,因此将建立连接⑧。同时,不幸的是,UnauthorizaedAccount新的实例启动了相同的端点发现逻辑,这导致了更多不必要的操作:

    • 它创建HTTP请求④⑤⑥⑦,并且DC 4再次获得了相同的地址196.55.216.85。
    • 由于fetchConfigFromAddress相同的复制问题,由于内部的MTContext无法识别DC 4的身份验证信息,因此会为其创建连接 ⑨ ,并创建 ⑨ 再次发送help.getConfig协议。
    • 在配置数据中返回相同的地址集。该currentAddressSet错误导致它调用updateAddressSetForDatacenterWithId。
    • 错误的更改事件将传播到与MTProtoDC 5具有有效连接⑧的。它将重置它并创建连接⑪以继续向DC 5发出身份验证信息请求。

    获得身份验证信息后,连接⑪将关闭,并且连接 ⑫将开始发送auth.sendAuthto DC 5。

    此阶段的摘要:

    • 创建5个TCP连接和4个HTTP请求。⑫到DC 5的连接and和DC到DC 2的连接被激活,而其他开关则关闭。实施的注意事项导致了多余的连接和请求。
    • 客户端获得DC 1、2、4、5的身份验证信息。
    • 地址集列表没有更改,尽管它已再次更新。

    顺便说一句,如果种子地址和从DoH来的备用地址不起作用了,Telegram-iOS会在20秒后提示你设置代理,目前没有其他解决办法。

    输入授权码

    part-4-code.png

    输入从SMS接收到的登录代码后,auth.signIn通过活动连接由RPC发送。DC 5验证其正确并返回auth.Authorization.authorization。最终,客户端最终可以通过调用switchToAuthorizedAccount来替换其未经授权的状态。

    创建新的AccountNetwork替换正在使用的,连接⑫和⑦关闭。创建连接 ⑬ 连接91.108.56.143到DC 5获取用户身份验证数据。ChatHistoryPreloadManager建立连接 ⑭ 与DC 5的连接以下载聊天记录

    managedConfigurationUpdates函数中一堆RPC通过连接⑬发送,包括help.getConfig协议。DC 5的配置与DC 4的配置具有不同的地址集列表,尤其是DC 5的地址更改为91.108.56.156。连接⑬⑭和不作为受到新的配置forceUpdateSchemes假的managedConfigurationUpdates

    // Addresses in Config.config from DC 5
    MTDatacenterAddressListData({
        1 =     (
            "149.154.175.57:443#(media no, cdn no, preferForProxy no, secret )",
            "149.154.175.50:443#(media no, cdn no, preferForProxy yes, secret )",
            "2001:0b28:f23d:f001:0000:0000:0000:000a:443#(media no, cdn no, preferForProxy no, secret )"
        );
        2 =     (
            "149.154.167.50:443#(media no, cdn no, preferForProxy no, secret )",
            "149.154.167.51:443#(media no, cdn no, preferForProxy yes, secret )",
            "149.154.167.151:443#(media yes, cdn no, preferForProxy no, secret )",
            "2001:067c:04e8:f002:0000:0000:0000:000a:443#(media no, cdn no, preferForProxy no, secret )",
            "2001:067c:04e8:f002:0000:0000:0000:000b:443#(media yes, cdn no, preferForProxy no, secret )"
        );
        3 =     (
            "149.154.175.100:443#(media no, cdn no, preferForProxy no, secret )",
            "149.154.175.100:443#(media no, cdn no, preferForProxy yes, secret )",
            "2001:0b28:f23d:f003:0000:0000:0000:000a:443#(media no, cdn no, preferForProxy no, secret )"
        );
        4 =     (
            "149.154.167.92:443#(media no, cdn no, preferForProxy no, secret )",
            "149.154.167.92:443#(media no, cdn no, preferForProxy yes, secret )",
            "149.154.166.120:443#(media yes, cdn no, preferForProxy no, secret )",
            "2001:067c:04e8:f004:0000:0000:0000:000b:443#(media yes, cdn no, preferForProxy no, secret )",
            "2001:067c:04e8:f004:0000:0000:0000:000a:443#(media no, cdn no, preferForProxy no, secret )"
        );
        5 =     (
            "91.108.56.156:443#(media no, cdn no, preferForProxy no, secret )",
            "91.108.56.156:443#(media no, cdn no, preferForProxy yes, secret )",
            "2001:0b28:f23f:f005:0000:0000:0000:000a:443#(media no, cdn no, preferForProxy no, secret )"
        );
    })
    

    主界面已准备好与帐户一起显示。其他模块开始获取UI组件的资源,例如头像图片等。所有请求均通过multiplexedRequestManager拥有的account.network发送。在此登录会话期间,所有资源都位于DC 1上。

    在客户端可以从DC 1下载文件之前,它必须将其用户授权从DC 5转移到DC1。创建连接 ⑮要求DC 5导出身份验证数据,创建连接⑯将数据导入DC 1。业务完成后,两个连接均关闭。

    MultiplexedRequestManagerContext每个DC最多限制4个workers。这就是为什么有连接 ⑰ ⑱ ⑲ ⑳用来Download的原因。

    4 结论

    • 在首次登录过程中。尽管要完成许多任务,但拥有20个TCP连接和8个HTTP请求可能并不好。
    • 可以优化连接和请求的使用。
    • 为了使即使通讯产品可靠地连接到其数据中心,它需要很多效果。对于意外的网络问题,必须有多个备份计划。除了Telegram中使用的方法外,还有许多其他方法可以探索。
    • 我对以纯粹的反应方式对连接和数据的状态进行建模感到褒贬不一。好消息是它确实有效。但这会导致具有许多信号的复杂依赖关系结构。
    • 集成调试工具以帮助探索运行状态将非常好,例如FlipperFLEXTimelane等。

    相关文章

      网友评论

          本文标题:Telegram-iOS 源码分析:第四部分(MTProto)

          本文链接:https://www.haomeiwen.com/subject/xbsabktx.html