內(nèi)置消息簡析
在 MTProtoKit 中,有很多內(nèi)置的消息,除了這些消息之外,還有和解析這些消息相關的類,比如 MTBufferReader 和 MTInternalMessageParser,這些都是用來對這些內(nèi)置消息進行解析用的;除了內(nèi)置消息,還應該有很多業(yè)務相關性的消息,而這些消息都不在 MTProtoKit 考慮之中,MTProtoKit 將其它消息的序列化由 MTSerialization 協(xié)議委托給了使用者(Serialization)去實現(xiàn),這樣做還是很合理的,因為 MTProto 是一個非常動態(tài)的協(xié)議,擴展性非常強。
內(nèi)置消息其實就像編程語言給我們提供的標準庫一樣,它是框架,也是基礎,下面簡單選取一些消息做個介紹:
- MTBadMsgNotificationMessage:服務端未能正確解析客戶端消息時,會返回該消息,并附帶錯誤碼。
-
MTBadServerSaltNotificationMessage:服務端對
Salt驗證失敗時,會返回該消息。 - MTDestroySessionResponseOkMessage:客戶端請求銷毀當前會話,服務端返回銷毀成功的響應。
- MTDestroySessionResponseNoneMessage:客戶端請求銷毀當前會話,服務端返回未找到該會話的響應。
-
MTDropRpcResultUnknownMessage:客戶端請求服務端取消某次
RPC請求,服務端返回未知狀態(tài)的響應。 -
MTDropRpcResultDroppedRunningMessage:客戶端請求服務端取消某次
RPC請求,服務端返回正在處理的響應。 -
MTDropRpcResultDroppedMessage:客戶端請求服務端取消某次
RPC請求,服務端返回處理完成的響應。 -
MTFutureSaltsMessage:客戶端請求服務端
Salt,服務端的響應消息。 - MTMsgsStateReqMessage:當消息處理的任何一方(服務器或客戶端)長時間未收到發(fā)出消息的響應,則可以通過該請求來查詢消息處理狀態(tài)。
- MTMsgsStateInfoMessage:消息處理狀態(tài)響應。
- MTMsgAllInfoMessage:自發(fā)性的通知另一方,消息處理的狀態(tài)。
- MTMsgContainerMessage:消息容器,用于多個消息同時打包發(fā)送。
- MTMsgDetailedResponseInfoMessage:當收到重復的消息請求時,且響應內(nèi)容過大,服務端會返回該條響應,用于描述響應內(nèi)容。
- MTMsgResendReqMessage:客戶端請求消息響應重傳,旨在處理重復請求時,明確的讓服務端返回響應內(nèi)容。
- MTMsgsAckMessage:消息回執(zhí)。
- MTNewSessionCreatedMessage:服務端通知客戶端,一個新的會話被創(chuàng)建。
-
MTRpcResultMessage:
RPC請求響應消息。 -
MTRpcError:
RPC請求錯誤響應。
全局上下文
什么是上下文呢?
上下文就是在某個特定的場景里,用于記錄該場景特定狀態(tài)的一種抽象。
所謂的全局上下文,也就是 MTContext 類,這是一個使用相當頻繁的類,它的主要意圖是用來給 MTProtoKit 中,其它類提供一個公共的運行上下文,也相當于是整個 MTProtoKit 的入口點。所以,一些公共的狀態(tài)和方法都會被提升到這個類中,它大體記錄了以下信息:
1.客戶端運行環(huán)境,即 MTApiEnvironment。
2.非內(nèi)置消息的序列化器實現(xiàn),即 MTSerialization 協(xié)議(Serialization)的實現(xiàn)。
3.客戶端時間,并允許設定偏差來校準。
4.當前用戶的授權(quán)相關信息和操作。
5.數(shù)據(jù)中心的相關信息和操作。
6.傳輸格式(MTTransportScheme)的相關信息和操作。
其他細節(jié)
現(xiàn)在,我們再來看看一些比較有意思的細節(jié)處理;首先是 MTBuffer 類中,字節(jié)對齊的算法實現(xiàn):
static inline int roundUp(int numToRound, int multiple)
{
return multiple == 0 ? numToRound : ((numToRound % multiple) == 0 ? numToRound : (numToRound + multiple - (numToRound % multiple)));
}
很簡單的算法,但很有意思,使用 roundUp(17, 4),則會得到 17 按照 4 向上對齊的結(jié)果,也就是 20。
MTBuffer 中,還有一個方法,也就是追加 TL 字節(jié),我們來看一下:
- (void)appendTLBytes:(NSData *)bytes
{
int32_t length = (int32_t)bytes.length;
if (bytes == nil || length == 0)
{
[self appendInt32:0];
return;
}
int paddingBytes = 0;
if (length >= 254)
{
uint8_t tmp = 254;
[self appendBytes:&tmp length:1];
[self appendBytes:(const uint8_t *)&length length:3];
paddingBytes = roundUp(length, 4) - length;
}
else
{
[self appendBytes:(const uint8_t *)&length length:1];
paddingBytes = roundUp(length + 1, 4) - (length + 1);
}
[self appendBytes:bytes.bytes length:length];
uint8_t tmp = 0;
for (int i = 0; i < paddingBytes; i++)
[self appendBytes:&tmp length:1];
}
當這個塊的長度小于 254 時,第一個字節(jié)就是用來標識內(nèi)容的長度;而當這個塊的長度大于或等于 254 時,第一個字節(jié)只是一個標志,后面 3 個字節(jié)才是真正的長度,所以,每個塊的最大長度是 24 位值,而不是 32 位;這樣做,長度值所占用的字節(jié)就可以被壓縮了。
再來看一個 MTInternalMessageParser 中的 decompressGZip 方法,因為消息是可以放置在 gzip 容器中進行傳輸?shù)?,所以客戶端需要解壓字?jié)流:
+ (NSData *)decompressGZip:(NSData *)data
{
const int kMemoryChunkSize = 1024;
NSUInteger length = [data length];
int windowBits = 15 + 32; //Default + gzip header instead of zlib header
int retCode;
unsigned char output[kMemoryChunkSize];
uInt gotBack;
NSMutableData *result;
z_stream stream;
if ((length == 0) || (length > UINT_MAX)) //FIXME: Support 64 bit inputs
return nil;
bzero(&stream, sizeof(z_stream));
stream.avail_in = (uInt)length;
stream.next_in = (unsigned char*)[data bytes];
retCode = inflateInit2(&stream, windowBits);
if(retCode != Z_OK)
{
NSLog(@"%s: inflateInit2() failed with error %i", __PRETTY_FUNCTION__, retCode);
return nil;
}
result = [NSMutableData dataWithCapacity:(length * 4)];
do
{
stream.avail_out = kMemoryChunkSize;
stream.next_out = output;
retCode = inflate(&stream, Z_NO_FLUSH);
if ((retCode != Z_OK) && (retCode != Z_STREAM_END))
{
NSLog(@"%s: inflate() failed with error %i", __PRETTY_FUNCTION__, retCode);
inflateEnd(&stream);
return nil;
}
gotBack = kMemoryChunkSize - stream.avail_out;
if (gotBack > 0)
[result appendBytes:output length:gotBack];
} while( retCode == Z_OK);
inflateEnd(&stream);
return (retCode == Z_STREAM_END ? result : nil);
}