版权声明
本文内容均为搬运,目的只为更方便的学习Telegram编码思维。
正如在上一篇文章中所阐述的,TCP是Telegram-iOS上仅存的MTProto传输。让我们继续分析MTProto连接管理的实现细节。
网络部分的代码主要位于模块TelegramCore
和MTProtoKit
中。在正文开始前,我们先来看一个简单的问题:
在首次登录过程中
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号:AS62014,AS62041,AS59930和AS44907(如果您感兴趣,可以通过搜索AS号找到更多静态IP)。
端点发现
Telegram-iOS可以通过内部和外部服务更新端点。这些方法作为其他方法的补充,以最大限度地提高更新的成功率。结果通过keydatacenterAddressSetById
保存在Keychain中。
- 通过
Google Public DNS
的JSON API
在HTTPS
上进行DNS-over-HTTPS。tapv3.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 PushKit和UserNotifications。远程通知中的
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如何构建代码的。如下图所示:
- 有一个依赖关系链,使UI控制器可以访问网络模块。大多数控制器依赖于一个帐户的数据模型,它要么实例
Account
或UnauthorizedAccount
。 - 帐户类公开其
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.在首次登录过程中
典型的首次登录过程分为四个阶段:欢迎界面,电话号码界面,验证码界面和主界面。让我们看下每个阶段触发的连接数。
提供了几张图来说明每个阶段的简化工作流程。有关图的一些注意事项:
- 为了简化,省略了许多中间信号和类。
- 橙色节点是Swift中的代码。
- 绿色节点是Objective-C中的代码。
- 蓝色边缘表示在此阶段创建的连接,而虚线表示请求数据中心身份验证信息的连接,而粗体表示表示RPC的连接。
- 红色边缘表示HTTP请求。
- 粉色的数据中心节点是通过端点发现更新的新IP地址,黑色的是种子地址。
首次启动
首次启动该应用程序时,每个数据中心都没有帐户数据,也没有身份验证信息。一个新的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.85
DC 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 )"
);
})
如果新数据不同,则原始上下文将使用新配置替换其种子数据中心地址集,并将其保存到钥匙串中。更改也分派给所有监听者。原文作者认为,代码内部存在一个错误fetchConfigFromAddress
,currentAddressSet
是从错误的上下文中提取的,并且始终为 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的认证信息。
数据中心地址集已更新。
输入电话号码
输入电话号码并点击下一步后,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秒后提示你要设置代理,目前没有其他解决办法。
输入授权码
输入从SMS接收到的登录代码后,auth.signIn
通过活动连接由RPC发送。DC 5验证其正确并返回auth.Authorization.authorization
。最终,客户端最终可以通过调用switchToAuthorizedAccount
来替换其未经授权的状态。
创建新的Account
和Network
替换正在使用的,连接⑫和⑦关闭。创建连接 ⑬ 连接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
的原因。