[gevent源碼分析] c-ares非同步DNS請求

來源:互聯網
上載者:User

標籤:des   style   blog   http   io   os   使用   ar   for   

c-ares是非同步DNS請求庫,libcurl,libevent,wireshark都使用了c-ares,gevent1.0版本前使用的是libevent,

所以它的DNS請求也是使用c-ares,1.0版本後使用cython封裝了c-ares。

c-ares官方文檔,http://c-ares.haxx.se/docs.html。

gevent中DNS預設使用的是線程池版本的,可通過設定GEVENT_RESOLVER=ares環境變數使用c-ares非同步庫。






如何證明的確是非同步呢,試著跑一遍你就知道了?

#coding=utf8import socketimport geventfrom gevent import get_hubfrom gevent.resolver_ares import Resolverr = get_hub().resolver = Resolver(servers=[‘8.8.8.8‘])def f(w):    print w,r.gethostbyname(w)for w in [‘www.google.com‘,‘www.baidu.com‘,‘www.apple.com‘]:    gevent.spawn(f,w)gevent.sleep(6)


在開始之前先講幾個c-ares庫函數,熟悉之後再看ares.pyx會有種和親切的感覺。
cares.ares_library_init(cares.ARES_LIB_INIT_ALL)
初始化ares庫,其實只對windows平台做了處理,主要是為了載入iphlpapi.dll,在非windows平台可不調用。
如果調用一定要在c-ares任何函數之前調用。

cares.ares_library_cleanup()
相對於cares.ares_library_init,在windows平台將釋放iphlpapi.dll,非windows平台可不調用。
gevent中並沒有調用該函數,作者在__dealloc__中也用?號表明了這一點,我不太理解,可能有更好的理由吧。

cares.ares_destroy(self.channel)
銷毀channel,釋放記憶體,關閉開啟的socket,這是在__dealloc__中調用

cares.ares_init_options(&channel, &options, optmask)
這是ares中最核心的函數,用於初始化channel,options,optmask主要是通過channel的__init__構造

cdef public class channel [object PyGeventAresChannelObject, type PyGeventAresChannel_Type]:    def __init__(self, object loop, flags=None, timeout=None, tries=None, ndots=None,                 udp_port=None, tcp_port=None, servers=None):

參數說明:

flags用於控制一查詢行為,如ARES_FLAG_USEVC,將只發送TCP請求(我們知道DNS既有TCP也有UDP)
ARES_FLAG_PRIMARY :只向第一個伺服器發送請求,還有其它選項參考ares_init_options函數文檔
timeout:指明第一次請求的逾時時間,單位為秒,c-ares單位為毫秒,gevent會轉換,第一次之後的逾時c-area有它自己的演算法
tries:請求嘗試次數,預設4次
ndots:最少‘.‘的數量,預設是1,如果大於1,就直接尋找網域名稱,不然會和本地區名合并(init_by_environment設定本地區名)
udp_port,tcp_port:使用的udp,tcp連接埠號碼,預設53
servers:發送dns請求的伺服器,見下面ares_set_servers
ndots多說一句,比如ping bifeng(這是我一同事的主機),檢測發現沒有‘.‘(也就是小於ndots),所以會把本地區給加上去該操作在ares_search.c中ares_search函數中


cares.ares_set_servers(self.channel, cares.ares_addr_node* c_servers)
設定dns請求伺服器,設定完成需要free掉c_servers的記憶體空間,因為ares_set_servers中重新malloc記憶體空間了。
在set_servers中,通過finally free記憶體空間

            c_servers = <cares.ares_addr_node*>malloc(sizeof(cares.ares_addr_node) * length)            if not c_servers:                raise MemoryError            try:                index = 0                for server in servers:                    ...                c_servers[length - 1].next = NULL                index = cares.ares_set_servers(self.channel, c_servers)                if index:                    raise ValueError(strerror(index))            finally:                free(c_servers)

你可能很好奇,c-ares是如何和gevent(libev)的socket關聯起來的,因為DNS的本質也是
socket請求,所以底層也是需要使用作業系統提供的epoll等機制,而c-ares提供了socket狀態變化的介面,
這就可以讓c-ares運行在libev上面,所有的魔法其實都是ares_options.sock_state_cb向外提供的。

#ares.hstruct ares_options {  int flags;  int timeout; /* in seconds or milliseconds, depending on options */  int tries;  ....  ares_sock_state_cb sock_state_cb;  void *sock_state_cb_data;};ARES_OPT_SOCK_STATE_CB void (*sock_state_cb)(void *data, int s, int read, int write)
當dns socket狀態改變時將回調sock_state_cb,而在channel的__init__中設定為gevent_sock_state_callback

def __init__(...)    options.sock_state_cb = <void*>gevent_sock_state_callback    options.sock_state_cb_data = <void*>selfcdef void gevent_sock_state_callback(void *data, int s, int read, int write):    if not data:        return    cdef channel ch = <channel>data    ch._sock_state_callback(s, read, write)
gevent_sock_state_callback只做了一件事就是調用channel的_sock_state_callback,並設定是讀是寫
    cdef _sock_state_callback(self, int socket, int read, int write):        if not self.channel:            return        cdef object watcher = self._watchers.get(socket)        cdef int events = 0        if read:            events |= EV_READ        if write:            events |= EV_WRITE        if watcher is None:            if not events:                return            watcher = self.loop.io(socket, events) #socket第一次,啟動io watcher            self._watchers[socket] = watcher        elif events: #已有watcher,判斷事件是否變化了            if watcher.events == events:                return            watcher.stop()            watcher.events = events #設定新狀態        else:            watcher.stop()            self._watchers.pop(socket, None)            if not self._watchers:                self._timer.stop()            return #沒有事件了,也就是都處理完了,將回調我們的最終回呼函數(如調用gethostbyname時設定的回調)        watcher.start(self._process_fd, watcher, pass_events=True) #watcher設定回調        self._timer.again(self._on_timer) #讓c-ares每秒處理一下逾時和broken_connections
前面io wather的回調self._process_fd主要就是調用cares.ares_process_fd對指定的檔案描述符繼續處理,
cares.ARES_SOCKET_BAD代表該事件不做處理,其實也就是該事件已經處理完了。
    def _process_fd(self, int events, object watcher):        if not self.channel:            return        cdef int read_fd = watcher.fd #只處理的檔案描述符        cdef int write_fd = read_fd        if not (events & EV_READ): #沒有可讀事件,將讀fd設為"不處理"            read_fd = cares.ARES_SOCKET_BAD        if not (events & EV_WRITE): #沒有可寫事件,將寫fd設為"不處理"            write_fd = cares.ARES_SOCKET_BAD        cares.ares_process_fd(self.channel, read_fd, write_fd)

其實到上面c-ares流程已經差不多了,最後會回調設定的最終回調,我們來看一下gethostbyname的操作
定義於resolver_ares.py的gethostbyname函數,調用的是gethostbyname_ex

   def gethostbyname_ex(self, hostname, family=AF_INET):        while True:            ares = self.ares            try:                waiter = Waiter(self.hub) #使用Waiter                ares.gethostbyname(waiter, hostname, family) #調用ares.gethostbyname,設定回調為waiter                result = waiter.get() #我們知道,waiter沒有結果時會切換到hub,完美的和gevent結合起來                if not result[-1]:                    raise gaierror(-5, ‘No address associated with hostname‘)                return result            except gaierror:                if ares is self.ares:                    raise

Waiter定義了__call__方法,所以可以直接作為回呼函數
ares.gethostbyname主要就是調用了cares.ares_gethostbyname(self.channel, name, family, <void*>gevent_ares_host_callback, <void*>arg)
當DNS請求成功或失敗都會回調gevent_ares_host_callback
而gevent_ares_host_callback會回調上面的waiter,並把結果傳給waiter,這邊可以自己看下代碼,比較簡單。
waiter.__call__會switch到之前切換的greenlet,即前面的waiter.get()處,此時將返回result,gethostbyname成功執行。

c-ares真的很美,僅僅通過提供幾個介面,就可以讓自己和其它的架構完美結合,very nice!!!

我之前就是很好奇c-ares的運行方式,內部DNS細節可能並不關注,關注的就是結合問題,花了兩個晚上寫了這篇

文章,覺得很值。






[gevent源碼分析] c-ares非同步DNS請求

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.