最近公司業務須要,對網絡進行監控,本文主要記錄下在網絡流量監控實現過程當中遇到的一些問題的解決方法。html
如今網上都有不少網絡監控的文章,本文主要是參考了ios
iOS 流量監控分析 git
iOS 性能監控方案 Wedjatgithub
移動端監控體系之技術原理剖析objective-c
Http 請求頭中的 Proxy-Connection數據庫
Hypertext Transfer Protocol -- HTTP/1.1json
HTTP 網絡的 Response 主要由 3 部分構成,分佈是 status line,header 和 body,如下主要是分別分析了這 3 個部分流量的計算方法。bash
經過 iOS 流量監控分析 做者提供的方法,咱們能夠獲取到 Response 的 Status-Line服務器
typedef CFHTTPMessageRef (*DMURLResponseGetHTTPResponse)(CFURLRef response);
- (NSString *)statusLineFromCF {
NSURLResponse *response = self;
NSString *statusLine = @"";
// 獲取CFURLResponseGetHTTPResponse的函數實現
NSString *funName = @"CFURLResponseGetHTTPResponse";
DMURLResponseGetHTTPResponse originURLResponseGetHTTPResponse =
dlsym(RTLD_DEFAULT, [funName UTF8String]);
SEL theSelector = NSSelectorFromString(@"_CFURLResponse");
if ([response respondsToSelector:theSelector] &&
NULL != originURLResponseGetHTTPResponse) {
// 獲取NSURLResponse的_CFURLResponse
CFTypeRef cfResponse = CFBridgingRetain([response performSelector:theSelector]);
if (NULL != cfResponse) {
// 將CFURLResponseRef轉化爲CFHTTPMessageRef
CFHTTPMessageRef messageRef = originURLResponseGetHTTPResponse(cfResponse);
statusLine = (__bridge_transfer NSString *)CFHTTPMessageCopyResponseStatusLine(messageRef);
CFRelease(cfResponse);
}
}
return statusLine;
}
複製代碼
這個方法獲取到的 Response 的 Status-Line 是能夠的,對於 Status-Line 的計算我作了一點小補充。cookie
在這裏先簡單闡述下 Status-Line 在 rfc - HTTP/1.1 中的定義
The first line of a Response message is the Status-Line, consisting of the protocol version followed by a numeric status code and its associated textual phrase, with each element separated by SP characters. No CR or LF is allowed except in the final CRLF sequence.
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
複製代碼
The Status-Code element is a 3-digit integer result code of the attempt to understand and satisfy the request. These codes are fully defined in section 10. The Reason-Phrase is intended to give a short textual description of the Status-Code. The Status-Code is intended for use by automata and the Reason-Phrase is intended for the human user. The client is not required to examine or display the Reason- Phrase.
上面的公式中 SP 表明空格, CR 表明回車,LF表明換行,也就是說,一個完整的 Status-Line 以下所示
Status-Line = HTTP-Version 空格 Status-Code 空格 Reason-Phrase /r/n
複製代碼
而上面定義的最後一句話 The client is not required to examine or display the Reason- Phrase 表示並無強制要求客戶端去解析或是展現 Reason- Phrase 字段,也就是說 Reason- Phrase 這個值是能夠不存在的。而用 Wirehark 進行網絡捉包時,也的確發現了有些 Response 的 Stattus-Line 是沒有帶上 Reason-Phrase,
經過對比,會發現雖然有些 Status-Line 中沒有 Reason-Phrase,可是在 Status-Code 後面仍是會帶有一個空格,而在 iOS 中,經過上面方法拿到的 Status-Line,若是是不帶有 Reason-Phrase 時,在字符串的最後面是沒有添加一個空格的,因此對於這種狀況須要作下特殊處理。另外,Status-Line 的數據長度計算,是須要加上 Status-Line 尾部攜帶的 CRLF。最終,Status-Line 計算的修改結果以下:
- (NSUInteger)ep_getStatusLineLengths:(NSString *)statusLine {
NSMutableString *lineStr = @"".mutableCopy;
[lineStr appendString: statusLine];
NSArray *statusLineArr = [statusLine componentsSeparatedByString:@" "];
// 若是 Status-Line 只包含了 HTTP-Version 和 Status-Code 時,判斷下字符串的尾部是否添加了空格,若是沒有,則人爲添加一個空格
if (statusLineArr.count == 2 && ![statusLine hasSuffix:@" "]) {
[lineStr appendString:@" "];
}
// 判斷 Status-Line 是否有 \r\n 後綴,若是沒有,也人爲添加上 \r\n 後綴
if (![lineStr hasSuffix:@"\r\n"]) {
[lineStr appendString:@"\r\n"];
}
// Status-Line 進行 Utf-8 編碼後,獲取到其長度
NSData *lineData = [lineStr dataUsingEncoding:NSUTF8StringEncoding];
return lineData.length;
}
複製代碼
一樣的,在計算 Response Header 以前,咱們先去了解下 HTPP/1.1 中關於 Message Headers 的定義,以下引用所示:
Each header field consists of a name followed by a colon (":") and the field value. Field names are case-insensitive. The field value MAY be preceded by any amount of LWS, though a single SP is preferred. Header fields can be extended over multiple lines by preceding each extra line with at least one SP or HT. Applications ought to follow "common form", where one is known or indicated, when generating HTTP constructs, since there might exist some implementations that fail to accept anything
beyond the common forms.
message-header = field-name ":" [ field-value ] field-name = token field-value = *( field-content | LWS ) field-content = <the OCTETs making up the field-value and consisting of either *TEXT or combinations of token, separators, and quoted-string> 複製代碼
文檔中提示到 The field value MAY be preceded by any amount of LWS, though a single SP is preferred ,說明了在每個 filed-value 的開頭,有可能有若干個 LWS,也就是所謂的空格,文檔中也強調了最好只有一個空格,雖然沒有強制要求,可是使用 wireShark 進行捉包時,發現基本上 HTTP/1.1 請求的 field-value 的前面都帶有一個空格。以下圖所示:
另外,在文檔的 Message Types 章節中,也對多個 message-header 的格式進行了定義:
Both types of message consist of a start-line, zero or more header fields (also known as "headers"), an empty line (i.e., a line with nothing preceding the CRLF) indicating the end of the header fields, and possibly a message-body.
generic-message = start-line *(message-header CRLF) CRLF [ message-body ] start-line = Request-Line | Status-Line 複製代碼
每個 message-header 的末尾都會跟着一個 CRLF,並且會有一行空的 CRLF 來用於標識 header fileds 的結束;根據 message-headers 的格式定義,假設咱們有如下的 headers 內容
{
"Connection" = "keep-alive";
"Content-Type" = "application/json;charset=UTF-8";
"Date" = "Sat, 14 Jul 2018 02:31:00 GMT";
"Server" = "openresty/1.13.6.1";
"Transfer-Encoding" = "Identity";
}
複製代碼
在 HTTP/1.1 的header field 中的格式應該是以下所示:
Connection: keep-alive\r\nConnect-Type: application/json;charset=UTF-8\r\nData: Sat, 14 Jul 2018 02:31:00 GMT\r\nServer: openresty/1.13.6.1\r\nTransfer-Encoding: Identity\r\n\r\n
複製代碼
所以,根據定義最終生成的 message headers 的計算方法以下:
- (NSUInteger)ep_getHeadersLength:(NSDictionary *)headers {
NSUInteger headersLength = 0;
NSDictionary<NSString *, NSString *> *headerFields = headers;
NSString *headerStr = @"";
for (NSString *key in headerFields.allKeys) {
headerStr = [headerStr stringByAppendingString:key];
headerStr = [headerStr stringByAppendingString:@": "];
if ([headerFields objectForKey:key]) {
headerStr = [headerStr stringByAppendingString:headerFields[key]];
}
headerStr = [headerStr stringByAppendingString:@"\r\n"];
}
headerStr = [headerStr stringByAppendingString:@"\r\n"];
NSData *headerData = [headerStr dataUsingEncoding:NSUTF8StringEncoding];
headersLength = headerData.length;
return headersLength;
}
複製代碼
可是,當你高興的使用上面的方法去計算 Response Headers 的長度時,你會發現不管你怎麼計算,獲取到的 headers 的長度都會和你在 WireShark 中看到不一致。以下圖所示:
點擊 wireShark 的每一行 message header 在紅色圓圈部分能夠看到這個 message header 的長度,將全部 message header 的長度信息加起來(記得將最後一行的 \r\n 的長度也添加進去),計算後獲得的長度爲(28 + 37 + 46 + 28 + 30 + 2)171,而本身在程序大家計算出來的長度是 172,以下圖所示:
經過對比程序獲取到的 Response headers 的數據內容和 wireShark 獲取到的 headers 內容,能夠發現實際上,這2個 headers 的內容是不一致的:
對比能夠發現,在 wireShark 中 Transfer-Encoding 的值爲 chunked,可是在咱們的應用程序中獲取到的倒是 Identity,爲何會被修改掉筆者找了好久,暫時沒有找到比較有效的數據證實,可是猜想應該是蘋果在 CFNetwork 層幫咱們對收到的數據進行了解碼,因此對於 CFNetwork 的上一層來講,body 中的數據其實已經不是 chunked 編碼。
雖然不知道具體緣由,可是咱們能夠根據 HTTP/1.1 的協議定義,本身來判斷 Transfer-Encoding 的值,在 stackoverflow 已經有人給出了答案。
根據 rfc2616-sec3 上面的定義:
Whenever a transfer-coding is applied to a message-body, the set of transfer-codings MUST include "chunked", unless the message is terminated by closing the connection. When the "chunked" transfer- coding is used, it MUST be the last transfer-coding applied to the message-body. The "chunked" transfer-coding MUST NOT be applied more than once to a message-body. These rules allow the recipient to determine the transfer-length of the message
在 rfc7230 也有相似的描述
If any transfer coding other than chunked is applied to a response payload body, the sender MUST either apply chunked as the final transfer coding or terminate the message by closing the connection.
For example, Transfer-Encoding: gzip, chunked 複製代碼
indicates that the payload body has been compressed using the gzip coding and then chunked using the chunked coding while forming the message body.
對 message-body 採用任意的傳輸編碼類型,這些編碼類型中都必須包括 chunked 傳輸編碼方式,除非鏈接被關閉(鏈接被關閉能夠理解成 headers 中的 "connect" 或 "proxy-connect" 的值爲 close),並且 message-body 最外面的一層編碼必須是 chunked 編碼。
在 rfc2616-sec4 中有一段關於 Message Length 長度計算的定義:
2.If a Transfer-Encoding header field (section 14.41) is present and has any value other than "identity", then the transfer-length is defined by use of the "chunked" transfer-coding (section 3.6), unless the message is terminated by closing the connection.
3.If a Content-Length header field (section 14.13) is present, its decimal value in OCTETs represents both the entity-length and the transfer-length. The Content-Length header field MUST NOT be sent if these two lengths are different(i.e., if a Transfer-Encoding header field is present).
If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored. 複製代碼
若是 header filed 中存在 Transfer-Encoding 而且其值不是 「identity」 ,那麼,傳輸數據的長度是由 「chunked」 編碼決定的。
若是 header field 中存在 Content-Length,這個8位字節的十進制數值表明了實體長度和傳輸數據長度,也就是說,此時實體長度是等於傳輸數據長度,而當實體長度和傳輸數據長度2個值不一致時,Content-Length 能夠不用傳輸。(例如:Transfer-Encoding header field 存在時,即便 Content-Length 存在,也會被忽略掉)
因此綜合上面的全部定義,能夠得出如下結論:
因此,最終的結論和 stackoverflow 給出的判斷條件不是很一致,當 Response Header 中 Transfer-Encoding header field 存在,而且 HTTP 鏈接是常鏈接時,那麼,在網絡中必定是採用了 chunked 編碼傳輸。(判斷的是要忽略大小寫)
BOOL headerKeysContainsTransferEncodingHeaderField = [self stringArray:[headers allKeys] containsStringCaseInsensitive:@"Transfer-Encoding"];
BOOL headerValuseContainsKeepAliveString = [self stringArray:[headers allValues] containsStringCaseInsensitive:@"Keep-alive"] ;
NSString *tranferEncodingKey = [self getStringFromArray:[headers allKeys] caseInsensitiveString:@"Transfer-Encoding"];
BOOL headerTransferEncodingValueIsIdentify = [((NSString *)headers[tranferEncodingKey]).lowercaseString isEqualToString:@"identity"];
if (headerKeysContainsTransferEncodingHeaderField &&
headerValuseContainsKeepAliveString ) {
if (headerTransferEncodingValueIsIdentify) {
headers[tranferEncodingKey] = @"chunked";
}else {
NSString *transferEncodingvalue = headers[[self getStringFromArray:[headers allKeys] caseInsensitiveString:@"Transfer-Encoding"]];
NSString *newTransferEncodingvalue = [transferEncodingvalue stringByAppendingString:@", chunked"];
headers[tranferEncodingKey] = newTransferEncodingvalue;
}
}
- (BOOL)stringArray:(NSArray <NSString *>*)stringArray containsStringCaseInsensitive:(NSString *)string {
for (NSString *value in stringArray) {
if ([value.lowercaseString isEqualToString:string.lowercaseString]) {
return YES;
}
}
return NO;
}
- (NSString *)getStringFromArray:(NSArray <NSString *>*)stringArray caseInsensitiveString:(NSString *)string {
for (NSString *value in stringArray) {
if ([value.lowercaseString isEqualToString:string.lowercaseString]) {
return value;
}
}
return NULL;
}
複製代碼
在學習的過程當中,看到了 header filed 中的 Proxy-Connection 和 Connection,具體能夠看這篇文章Http 請求頭中的 Proxy-Connection ,挺有趣的。
關於 Response Body 的獲取,網上有不少資料都說起到了 content-Length 不許確的問題,並且也說起到了須要考慮到 Content-Encoding 值對數據大小的影響。由於從 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
獲取到的回調數據 data 是已經通過了 CFNetwork 層的解壓,因此按照 data 的長度計算獲取到的值都會比真實的值要大一點。因此須要在應用層本身模擬一次 zip 的壓縮,能夠獲取到更加接近真實傳輸的數據長度。
除了 Content-Encoding 對流量的大小計算有影響,Transfer-Encoding 對流量的計算其實也是有必定的影響的。先簡單瞭解下 Transfer-Encoding 和 Content-Encoding 直接的區別。摘抄至 rfc2616-sec3 :
Content coding values indicate an encoding transformation that has been or can be applied to an entity.
Transfer-coding values are used to indicate an encoding transformation that has been, can be, or may need to be applied to an entity-body in order to ensure "safe transport" through the network.This differs from a content coding in that the transfer-coding is a property of the message, not of the original entity
Content coding 是針對實體的編碼,在整個網絡傳輸過程當中,這個值通常是不會變的。Transfer-coding 是做用在兩個節點直接的數據傳輸編碼,當數據從服務器發送到客戶端時,可能通過了不少個節點,不一樣節點之間是能夠採用不一樣的 Transfer-coding。例如:假設如今收到一個 HTTP 回覆,讀取頭部信息時,獲取到的 content-coding 是 zip,transfer-coding 是 chunked,那麼,接受者須要先將收到的數據進行 chunked 解碼,而後再進行 zip 解碼,才能拿到服務器真正傳輸的數據。
在 HTTP/1.1 協議定義中,只要鏈接時常鏈接,那麼就必定會使用 Transfer-coding 中必定會有 chunked 編碼,並且傳輸數據的最外的一層編碼方式必定是 chunked 編碼。因此,咱們在計算 response body 的數據長度是,還須要考慮下 Transfer-coding 對數據的影響。
chunked 編碼主要是將消息的body轉換一組 chunk,每個 chunk 擁有一個本身大小的標誌,在 chunk 最後有可能會有一個包含實體頭部字段的尾部。容許動態生成內容跟隨必要的信息傳遞給接受者,接受者能夠它來驗證是否已經接受到完整的消息。chunked-Body 的格式以下:
Chunked-Body = *chunk
last-chunk
trailer
CRLF
chunk = chunk-size [ chunk-extension ] CRLF
chunk-data CRLF
chunk-size = 1*HEX
last-chunk = 1*("0") [ chunk-extension ] CRLF
chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
chunk-ext-name = token
chunk-ext-val = token | quoted-string
chunk-data = chunk-size(OCTET)
trailer = *(entity-header CRLF)
複製代碼
chunk-size 是一個 16進制的字符串,代表了 chunk 的大小。在 chunked 編碼的最後,會有一個 chunk-size 值爲0 的字符串,在該字符串後面還有一行空行。(感受理解成一個空的 chunk 就能夠了)
trailer 容許用戶在消息的尾部添加一些額外的 header 字段。
服務器使用 chunked transfer-coding 進行回覆時,除了下面列出的狀況,不容許使用使用 trailder:
通常來講,客戶端收到的回覆都是不會有 trailer,因此能夠不用考慮 trailer 對流量計算的影響。
根據定義,咱們採用 wireShare 捉一個 HTTP 的網絡包,瞭解下實際狀況和理論是否一致:
惋惜的時,在 iOS 中,暫時沒有找到獲取 chunk 個數 和 chunk 大小的方法,只能簡單的將 -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
的一次回調,就當作是收到了一次 chunk(這實際上是很不許確的,後面會後續會嘗試下 hook CFNetwork 的一些方法,看能不能監聽到真實的 chunk 信息)。將每次獲取到的 data 的大小記錄一些,而後在方向根據協議的定義模擬整個編碼過程,計算出編碼後數據的長度信息。具體代碼以下:
- (NSUInteger)ep_bodyLength:(NSData *)body headers:(NSDictionary *)headers chunkedSizes:(NSArray *)chunkedSizes {
NSString *transferEncodingKey = [EPUtils getStringFromArray:headers.allKeys caseInsensitiveString:@"Transfer-Encoding"];
NSString *contentEncodingKey = [EPUtils getStringFromArray:headers.allKeys caseInsensitiveString:@"Content-Encoding"];
NSString *tranferEncoding = [headers[transferEncodingKey] lowercaseString];
NSString *contentEncoding = [headers[contentEncodingKey] lowercaseString];
NSArray *transferEncodingArr = [tranferEncoding componentsSeparatedByString:@", "];
NSUInteger length = body.length;
NSData *bodyData = body;
if ([contentEncoding isEqualToString:@"gzip"]) {
// 須要模擬 gzip 編碼,獲取編碼後的數據長度
}
if (transferEncodingArr.count > 1) {
for (NSString *transferEncoding in transferEncodingArr) {
// 按照順便對數據進行編碼
if ([contentEncoding isEqualToString:@"gzip"]) {
// 須要模擬 gzip 編碼,獲取編碼後的數據長度
}else if ([transferEncoding isEqualToString:@"chunked"]) {
// 長度值獨佔一行,不包括它結尾的CRLF
NSUInteger CRLFSize = [@"/r/n" dataUsingEncoding:NSUTF8StringEncoding].length;
NSUInteger totalChunkedheaderAndFooterSize = 0;
for (NSString *chunkedSizeHex in chunkedSizes) {
NSUInteger chunkedHeaderSize = [chunkedSizeHex dataUsingEncoding:NSUTF8StringEncoding].length + CRLFSize;
totalChunkedheaderAndFooterSize += chunkedHeaderSize;
totalChunkedheaderAndFooterSize += CRLFSize;
}
// 最最有一個 chunked Size 大小爲 0 的 chunked
NSUInteger endChunkedHeaderSize = [@"0" dataUsingEncoding:NSUTF8StringEncoding].length + CRLFSize;
NSUInteger endChunkedFooterSize = CRLFSize;
totalChunkedheaderAndFooterSize += endChunkedHeaderSize;
totalChunkedheaderAndFooterSize += endChunkedFooterSize;
length = length + totalChunkedheaderAndFooterSize;
}
}
}
return length;
}
複製代碼
這裏只是模擬了整個 content-encoding 和 transfer-encoding 的編碼過程,最後的結果只是可以更加貼近真實傳輸數據的長度,可是其實仍是有不少偏差的。這裏雖然在 Content-Encoding 和 TransferEncoding 都進行了 gzip 的編碼判斷,可是在實際狀況下,若是實體已經用 gzip 壓縮過了,transfer-encoding 通常時不會進行 gzip 的壓縮的,通常只會採用 chunked 編碼。至於數據壓縮過程,能夠放到子線程去進行,子線程完成數據壓縮後在存入數據庫就能夠了。
請求流量能夠直接參考 iOS 流量監控分析 中計算。下面主要是記錄下計算流量過程當中的幾個知識點。
因爲缺少相關的接口,因此 reuqest 的 line 部分只能用一個經驗值來計算,通常來講,咱們都會如下面代碼進行計算:
- (NSUInteger)ep_getLineLength {
NSString *lineStr = [NSString stringWithFormat:@"%@ %@ %@\r\n", self.HTTPMethod, self.URL.path, @"HTTP/1.1"];
NSData *lineData = [lineStr dataUsingEncoding:NSUTF8StringEncoding];
return lineData.length;
}
複製代碼
這個計算方法其實已經和咱們平時看到的 line 的格式很一致,可是在某些狀況下,仍是有一些的偏差的,下面主要記錄下偏差的緣由。
在 rfc2616-sec5 定義了 Request-Line 的格式和內容:
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
複製代碼
其中,Method 和 HTTP-Version 的計算其實都沒有偏差,HTTP-Version 雖然咱們寫的是 HTTP/1.1,可是因爲 HTTP/1.0 和 HTTP/2.0 進行 UTF-8 編碼出來長度是一致的,因此沒有偏差。因此偏差的主要來源在於 Request-URI。
在 rfc2616-sec5 中,對於 Request-URI 的定義以下:
Request-URI = "*" | absoluteURI | abs_path | authority
複製代碼
由此能夠,上面代碼中的 self.URL.path 只是 Request-URI 4 種選項中的第3種,因此當 Request-URI 的值是其餘3個選項,而咱們以第3個選項進行計算時,偏差就產生了。
*
: 表示請求不該用於特定的資源,而是應用於服務器自己,而且只在 method 不必定應用於資源時才被容許。
absoluteURI
當請求發給代理時,須要完整的請求地址。
abs_path
廣泛狀況下都是採用相對地址。
由於 abs_path
是最爲廣泛的狀況,因此通常來講,直接用這種方式進行計算就能夠了。
經過 Cocoa 層建立的 request 中,header 的字段缺乏幾個,包括但不限於:
1. Accept
2. Connection / Proxy-Connection
3. Host
複製代碼
若是對於流量有比較高的要求,能夠本身補上一些經驗值。
就 iOS 客戶端本身發送的請求, Accept 大部分狀況下都是 */*
, 若是沒有設置代理,雖然 HTTP/1.1 默認常鏈接,可是通常在 header 裏面仍是會設置下 Connection: keep-alive
,若是設置了代理,那麼應該是Proxy-Connection: keep-alive
。Host 就用 request 中的 Host 就能夠了。
request 中時沒有 cookier 信息的,因此本身手動獲取下,而後添加到 header 中進行計算就能夠了。
- (NSDictionary<NSString *, NSString *> *)dgm_getCookiesByUrl:(NSURL *)url {
NSDictionary<NSString *, NSString *> *cookiesHeader;
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray<NSHTTPCookie *> *cookies = [cookieStorage cookiesForURL:url];
if (cookies.count) {
cookiesHeader = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
}
return cookiesHeader;
}
複製代碼
因爲我是使用 NSURLSession 進行網絡請求,並且 request 也不會像 response 同樣,回去 body 的傳輸進行編碼,因此直接用 NSURLSessionDelegate 的回調就能夠獲取到精確值了
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
複製代碼
在進行網絡請求時,除了流量外,網絡請求的相關時間統計也是很重要的一部分。須要統計的時間包括:
在 iOS 10 即以上的版本,能夠直接用 NSURLSession 的回調,具體資料網上一大堆,就不講了。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
複製代碼
若是要求是 iOS 9 的這幾個時間的監控,在網上找到的全部方法都是使用 fishhook 去 hook BSDSocket 的connect 方法,可是就本人測試沒有成功過,在 fishhook 中也找到了相關的解釋 How to hook socket or connect
另外在 iOS 性能監控方案 Wedjat 中提到了去 hook CFNetwork 的 CFReadStreamCreateForHTTPRequest 的方法,返回一個本身的 Proxy 的方式,我嘗試了好久,也一直沒有成功。經過使用 fishhook 我肯定是 hook 到了相關的方法了,可是在使用 NSURLSession 進行網絡請求時,並不會調用被我 hook 的方法。當若是是本身去調用 CFReadStreamCreateForHTTPRequest 方法,是會調用 hook 的方法。緣由應該是和 fishhook 沒辦法 hook connect 方法是一致的。
因此在時間統計方法,iOS 9 的手機目前只能獲取到 應用層請求開始時間,應用層收到回覆時間
若是有你們有更好的獲取 iOS9 的TCP鏈接的時間,