BIND9源码分析之UDP数据处理

来源:互联网 发布:怎么用c语言玩单片机 编辑:程序博客网 时间:2024/05/01 05:47

本文简要介绍一下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
2
3
4
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函数。

 

原创粉丝点击