標籤:

移動端 IP 優選方案

移動端 IP 優選方案

1. IP 優選目的

無論是從 Local DNS 解析域名,獲取到 IP 列表,還是從第三方的 DNS 解析服務中,獲取到域名對應的 IP 列表。我們獲得多個 IP 後,總是想選取一個最優的 IP 使用,本文主要探討如何在客戶端探測 IP 的連接性以及連接速度,保證返回可用性最好的IP,以達到「IP優選」的目的。

2. 新浪開源的 httpdns 的 sdk 里的測速邏輯

新浪開源一個 HTTPDNSLib ,裡面包含了測速邏輯,GitHub地址如下:

  • 《HTTPDNSLib-for-iOS》
  • 《HTTPDNSLib》

我們以該 sdk 里的測速邏輯為例進行原理解析。

3. IP 測試實現原理

使用 linux socket connect 和 select 函數實現的。 基於以下原理:

  1. 即使套介面是非阻塞的。如果連接的伺服器在同一台主機上,那麼在調用connect 建立連接時,連接通常會立即建立成功,我們必須處理這種情況。
  2. 源自Berkeley的實現(和Posix.1g)有兩條與select 和非阻塞IO相關的規則:

    A. 當連接建立成功時,套介面描述符變成可寫;

B. 當連接出錯時,套介面描述符變成既可讀又可寫。

詳細的測速實現如下,原理參考注釋:

以 iOS 實現為例:

- (int)testSpeedOf:(NSString *)ip port:(int16_t)port { NSString *oldIp = ip; //request time out float rtt = 0.0; //sock:將要被設置或者獲取選項的套接字。 int s = 0; struct sockaddr_in saddr; saddr.sin_family = AF_INET; // MARK: - 設置埠,這裡需要根據需要自定義,默認是80埠。 saddr.sin_port = htons(port); saddr.sin_addr.s_addr = inet_addr([ip UTF8String]); //saddr.sin_addr.s_addr = inet_addr("1.1.1.123"); if( (s=socket(AF_INET, SOCK_STREAM, 0)) < 0) { NSLog(@"ERROR:%s:%d, create socket failed.",__FUNCTION__,__LINE__); return 0; } NSDate *startTime = [NSDate date]; NSDate *endTime; //為了設置connect超時 把socket設置稱為非阻塞 int flags = fcntl(s, F_GETFL,0); fcntl(s,F_SETFL, flags | O_NONBLOCK); //對於阻塞式套接字,調用connect函數將激發TCP的三次握手過程,而且僅在連接建立成功或者出錯時才返回; //對於非阻塞式套接字,如果調用connect函數會之間返回-1(表示出錯),且錯誤為EINPROGRESS,表示連接建立,建立啟動但是尚未完成; //如果返回0,則表示連接已經建立,這通常是在伺服器和客戶在同一台主機上時發生。 int i = connect(s,(struct sockaddr*)&saddr, sizeof(saddr)); if(i == 0) { //建立連接成功,返回rtt時間。 因為connect是非阻塞,所以這個時間就是一個函數執行的時間,毫秒級,沒必要再測速了。 close(s); return 1; } struct timeval tv; int valopt; socklen_t lon; tv.tv_sec = HTTPDNS_SOCKET_CONNECT_TIMEOUT; tv.tv_usec = 0; fd_set myset; FD_ZERO(&myset); FD_SET(s, &myset); // MARK: - 使用select函數,對套接字的IO操作設置超時。 /** select函數 select是一種IO多路復用機制,它允許進程指示內核等待多個事件的任何一個發生,並且在有一個或者多個事件發生或者經歷一段指定的時間後才喚醒它。 connect本身並不具有設置超時功能,如果想對套接字的IO操作設置超時,可使用select函數。 **/ int maxfdp = s+1; int j = select(maxfdp, NULL, &myset, NULL, &tv); if (j == 0) { NSLog(@"INFO:%s:%d, test rtt of (%@) timeout.",__FUNCTION__,__LINE__, oldIp); rtt = HTTPDNS_SOCKET_CONNECT_TIMEOUT_RTT; close(s); return rtt; } if (j < 0) { NSLog(@"ERROR:%s:%d, select function error.",__FUNCTION__,__LINE__); rtt = 0; close(s); return rtt; } /** 對於select和非阻塞connect,注意兩點: [1] 當連接成功建立時,描述符變成可寫; [2] 當連接建立遇到錯誤時,描述符變為即可讀,也可寫,遇到這種情況,可調用getsockopt函數。 **/ lon = sizeof(int); //valopt 表示錯誤信息。 // MARK: - 測試核心邏輯,連接後,獲取錯誤信息,如果沒有錯誤信息就是訪問成功 /*! * //getsockopt函數可獲取影響套接字的選項,比如SOCKET的出錯信息 * (get socket option) */ getsockopt(s, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &lon); //如果有錯誤信息: if (valopt) { NSLog(@"ERROR:%s:%d, select function error.",__FUNCTION__,__LINE__); rtt = 0; } else { endTime = [NSDate date]; rtt = [endTime timeIntervalSinceDate:startTime] * 1000; } close(s); return rtt;}

目前 《阿里雲 HTTPDNS SDK 》 內部已經集成了該邏輯,如果有興趣可以進釘釘群交流:"【客服群】阿里雲移動服務-HTTPDNS",群號:11777313。


推薦閱讀:

基於雲原生的秒殺系統設計思路
國內哪個雲平台比較靠譜?
阿里雲到底是個什麼東西,與亞馬遜的雲服務相比較,它處於什麼位置?
看雲廠商花式作死的九種方法
什麼是雲支付?

TAG:雲計算 |