BIND9源碼分析之UDP數據處理


本文簡要介紹一下BIND9中的UDP數據處理,包括如何創建socket、設置什么socket參數、多線程環境中如何讓多個線程讀取53端口的數據等等。

 

BIND9的架構采用event-driven和task-based。對於像TCP這樣的事件采用event-driven來等待讀寫時間,而在多線程方面,采用task-based的方式,服務器啟動時由task_manager創建一系列干活的線程task(CPU個數個),然后外部的用戶通過調用isc_task_send函數將事件掛到task_manager的事件隊列中,各線程(task)從這個事件隊列中獲得要干的活,開始干活。

 

對於UDP而言,BIND9的做法是在主進程中創建好UDP socket,然后各個線程都在這個fd上通過recvmsg來收取消息並處理(由於recvmsg是線程安全的,所以多線程環境中可以這樣用而不用加鎖)

關鍵流程如下所示:

1, 主進程ns_interface_listenudp <--ns_interface_setup <-- do_scan <-- ns_interfacemgr_scan0 <-- ns_interfacemgr_scan <-- scan_interfaces <--load_configuration <-- run_server <-- ns_server_create <-- setup <-- main

 其中ns_interface_listenudp的作用是在給定的IP地址和端口上監聽UDP事件:

 首先,調用 dns_dispatch_getudp()從interface manager中獲取到udpdispatch(如果沒有,就創建一個),其中在創建udpdispatch的 dispatch_createudp函數中:
   a, 會調用get_udpsocket()創建新的isc_socket_t對象。get_udpsocket()中關鍵的是open_socket(sockmgr, localaddr,ISC_SOCKET_REUSEADDRESS, &sock)函數,它創建一個socket,並調用bind綁定IP地址和端口。關鍵步驟是:
 
  1),調用socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);創建socket.
  2),調用 flags = fcntl(fd, F_GETFL, 0); flags |= PORT_NONBLOCK;  ret = fcntl(fd, F_SETFL, flags);將socket設置為非阻塞模式
  3),調用setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on))設置套接字選項
  4),調用bind(sock->fd, &sockaddr->type.sa, sockaddr->length) 為該socket綁定IP地址和端口
 
    b, socket創建好,bind好之后。分配64個task(這些task存在dispatch->task[64]數組中),然后為dispatch->ctlevent分配事件,它調用distroy_disp來銷毀一個dispatch(當然其中會調用closesocket來關閉一個socket).
 
  2,通過getudp獲取到udpdispatch之后,就調用 ns_clientmgr_createclients(ifp->clientmgr, ns_g_cpus, ifp, ISC_FALSE);來創建線程監聽這個interface ifp了:
      創建ns_g_cpu個client,然后調用ev = &client->ctlevent;  isc_task_send(client->task, &ev);開始干client_start活
     其中創建client的時候,client->sendevent干client_senddone活;  client->recvevent干client_request活; client->ctlevent干client_start活
 
3, client_start
       對於UDP,執行client_udprecv()函數,client_udprecv的核心是執行isc_socket_recv2函數,isc_socket_recv2的核心是執行socket_recv();socket_recv的核心是doio_recv()。
      doio_recv()中就是recvmsg()系統調用。

 

工作線程

  上面的isc_task_send(client->task, &ev)被調用后,主進程就把任務丟給了工作線程去做。

  

示意圖如下所示:

從上圖中可以看到,當線程通過recvmsg接收到數據后會交給隊列中的線程執行client_request()函數;client_request就是解包處理DNS請求。

如果線程通過recvmsg沒有接收到數據,則將這個fd加入到epoll的監聽隊列中(BIND9有一個單獨的線程執行epoll循環),當這個fd上有讀事件的時候,epoll線程會調用internal_recv()處理UDP數據,internal_recv的核心依然是doio_recv函數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM