• 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函数。

  • 相关阅读:
    帝国 标签模板 使用程序代码 去除html标记 并 截取字符串
    iis6 伪静态 iis配置方法 【图解】
    您来自的链接不存在 帝国CMS
    帝国cms Warning: Cannot modify header information headers already sent by...错误【解决方法】
    .fr域名注册 51元注册.fr域名
    帝国网站管理系统 恢复栏目目录 建立目录不成功!请检查目录权限 Godaddy Windows 主机
    星外虚拟主机管理平台 开通数据库 出现Microsoft OLE DB Provider for SQL Server 错误 '8004' 从字符串向 datetime 转换失败
    ASP.NET 自定义控件学习研究
    CSS层叠样式表之CSS解析机制的优先级
    ASP.NET程序员工作面试网络收藏夹
  • 原文地址:https://www.cnblogs.com/cobbliu/p/3590685.html
Copyright © 2020-2023  润新知