libcurl 域名解析分析
背景
我們公司的產品使用 libcurl 作為基礎網絡庫,線上環境中經常會有域名解析失敗導致的問題。libcurl 的域名解析默認情況下是調用系統 API 完成的,并且用戶的網絡環境可能比較復雜,比如:是否連接了代理服務器,是否開啟防火墻,域名解析過程是不是被運營商劫持等等。所以對于此類問題,通常是只能在特定的機器和網絡環境下復現,非常難確定具體原因。
排查這類問題中我們也逐步有了一些想法:
這篇文章主要記錄一下我是如何實現 libcurl 域名解析 PK 機制的。
首先需要弄清下面兩個問題:
libcurl 的域名解析流程
域名解析是網絡連接的第一步,libcurl 使用了一個狀態機管理網絡連接的每個狀態,代碼在 multi.c 這個文件中:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | static CURLMcode multi_runsingle(struct Curl_multi *multi,struct curltime now,struct Curl_easy *data) {...switch(data->mstate) {case CURLM_STATE_INIT:/* init this transfer. */result=Curl_pretransfer(data);if(!result) {/* after init, go CONNECT */multistate(data, CURLM_STATE_CONNECT);Curl_pgrsTime(data, TIMER_STARTOP);rc = CURLM_CALL_MULTI_PERFORM;}break;case CURLM_STATE_CONNECT_PEND:/* We will stay here until there is a connection available. Thenwe try again in the CURLM_STATE_CONNECT state. */break;case CURLM_STATE_CONNECT:/* Connect. We want to get a connection identifier filled in. */Curl_pgrsTime(data, TIMER_STARTSINGLE);result = Curl_connect(data, &data->easy_conn,&async, &protocol_connect);...case CURLM_STATE_WAITRESOLVE:...result = Curl_resolver_is_resolved(data->easy_conn, &dns);... } |
CURLM_STATE_CONNECT 這個狀態時會發起連接請求 Curl_connect, 解析域名的調用邏輯就封裝在這個方法里面。libcurl 的域名解析有同步和異步兩種方式,默認是異步的方式。異步域名解析的接口定義在 asyn.h 這個頭文件中。
主要接口如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | .../** Curl_resolver_is_resolved()** Called repeatedly to check if a previous name resolve request has* completed. It should also make sure to time-out if the operation seems to* take too long.** Returns normal CURLcode errors.*/ CURLcode Curl_resolver_is_resolved(struct connectdata *conn,struct Curl_dns_entry **dns);/** Curl_resolver_getaddrinfo() - when using this resolver** Returns name information about the given hostname and port number. If* successful, the 'hostent' is returned and the forth argument will point to* memory we need to free after use. That memory *MUST* be freed with* Curl_freeaddrinfo(), nothing else.** Each resolver backend must of course make sure to return data in the* correct format to comply with this.*/ Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,const char *hostname,int port,int *waitp); ... |
Curl_resolver_getaddrinfos 是域名解析的接口,具體實現有兩種方式:asyn-thread 和 asyn-ares; 前者是在開啟了一個線程然后調用系統的域名解析API,后者是使用 c-ares 這個庫實現異步域名解析。默認情況下,libcurl 使用的是 asyn-thread, 如果你想使用 asyn-ares, 需要打開 USE_ARES 這個編譯選項。
Curl_resolver_is_resolved 返回域名解析是否完成,libcurl multi 中有個 hearbeat 就是通過調用這個方法輪詢域名解析是否完成。
整體流程圖如下:
?
域名解析 PK 流程
c-ares 是一個跨平臺異步域名解析庫,完整地實現了 DNS 協議標準,沒有使用平臺相關個API;因此,當我們遇到系統域名解析問題時,很自然地想到了是否可以使用 asyn-ares 做 backup 或者同時發起 asyn-thread 和 asyn-ares 兩種解析方式。
但是 libcurl 中默認只能使用一種域名解析方式,也就時說如果打開了 USE_ARES 編譯選項,就無法使用 aysn-thread 這種方式做域名解析了。所以這里需要重新定義一個編譯選項并實現 asyn 接口。
如何在 libcurl 中實現域名解析 PK 呢?總結一下有以下需要解決的問題:
- 返回解析結果,狀態機更新,繼續處理下一個狀態
- 取消另一個正在處理的域名解析請求
- 如果其中一個請求失敗了,忽略失敗處理邏輯,繼續等待另一個域名解析請求返回
- 如果兩個請求都失敗了,進行失敗處理
?
參考資料
總結
以上是生活随笔為你收集整理的libcurl 域名解析分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TCP-IP详解:快速重传与快速恢复
- 下一篇: 如何分析802.11协议中的BA帧(bl