DNS with libevent

DNS with libevent: high-level and low-level functionality

libevent提供了少量用于解析DNS名字的API,以及用于实现简单DNS服务器的机制。
我们从用于名字查询的高层机制开始介绍,然后介绍底层机制和服务器机制。

Portable blocking name resolution

为移植已经使用阻塞式名字解析的程序,libevent提供了标准getaddrinfo()接口的可移植实现。对于需要运行在没有getaddrinfo()函数,或者getaddrinfo()不像我们的替代函数那样遵循标准的平台上的程序,这个替代实现很有用。

getaddrinfo()接口由RFC 中定义。关于libevent如何不满足其一致性实现的概述,请看下面的“兼容性提示”节。

/* Extension from POSIX.1:2001.  */
#ifdef __USE_XOPEN2K
#define evutil_addrinfo addrinfo
/* Structure to contain information about address of a service provider.  */
struct addrinfo
{
  int ai_flags;			/* Input flags.  */
  int ai_family;		/* Protocol family for socket.  */
  int ai_socktype;		/* Socket type.  */
  int ai_protocol;		/* Protocol for socket.  */
  socklen_t ai_addrlen;		/* Length of socket address.  */
  struct sockaddr *ai_addr;	/* Socket address for socket.  */
  char *ai_canonname;		/* Canonical name for service location.  */
  struct addrinfo *ai_next;	/* Pointer to next in list.  */
};
/** @name evutil_getaddrinfo() error codes

    These values are possible error codes for evutil_getaddrinfo() and
    related functions.

    @{
*/
#if defined(EAI_ADDRFAMILY) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_EAI_ADDRFAMILY EAI_ADDRFAMILY
#else
#define EVUTIL_EAI_ADDRFAMILY -901
#endif
#if defined(EAI_AGAIN) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_EAI_AGAIN EAI_AGAIN
#else
#define EVUTIL_EAI_AGAIN -902
#endif
#if defined(EAI_BADFLAGS) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_EAI_BADFLAGS EAI_BADFLAGS
#else
#define EVUTIL_EAI_BADFLAGS -903
#endif
#if defined(EAI_FAIL) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_EAI_FAIL EAI_FAIL
#else
#define EVUTIL_EAI_FAIL -904
#endif
#if defined(EAI_FAMILY) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_EAI_FAMILY EAI_FAMILY
#else
#define EVUTIL_EAI_FAMILY -905
#endif
#if defined(EAI_MEMORY) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_EAI_MEMORY EAI_MEMORY
#else
#define EVUTIL_EAI_MEMORY -906
#endif
/* This test is a bit complicated, since some MS SDKs decide to
 * remove NODATA or redefine it to be the same as NONAME, in a
 * fun interpretation of RFC 2553 and RFC 3493. */
#if defined(EAI_NODATA) && defined(EVENT__HAVE_GETADDRINFO) && (!defined(EAI_NONAME) || EAI_NODATA != EAI_NONAME)
#define EVUTIL_EAI_NODATA EAI_NODATA
#else
#define EVUTIL_EAI_NODATA -907
#endif
#if defined(EAI_NONAME) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_EAI_NONAME EAI_NONAME
#else
#define EVUTIL_EAI_NONAME -908
#endif
#if defined(EAI_SERVICE) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_EAI_SERVICE EAI_SERVICE
#else
#define EVUTIL_EAI_SERVICE -909
#endif
#if defined(EAI_SOCKTYPE) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_EAI_SOCKTYPE EAI_SOCKTYPE
#else
#define EVUTIL_EAI_SOCKTYPE -910
#endif
#if defined(EAI_SYSTEM) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_EAI_SYSTEM EAI_SYSTEM
#else
#define EVUTIL_EAI_SYSTEM -911
#endif

#define EVUTIL_EAI_CANCEL -90001

#if defined(AI_PASSIVE) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_AI_PASSIVE AI_PASSIVE
#else
#define EVUTIL_AI_PASSIVE 0x1000
#endif
#if defined(AI_CANONNAME) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_AI_CANONNAME AI_CANONNAME
#else
#define EVUTIL_AI_CANONNAME 0x2000
#endif
#if defined(AI_NUMERICHOST) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_AI_NUMERICHOST AI_NUMERICHOST
#else
#define EVUTIL_AI_NUMERICHOST 0x4000
#endif
#if defined(AI_NUMERICSERV) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_AI_NUMERICSERV AI_NUMERICSERV
#else
#define EVUTIL_AI_NUMERICSERV 0x8000
#endif
#if defined(AI_V4MAPPED) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_AI_V4MAPPED AI_V4MAPPED
#else
#define EVUTIL_AI_V4MAPPED 0x10000
#endif
#if defined(AI_ALL) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_AI_ALL AI_ALL
#else
#define EVUTIL_AI_ALL 0x20000
#endif
#if defined(AI_ADDRCONFIG) && defined(EVENT__HAVE_GETADDRINFO)
#define EVUTIL_AI_ADDRCONFIG AI_ADDRCONFIG
#else
#define EVUTIL_AI_ADDRCONFIG 0x40000
#endif
/**@}*/

int evutil_getaddrinfo(const char *nodename, const char *servname, 
                       const struct addrinfo *hints_in, struct addrinfo **res);

void
 evutil_freeaddrinfo(struct evutil_addrinfo *ai)
{
#ifdef EVENT__HAVE_GETADDRINFO
	if (!(ai->ai_flags & EVUTIL_AI_LIBEVENT_ALLOCATED)) {
		freeaddrinfo(ai);
		return;
	}
#endif
	while (ai) {
		struct evutil_addrinfo *next = ai->ai_next;
		if (ai->ai_canonname)
			mm_free(ai->ai_canonname);
		mm_free(ai);
		ai = next;
	}
}

evutil_getaddrinfo()函数试图根据hints给出的规则,解析指定的nodename和servname,建立一个evutil_addrinfo结构体链表,将其存储在*res中。成功时函数返回0,失败时返回非零的错误码

必须至少提供nodenameservname中的一个。如果提供了nodename,则它是IPv4字面地址(如127.0.0.1)、IPv6字面地(如::1、或者是DNS名字(如www.example.com)。如果提供了servname,则它是某网络服务的符号名(如https),或者是一个包含十进制端口号的字符串(如443)。

如果不指定servname,则res中的端口号将是零。如果不指定nodename,则res中的地址要么是localhost(默认),要么是“任意”(如果设置了EVUTIL_AI_PASSIVE)。

hints的ai_flags字段指示evutil_getaddrinfo如何进行查询,它可以包含0个或者多个以或运算连接的下述标志:

  • EVUTIL_AI_PASSIVE

这个标志指示将地址用于监听,而不是连接。通常二者没有差别,除非nodename为空:对于连接,空的nodename表示localhost(127.0.0.1或者::1);而对于监听,空的nodename表示任意(0.0.0.0或者::0)。

  • EVUTIL_AI_CANONNAME

如果设置了这个标志,则函数试图在ai_canonname字段中报告标准名称。

  • EVUTIL_AI_NUMERICHOST

如果设置了这个标志,函数仅仅解析数值类型的IPv4和IPv6地址;如果nodename要求名字查询,函数返回EVUTIL_EAI_NONAME错误。

  • EVUTIL_AI_NUMERICSERV

如果设置了这个标志,函数仅仅解析数值类型的服务名。如果servname不是空,也不是十进制整数,函数返回EVUTIL_EAI_NONAME错误。

  • EVUTIL_AI_V4MAPPED

这个标志表示,如果ai_family是AF_INET6,但是找不到IPv6地址,则应该以v4映射(v4-mapped)型IPv6地址的形式返回结果中的IPv4地址。当前evutil_getaddrinfo()不支持这个标志,除非操作系统支持它。

  • EVUTIL_AI_ALL

如果设置了这个标志和EVUTIL_AI_V4MAPPED,则无论结果是否包含IPv6地址,IPv4地址都应该以v4映射型IPv6地址的形式返回。当前evutil_getaddrinfo()不支持这个标志,除非操作系统支持它。

  • EVUTIL_AI_ADDRCONFIG

如果设置了这个标志,则只有系统拥有非本地的IPv4地址时,结果才包含IPv4地址;只有系统拥有非本地的IPv6地址时,结果才包含IPv6地址。

hints的ai_family字段指示evutil_getaddrinfo()应该返回哪个地址。字段值可以是AF_INET,表示只请求IPv4地址;也可以是AF_INET6,表示只请求IPv6地址;或者用AF_UNSPEC表示请求所有可用地址。

hints的ai_socktype和ai_protocol字段告知evutil_getaddrinfo()将如何使用返回的地址。这两个字段值的意义与传递给socket()函数的socktype和protocol参数值相同。

成功时函数新建一个evutil_addrinfo结构体链表,存储在*res中,链表的每个元素通过ai_next指针指向下一个元素。因为链表是在堆上分配的,所以需要调用evutil_freeaddrinfo()进行释放。

如果失败,函数返回数值型的错误码:

  • EVUTIL_EAI_ADDRFAMILY

    请求的地址族对nodename没有意义。

  • EVUTIL_EAI_AGAIN

    名字解析中发生可以恢复的错误,请稍后重试。

  • EVUTIL_EAI_FAIL

    名字解析中发生不可恢复的错误:解析器或者DNS服务器可能已经崩溃。

  • EVUTIL_EAI_BADFLAGS

    hints中的ai_flags字段无效。

  • EVUTIL_EAI_FAMILY

    不支持hints中的ai_family字段。

  • EVUTIL_EAI_MEMORY

    回应请求的过程耗尽内存。

  • EVUTIL_EAI_NODATA

    请求的主机不存在。

  • EVUTIL_EAI_SERVICE

    请求的服务不存在。

  • EVUTIL_EAI_SOCKTYPE

    不支持请求的套接字类型,或者套接字类型与ai_protocol不匹配。

  • EVUTIL_EAI_SYSTEM

    名字解析中发生其他系统错误,更多信息请检查errno。

  • EVUTIL_EAI_CANCEL

    应用程序在解析完成前请求取消。evutil_getaddrinfo()函数从不产生这个错误,但是后面描述的evdns_getaddrinfo()可能产生这个错误。
    调用evutil_gai_strerror()可以将上述错误值转化成描述性的字符串。

Attention

如果操作系统定义了addrinfo结构体,则evutil_addrinfo仅仅是操作系统内置的addrinfo结构体的别名。类似地,如果操作系统定义了AI_标志,则相应的EVUTIL_AI_标志仅仅是本地标志的别名;如果操作系统定义了EAI_错误,则相应的EVUTIL_EAI_只是本地错误码的别名。

example

#include <event2/util.h>
#include <event2/event.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <memory.h>
 
#include <assert.h>
#include <unistd.h>

/* Set N bytes of S to C.  */
extern void *memset (void *__s, int __c, size_t __n) __THROW __nonnull ((1));
evutil_socket_t 
get_tcp_socket_for_host(const char *hostname,ev_uint64_t port){
    char port_str[6];
    struct evutil_addrinfo hints;
    struct evutil_addrinfo *res = nullptr;

    int err;
    evutil_socket_t sock;
    evutil_snprintf(port_str,sizeof(port_str),"%llu",(unsigned long long)port);
    memset((&hints), 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = EVUTIL_AI_ADDRCONFIG;
    hints.ai_protocol = IPPROTO_TCP;
    err = evutil_getaddrinfo(hostname,port_str,&hints,&res);
    if(err < 0){
         
        return -1;
    }
    assert(res);
    sock = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
    if(sock < 0){
        
        EVUTIL_CLOSESOCKET(sock);
        return -1;
    }
    if(connect(sock,res->ai_addr,res->ai_addrlen) < 0){
        EVUTIL_CLOSESOCKET(sock);
        return -1;
    }
    return sock;
    
}

上述函数和常量是2.0.3-alpha版本新增加的,声明在event2/util.h中。

Non-blocking name resolution using evdns_getaddrinfo()

通常的getaddrinfo(),以及上面的evutil_getaddrinfo()的问题是,它们是阻塞的:调用线程必须等待函数查询DNS服务器,等待回应。对于libevent,这可能不是期望的行为。

对于非阻塞式应用,libevent提供了一组函数用于启动DNS请求,让libevent等待服务器回应。

/** Callback for evdns_getaddrinfo. */
typedef void (*evdns_getaddrinfo_cb)(int result, struct evutil_addrinfo *res, void *arg);

/* State data used to implement an in-progress getaddrinfo. */
struct evdns_getaddrinfo_request {
	struct evdns_base *evdns_base;
	/* Copy of the modified 'hints' data that we'll use to build
	 * answers. */
	struct evutil_addrinfo hints;
	/* The callback to invoke when we're done */
	evdns_getaddrinfo_cb user_cb;
	/* User-supplied data to give to the callback. */
	void *user_data;
	/* The port to use when building sockaddrs. */
	ev_uint16_t port;
	/* The sub_request for an A record (if any) */
	struct getaddrinfo_subrequest ipv4_request;
	/* The sub_request for an AAAA record (if any) */
	struct getaddrinfo_subrequest ipv6_request;

	/* The cname result that we were told (if any) */
	char *cname_result;

	/* If we have one request answered and one request still inflight,
	 * then this field holds the answer from the first request... */
	struct evutil_addrinfo *pending_result;
	/* And this event is a timeout that will tell us to cancel the second
	 * request if it's taking a long time. */
	struct event timeout;

	/* And this field holds the error code from the first request... */
	int pending_error;
	/* If this is set, the user canceled this request. */
	unsigned user_canceled : 1;
	/* If this is set, the user can no longer cancel this request; we're
	 * just waiting for the free. */
	unsigned request_done : 1;
};


struct evdns_getaddrinfo_request *evdns_getaddrinfo(struct evdns_base *dns_base,
    const char *nodename, const char *servname,
    const struct evutil_addrinfo *hints_in,
    evdns_getaddrinfo_cb cb, void *arg)void evdns_getaddrinfo_cancel(struct evdns_getaddrinfo_request *data)   

除了不会阻塞在DNS查询上,而是使用libevent的底层DNS机制进行查询外,evdns_getaddrinfo()和evutil_getaddrinfo()是一样的。因为函数不是总能立即返回结果,所以需要提供一个evdns_getaddrinfo_cb类型的回调函数,以及一个给回调函数的可选的用户参数。

此外,调用evdns_getaddrinfo()还要求一个evdns_base指针。evdns_base结构体为libevent的DNS解析器保持状态和配置。关于如何获取evdns_base指针,请看下一节。

如果失败或者立即成功,函数返回NULL。否则,函数返回一个evdns_getaddrinfo_request指针。在解析完成之前可以随时使用evdns_getaddrinfo_cancel()和这个指针来取消解析。

注意:不论evdns_getaddrinfo()是否返回NULL,是否调用了evdns_getaddrinfo_cancel(),回调函数总是会被调用。

evdns_getaddrinfo()内部会复制nodename、servname和hints参数,所以查询进行过程中不必保持这些参数有效。

example

使用evdns_getaddrinfo()的非阻塞查询

#include <event2/event.h>
#include <event2/util.h>
#include <event2/dns.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

int n_pending_requests = 0;

struct event_base *base = NULL;

struct user_data{
    char * name;/*the name we're resloving*/
    int idx;/*its  position on the command line*/
};


void callback(int errcode, struct evutil_addrinfo *addr, void *ptr) {
    // 将指针转换为用户数据结构
    struct user_data *data = (struct user_data*)ptr;
    const char *name = data->name;

    if (errcode) {
        // 如果出错,打印错误信息
        printf("%d %s -> %s\n", data->idx, name, evutil_gai_strerror(errcode));
    } else {
        // 打印成功的地址信息
        struct evutil_addrinfo *ai;
        printf("%d. %s", data->idx, name);

        // 如果存在规范名称,则打印
        if (addr->ai_canonname) {
            printf("[%s]", addr->ai_canonname);
        }
        puts("");

        // 遍历地址链表
        for (ai = addr; ai; ai = ai->ai_next) {
            char buf[128];
            const char *s = NULL;

            // 根据地址族处理不同类型的地址
            if (ai->ai_family == AF_INET) {
                struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr;
                s = evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf));
            } else if (ai->ai_family == AF_INET6) {
                struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr;
                s = evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof(buf));
            }

            // 如果成功转换地址,打印它
            if (s) {
                printf(" -> %s\n", s);
            }
        }

        // 释放地址信息结构
        evutil_freeaddrinfo(addr);
    }

    // 释放用户数据中的名称和数据结构
    free(data->name);
    free(data);

    // 如果没有待处理请求,退出事件循环
    if (--n_pending_requests == 0) {
        event_base_loopexit(base, NULL);
    }
}


int main(int argc, char **argv) {

    int i ;
    struct evdns_base *dnsbase;
    if(argc == 1){
        puts("No address given");
        return 0;
    }

    base = event_base_new();
    if(!base){
        puts("event_base_new failed");
        return 1;
    }

    dnsbase = evdns_base_new(base, 1);

    if(!dnsbase)
        return 2;


    for(i = 1; i < argc; ++i){
        struct evutil_addrinfo hints;
        struct evdns_getaddrinfo_request *req;
        struct user_data *data;
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = EVUTIL_AI_CANONNAME;
        hints.ai_protocol = IPPROTO_TCP;

        if(!(data = (struct user_data*)malloc(sizeof(struct user_data)))){
            puts("malloc failed");
            return 3;
        }
        
        if(!(data->name = strdup(argv[i]))){
            puts("strdup failed");
            return 4;
        }

        data->idx = i;
        ++n_pending_requests;
        req = evdns_getaddrinfo(dnsbase, argv[i], NULL,&hints, callback,data);

        if(req == NULL){
            puts("evdns_getaddrinfo failed");
            return 5;
        }


    }

    if(n_pending_requests)
        event_base_dispatch(base);
    evdns_base_free(dnsbase,0);
    event_base_free(base);
}

上述函数是2.0.3-alpha版本新增加的,声明在event2/dns.h中。

Create and configure evdns_base

使用evdns进行非阻塞DNS查询之前需要配置一个evdns_base。evdns_base存储名字服务器列表和DNS配置选项.
跟踪活动的、进行中的DNS请求。

struct evdns_base * evdns_base_new(struct event_base *event_base, int flags);
void evdns_base_free(struct evdns_base *base, int fail_requests);

成功时evdns_base_new()返回一个新建的evdns_base,失败时返回NULL。如果initialize参数为true,函数试图根据操作系统的默认值配置evdns_base;否则,函数让evdns_base为空,不配置名字服务器和选项。

可以用evdns_base_free()释放不再使用的evdns_base。如果fail_request参数为true,函数会在释放evdns_base前让所有进行中的请求使用取消错误码调用其回调函数。

Initialize evdns using system configuration

如果需要更多地控制evdns_base如何初始化,可以为evdns_base_new()的initialize参数传递0,然后调用下述函数。

#define DNS_OPTION_HOSTSFILE 1
#define DNS_OPTION_NAMESERVERS 2
#define DNS_OPTION_MISC 4
#define DNS_OPTION_HOSTSFILE 8
#define DNS_OPTIONS_ALL 15

int evdns_base_resolv_conf_parse(struct evdns_base *base, int flags, const char *const filename);

#ifdef WIN32
    int evdns_base_resolv_config_windows_namseservers(struct evdns_base *);
    #define EVDNS_BASE_CONFIG_WINDOWS_NAMESERVERS_IMPLEMENTED
#endif

evdns_base_resolv_conf_parse()函数扫描resolv.conf格式的文件filename,从中读取flags指示的选项(关于resolv.conf文件的更多信息,请看Unix手册)。

  • DNS_OPTION_SEARCH

    请求从resolv.conf文件读取domain和search字段以及ndots选项,使用它们来确定使用哪个域(如果存在)来搜索不是全限定的主机名。

  • DNS_OPTION_NAMESERVERS

    请求从resolv.conf中读取名字服务器地址。

  • DNS_OPTION_MISC

    请求从resolv.conf文件中读取其他配置选项。

  • DNS_OPTION_HOSTSFILE

    请求从/etc/hosts文件读取主机列表。

  • DNS_OPTION_ALL

    请求从resolv.conf文件获取尽量多的信息。

Windows中没有可以告知名字服务器在哪里的resolv.conf文件,但可以用evdns_base_config_windows_nameservers()函数从注册表(或者NetworkParams,或者其他隐藏的地方)读取名字服务器。

resolv.conf File Format

resolv.conf是一个文本文件,每一行要么是空行,要么包含以#开头的注释,要么由一个跟随零个或者多个参数的标记组成。可以识别的标记有:
  • nameserver

    必须后随一个名字服务器的IP地址。作为一个扩展,libevent允许使用IP:Port或者[IPv6]:port语法为名字服务器指定非标准端口。

  • domain

本地域名

  • search

    解析本地主机名时要搜索的名字列表。如果不能正确解析任何含有少于“ndots”个点的本地名字,则在这些域名中进行搜索。比如说,如果“search”字段值为example.com,“ndots”为1,则用户请求解析“www”时,函数认为那是“www.example.com”。

  • options

空格分隔的选项列表。选项要么是空字符串,要么具有格式option:value(如果有参数)。可识别的选项有:
ndots:INTEGER
用于配置搜索,请参考上面的“search”,默认值是1。

 timeout:FLOAT

	等待DNS服务器响应的时间,单位是秒。默认值为5秒。

 max-timeouts:INT

	名字服务器响应超时几次才认为服务器当机?默认是3次。

 max-inflight:INT

	最多允许多少个未决的DNS请求?(如果试图发出多于这么多个请求,则过多的请求将被延迟,直到某个请求被响应或者超时)。默认值是XXX。

 attempts:INT

	在放弃之前重新传输多少次DNS请求?默认值是XXX。

如果非零,evdns会为发出的DNS请求设置随机的事务ID,并且确认回应具有同样的随机事务ID值。这种称作“0x20 hack”的机制可以在一定程度上阻止对DNS的简单激活事件攻击。这个选项的默认值是1。

(这段原文不易理解,译文可能很不准确。这里给出原文:If nonzero,we randomize the case on outgoing DNS requests and make sure that replies have the same case as our requests.This so-called “0x20 hack” can help prevent some otherwise simple active events against DNS.)

 bind-to:ADDRESS

	如果提供,则向名字服务器发送数据之前绑定到给出的地址。对于2.0.4-alpha版本,这个设置仅应用于后面的名字服务器条目。

 initial-probe-timeout:FLOAT

	确定名字服务器当机后,libevent以指数级降低的频率探测服务器以判断服务器是否恢复。这个选项配置(探测时间间隔)序列中的第一个
	超时,单位是秒。默认值是10。

  getaddrinfo-allow-skew:FLOAT

	同时请求IPv4和IPv6地址时,evdns_getaddrinfo()用单独的DNS请求包分别请求两种地址,
	因为有些服务器不能在一个包中同时处理两种请求。服务器回应一种地址类型后,函数等待一段时间确定另一种类型的地址是否到达。
	这个选项配置等待多长时间,单位是秒。默认值是3秒。不识别的字段和选项会被忽略。

Manually configure evdns

如果需要更精细地控制evdns的行为,可以使用下述函数:


int evdns_base_nameserver_sockaddr_add(struct evdns_base *base, const struct sockaddr *sa, socklen_t len,
									   unsigned int flags);
int evdns_base_nameserver_ip_add(struct evdns_base *base, const char *ip_as_string);
int evdns_base_load_hosts(struct evdns_base *base, const char *hosts_fname);

void evdns_base_search_clear(struct evdns_base *base);
void evdns_base_search_add(struct evdns_base *base, const char *domain);
void evdns_base_search_ndots_set(struct evdns_base *base, int ndots);

int evdns_base_set_option(struct evdns_base *base, const char *option, const char *val);
int evdns_base_count_nameservers(struct evdns_base *base);

evdns_base_nameserver_sockaddr_add()函数通过地址向evdns_base添加名字服务器。当前忽略flags参数,为向前兼容考虑,应该传入0。成功时函数返回0,失败时返回负值。(这个函数在2.0.7-rc版本加入)

evdns_base_nameserver_ip_add()函数向evdns_base加入字符串表示的名字服务器,格式可以是IPv4地址、IPv6地址、带端口号的IPv4地址(IPv4:Port),或者带端口号的IPv6地址([IPv6]:Port)。成功时函数返回0,失败时返回负值。

evdns_base_load_hosts()函数从hosts_fname文件中载入主机文件(格式与/etc/hosts相同)。成功时函数返回0,失败时返回负值。

evdns_base_search_clear()函数从evdns_base中移除所有(通过search配置的)搜索后缀;evdns_base_search_add()则添加后缀。

evdns_base_set_option()函数设置evdns_base中某选项的值。选项和值都用字符串表示。(2.0.3版本之前,选项名后面必须有一个冒号)

解析一组配置文件后,可以使用evdns_base_count_nameservers()查看添加了多少个名字服务器。

Library configuration

有一些为evdns模块设置库级别配置的函数:

/**
  A callback that is invoked when a log message is generated

  @param is_warning indicates if the log message is a 'warning'
  @param msg the content of the log message
 */
typedef void (*evdns_debug_log_fn_type)(int is_warning, const char *msg);
void evdns_set_log_fn(evdns_debug_log_fn_type);
void evdns_set_transaction_id_fn(evdns_debug_log_fn_type);

因为历史原因,evdns子系统有自己单独的日志。evdns_set_log_fn()可以设置一个回调函数,以便在丢弃日志消息前做一些操作。

为安全起见,evdns需要一个良好的随机数发生源:使用0x20 hack的时候,evdns通过这个源来获取难以猜测(hard-to-guess)的事务ID以随机化查询(请参考“randomize-case”选项)。然而,较老版本的libevent没有自己的安全的RNG(随机数发生器)。此时可以通过调用evdns_set_transaction_id_fn(),传入一个返回难以预测(hard-to-predict)的两字节无符号整数的函数,来为evdns设置一个更好的随机数发生器。

2.0.4-alpha以及后续版本中,libevent有自己内置的安全的RNG,evdns_set_transaction_id_fn()就没有效果了。

Low-level DNS interface

有时候需要启动能够比从evdns_getaddrinfo()获取的DNS请求进行更精细控制的特别的DNS请求,libevent也为此提供了接口。

Missing features

当前libevent的DNS支持缺少其他底层DNS系统所具有的一些特征,如支持任意请求类型和TCP请求。如果需要evdns所不具有的特征,欢迎贡献一个补丁。也可以看看其他全特征的DNS库,如c-ares。

/**
 * The callback that contains the results from a lookup.
 * - result is one of the DNS_ERR_* values (DNS_ERR_NONE for success)
 * - type is either DNS_IPv4_A or DNS_PTR or DNS_IPv6_AAAA
 * - count contains the number of addresses of form type
 * - ttl is the number of seconds the resolution may be cached for.
 * - addresses needs to be cast according to type.  It will be an array of
 *   4-byte sequences for ipv4, or an array of 16-byte sequences for ipv6,
 *   or a nul-terminated string for PTR.
 */
typedef void (*evdns_callback_type) (int result, char type, int count, int ttl, 
									 void *addresses, void *arg);

struct evdns_request* 
	evdns_base_resolve_ipv4(evutil_socket_t fd, const char *hostname, 
                                              evdns_callback_type callback, void *arg);

struct evdns_request* 
	evdns_base_resolve_ipv6(struct evdns_base *base, const char *name,
                                              int flags, evdns_callback_type callback, void *ptr);

struct evdns_request* 
	evdns_base_resolve_reverse(struct evdns_base *base, const struct in_addr *in,
                                                 int flags, evdns_callback_type callback, void *ptr);

struct evdns_request* 
	evdns_base_resolve_reverse_ipv6(struct evdns_base *base, const struct in6_addr *in,
                                                      int flags, evdns_callback_type callback, void *ptr);

这些解析函数为一个特别的记录发起DNS请求。每个函数要求一个evdns_base用于发起请求、一个要查询的资源(正向查询时的主机名,或者反向查询时的地址)、一组用以确定如何进行查询的标志、一个查询完成时调用的回调函数,以及一个用户提供的传给回调函数的指针。

flags参数可以是0,也可以用DNS_QUERY_NO_SEARCH明确禁止原始查询失败时在搜索列表中进行搜索。DNS_QUERY_NO_SEARCH对反向查询无效,因为反向查询不进行搜索。

请求完成(不论是否成功)时回调函数会被调用。回调函数的参数是指示成功或者错误码(参看下面的DNS错误表)的result、一个记录类型(DNS_IPv4_A、DNS_IPv6_AAAA,或者DNS_PTR)、addresses中的记录数、以秒为单位的存活时间、地址(查询结果),以及用户提供的指针。

发生错误时传给回调函数的addresses参数为NULL。没有错误时:对于PTR记录,addresses是空字符结束的字符串;对于IPv4记录,则是网络字节序的四字节地址值数组;对于IPv6记录,则是网络字节序的16字节记录数组。(注意:即使没有错误,addresses的个数也可能是0。名字存在,但是没有请求类型的记录时就会出现这种情况)

可能传递给回调函数的错误码如下:

DNS ERRORCODE

错误码
意义
DNS_ERR_NONE没有错误
DNS_ERR_FORMAT服务器不识别查询请求
DNS_ERR_SERVERFAILED服务器内部错误
DNS_ERR_NOTEXIST没有给定名字的记录
DNS_ERR_NOTIMPL服务器不识别这种类型的查询
DNS_ERR_REFUSED因为策略设置,服务器拒绝查询
DNS_ERR_TRUNCATEDDNS记录不适合UDP分组
DNS_ERR_UNKNOWN未知的内部错误
DNS_ERR_TIMEOUT等待超时
DNS_ERR_SHUTDOWN用户请求关闭evdns系统
DNS_ERR_CANCEL用户请求取消查询

可以用下述函数将错误码转换成错误描述字符串

const char *
evdns_err_to_string(int err)
{
    switch (err) {
	case DNS_ERR_NONE: return "no error";
	case DNS_ERR_FORMAT: return "misformatted query";
	case DNS_ERR_SERVERFAILED: return "server failed";
	case DNS_ERR_NOTEXIST: return "name does not exist";
	case DNS_ERR_NOTIMPL: return "query not implemented";
	case DNS_ERR_REFUSED: return "refused";

	case DNS_ERR_TRUNCATED: return "reply truncated or ill-formed";
	case DNS_ERR_UNKNOWN: return "unknown";
	case DNS_ERR_TIMEOUT: return "request timed out";
	case DNS_ERR_SHUTDOWN: return "dns subsystem shut down";
	case DNS_ERR_CANCEL: return "dns request canceled";
	case DNS_ERR_NODATA: return "no records in the reply";
	default: return "[Unknown error code]";
    }
}	

每个解析函数都返回不透明的evdns_request结构体指针。回调函数被调用前的任何时候都可以用这个指针来取消请求:

/* exported function */
void evdns_cancel_request(struct evdns_base *base, struct evdns_request *handle)

Suspend DNS client operation and change name server

有时候需要重新配置或者关闭DNS子系统,但不能影响进行中的DNS请求。

int evdns_base_clear_nameservers_and_suspend(struct evdns_base *base);
int evdns_base_resume(struct evdns_base *base);

evdns_base_clear_nameservers_and_suspend()会移除所有名字服务器,但未决的请求会被保留,直到随后重新添加名字服务器,调用evdns_base_resume()。

这些函数成功时返回0,失败时返回-1。它们在2.0.1-alpha版本引入。

DNS server API

libevent为实现不重要的DNS服务器,响应通过UDP传输的DNS请求提供了简单机制。本章节要求读者对DNS协议有一定的了解。

Create and close DNS server

struct evdns_server_port* evdns_add_server_port_with_base(struct event_base *base, int socket, int flags, 
                                    evdns_request_callback_fn_type callback, void *user_data);
/**
   A callback to implement a DNS server.  The callback function receives a DNS
   request.  It should then optionally add a number of answers to the reply
   using the evdns_server_request_add_*_reply functions, before calling either
   evdns_server_request_respond to send the reply back, or
   evdns_server_request_drop to decline to answer the request.

   @param req A newly received request
   @param user_data A pointer that was passed to
      evdns_add_server_port_with_base().
 */
typedef void (*evdns_request_callback_fn_type)(struct evdns_server_request *, void * user_data);
void evdns_close_server_port(struct evdns_server_port *port);

要开始监听DNS请求,调用evdns_add_server_port_with_base()。函数要求用于事件处理的event_base、用于监听的UDP套接字、可用的标志(现在总是0)、一个收到DNS查询时要调用的回调函数,以及要传递给回调函数的用户数据指针。函数返回evdns_server_port对象。

使用DNS服务器完成工作后,需要调用evdns_close_server_port()。

evdns_add_server_port_with_base()是2.0.1-alpha版本引入的,而evdns_close_server_port()则由1.3版本引入。

Check DNS request

不幸的是,当前libevent没有提供较好的获取DNS请求的编程接口,用户需要包含event2/dns_struct.h文件,查看evdns_server_request结构体。

未来版本的libevent应该会提供更好的方法。

 /*
 * Structures used to implement a DNS server.
 */
struct evdns_server_request {
	int flags;
	int nquestions;
	struct evdns_server_question **questions;
};

#define EVDNS_QTYPE_ALL  255;
#define EVDNS_QTYPE_AXFR 252;


/*
 * Structures used to implement a DNS server.
 */

struct evdns_server_request {
	int flags;
	int nquestions;
	struct evdns_server_question **questions;
};
struct evdns_server_question {
	int type;
#ifdef __cplusplus
	int dns_question_class;
#else
	/* You should refer to this field as "dns_question_class".  The
	 * name "class" works in C for backward compatibility, and will be
	 * removed in a future version. (1.5 or later). */
	int class;
#define dns_question_class class
#endif
	char name[1];
};

#ifdef __cplusplus
}
#endif


flags字段包含请求中设置的DNS标志;nquestions字段是请求中的问题数;questions是evdns_server_question结构体指针数组。每个evdns_server_question包含请求的资源类型(请看下面的EVDNS_*_TYPE宏列表)、请求类别(通常为EVDNS_CLASS_INET),以及请求的主机名。

这些结构体在1.3版本中引入,但是1.4版之前的名字是dns_question_class。名字中的“class”会让C++用户迷惑。仍然使用原来的“class”名字的C程序将不能在未来发布版本中正确工作。

int evdns_server_request_get_requesting_addr(struct evdns_server_request *req, 
                                             struct sockaddr *sa, int addr_len);
 

有时候需要知道某特定DNS请求来自何方,这时调用evdns_server_request_get_requesting_add()就可以了。应该传入有足够存储空间以容量地址的sockaddr:建议使用sockaddr_storage结构体。

这个函数在1.3版本中引入。

Response DNS request

DNS服务器收到每个请求后,会将请求传递给用户提供的回调函数,还带有用户数据指针。回调函数必须响应请求或者忽略请求,或者确保请求最终会被回答或者忽略。

回应请求前可以向回应中添加一个或者多个答案:

int evdns_server_request_add_a_reply(struct evdns_server_request *req, const char *name, 
                                        int n, const void *addrs, int ttl);

int evdns_server_request_add_aaaa_reply(struct evdns_server_request *req, const char *name, 
                                        int n, const void *addrs, int ttl);

int evdns_server_request_add_cname_reply(struct evdns_server_request *req, const char *name, 
                                        const char *cname, int ttl);

上述函数为请求req的DNS回应的结果节添加一个RR(类型分别为A、AAAA和CNAME)。各个函数中,name是要为之添加结果的主机名,ttl是以秒为单位的存活时间。对于A和AAAA记录,n是要添加的地址个数,addrs是到原始地址的指针:对于A记录,是以n4字节序列格式给出的IPv4地址;对于AAAA记录,是以n16字节序列格式给出的IPv6地址。

成功时函数返回0,失败时返回-1。

int evdns_server_request_add_ptr_reply(struct evdns_server_request *req, struct in_addr *in, 
                                       const char *inaddr_name, const char *hostname, int ttl);

这个函数为请求的结果节添加一个PTR记录。参数req和ttl跟上面的函数相同。必须提供in(一个IPv4地址)和inaddr_name(一个arpa域的地址)中的一个,而且只能提供一个,以指示为回应提供哪种地址。hostname是PTR查询的答案。

#define EVDNS_ANSWER_SECTION        0
#define EVDNS_AUTHORITY_SECTION     1
#define EVDNS_ADDITIONAL_SECTION    2

#define EVDNS_TYPE_A        1
#define EVDNS_TYPE_NS       2
#define EVDNS_TYPE_CNAME    5
#define EVDNS_TYPE_SOA      6
#define EVDNS_TYPE_PTR      12
#define EVDNS_TYPE_MX       15
#define EVDNS_TYPE_TXT      16
#define EVDNS_TYPE_AAAA     28

#define EVDNS_CLASS_INET4   1

int evdns_server_request_add_reply(struct evdns_server_request *req, int section, const char *name, int type, 
                                    int dns_class, int ttl, int datalen, int is_name, const char *data);

这个函数为请求req的DNS回应添加任意RR。section字段指示添加到哪一节,其值应该是某个EVDNS_SECTION。name参数是RR的名字字段。type参数是RR的类型字段,其值应该是某个EVDNS_TYPE。dns_class参数是RR的类别字段。RR的rdata和rdlength字段将从data处的datalen字节中产生。如果is_name为true,data将被编码成DNS名字(例如,使用DNS名字压缩)。否则,data将被直接包含到RR中。

int evdns_server_request_respond(struct evdns_server_request *req, int err);
int evdns_server_request_drop(struct evdns_server_request *req);

evdns_server_request_respond()函数为请求发送DNS回应,带有用户添加的所有RR,以及错误码err。如果不想回应某个请求,可以调用evdns_server_request_drop()来忽略请求,释放请求关联的内存和结构体。

#define EVDNS_FLAGS_AA 0X400
#define EVDNS_FLAGS_RD 0X080

void evdns_server_request_set_flags(struct evdns_server_request *req,int flags);

如果要为回应消息设置任何标志,可以在发送回应前的任何时候调用这个函数。

除了evdns_server_request_set_flags()首次在2.0.1-alpha版本中出现外,本节描述的所有函数都在1.3版本中引入。

DNS server example

Obosolote API

void evdns_base_search_ndot_set(struct evdns_base *base,const int ndots);
int  evdns_base_nameserver_add(struct evdns_base *base,unsigned long int address);
void evdns_set_random_bytes_fn(void*(*fn)(char*,size_t));

struct evdns_server_port *evdns_add_server_port(evutil_socket_t socket,int flags,
                                                evdns_request_callback_fn_type callback,void *user_data);

evdns_base_search_ndots_set()等价于使用evdns_base_set_option()设置ndots选项。

除了只能添加IPv4地址的名字服务器外,evdns_base_nameserver_add()函数的行为与evdns_base_nameserver_ip_add()相同。特别的是,evdns_base_nameserver_add()要求网络字节序的四字节地址。

2.0.1-alpha版本之前,不能为DNS服务端口指定event_base。通过evdns_add_server_port()添加的服务端口只能使用默认的event_base。

从版本2.0.1-alpha到2.0.3-alpha,可以使用evdns_set_random_bytes_fn(),而不是evdns_set_transsaction_id_fn(),来指定用于产生随机数的函数。这个函数现在没有效果了,因为libevent有自己的安全的随机数发生器了。

DNS_QUERY_NO_SEARCH标志曾经称作DNS_NO_SEARCH。

2.0.1-alpha版本之前,没有单独的evdns_base记号:evdns子系统中的所有信息都是全局存储的,操作这些信息的函数不需要evdns_base参数。这些函数现在都废弃了,但是还声明在event2/dns_compat.h中。它们通过一个单独的全局evdns_base实现,通过2.0.3-alpha版本引入的evdns_get_global_base()可以访问这个evdns_base。

在这里插入图片描述

EVDNS_CONFIG_WINDOWS_NAMESERVERS_IMPLEMENTED宏会被定义。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/885831.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

15年408计算机网络

第一题&#xff1a; 解析&#xff1a; 接收方使用POP3向邮件服务器读取邮件&#xff0c;使用的TCP连接&#xff0c;TCP向上层提供的是面向连接的&#xff0c;可靠的数据传输服务。 第二题&#xff1a; 解析&#xff1a;物理层-不归零编码和曼彻斯特编码 编码1&#xff1a;电平在…

CSS中字体图标的使用

引言&#xff1a; 在网页设计当中&#xff0c;会有很多很简洁的图标&#xff0c;比如箭头&#xff0c;照相机&#xff0c;放大镜等 这些大概率都是使用字体图标来完成的&#xff0c;因为字体图标比较简洁高效&#xff0c;不会像图片一样需要向浏览器请求数据。那么字体图标该…

网络协议详解--IPv6

IPv6产生背景 &#xff08;1&#xff09;地址空间的耗尽&#xff1a;因特网呈指数级发展&#xff0c;导致IPv4地址空间几乎耗尽。虽然采用了子网划分、CIDR和NAT地址转换技术&#xff0c;但这没有从根源解决地址耗尽的问题 &#xff08;2&#xff09;IP层安全需求的增长&#x…

Oracle exadata存储节点更换内存操作及报错处理

1.报错信息 在进行Oracle exadata巡检时&#xff0c;发现cell节点有一根内存报错&#xff0c;报错信息如下&#xff1a; 报错内存位置为&#xff1a;CPU1 P1/D2槽位 报错内存信息&#xff1a; 根据报错信息确认内存PN号、大小等息&#xff0c;并将信息反馈公司&#xff0c;及…

Hadoop框架及应用场景说明

Hadoop是一个开源的分布式系统基础架构。由多个组件组成&#xff0c;组件之间协同工作&#xff0c;进行大规模数据集的存储和处理。 本文将探讨Hadoop的架构以及应用场景。 一Hadoop框架 Hadoop的核心组件包含&#xff1a; 1. Hadoop分布式文件系统&#xff08;HDFS&#xff…

Qt --- 常用控件的介绍---Widget属性介绍

一、控件概述 编程&#xff0c;讲究的是站在巨人的肩膀上&#xff0c;而不是从头发明轮子。一个图形化界面上的内容&#xff0c;不需要咱们全都从零区实现&#xff0c;Qt中已经提供了很多内置的控件了&#xff08;按钮&#xff0c;文本框&#xff0c;单选按钮&#xff0c;复选…

【Java SE 题库】移除元素(暴力解法)--力扣

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 1. 题目 2. 解法(快慢“指针”) 3. 源码 4. 小结 1. 题目 给你一个数组 nums 和一个值 val&#xff0c;你需要原地移除所有数值等于 val 的元素。元素的顺…

C0007.Clion中添加ui文件及运行的完整步骤

1.创建ui文件 选择Ui文件目录&#xff0c;右击&#xff0c;打开Qt Designer&#xff1b; 创建完成后&#xff0c;保存ui界面&#xff0c;并且命名为test.ui&#xff1b; 2.新建头文件test.h 在include目录中&#xff0c;新建头文件&#xff0c;文件名为test.h 3.新建test.…

神点SAAS云财务系统/多账套/前后端全开源

>>>系统简述&#xff1a; 神点SAAS云财务软件开源版&#xff0c;包含账套、凭证字、科目、期初、币别、账簿、报表、凭证、结账等功能。 神点云财务系统&#xff0c;餐饮行业财务软件、微服务架构财务软件、开源云财务软件、Java全开源财务软件优选&#xff01; >…

「轻盈」之旅:OOM故障重现与解决

前期准备 本项目均采用 VisualVM 2.1.10 进行dump文件的分析。JDK1.8及之前所在目录的bin目录下有自带的VisualVM&#xff0c;JDK1.8以后需要自行手动安装下载。 下载地址&#xff1a;https://visualvm.github.io/download.html IDEA插件配置&#xff1a;在Plugins里搜索visual…

CSS文本格式化

通过 CSS 中的文本属性您可以像操作 Word 文档那样定义网页中文本的字符间距、对齐方式、缩进等等&#xff0c;CSS 中常用的文本属性如下所示&#xff1a; text-align&#xff1a;设置文本的水平对齐方式&#xff1b;text-decoration&#xff1a;设置文本的装饰&#xff1b;te…

Vue项目开发注意事项

事项一&#xff1a;项目代码放在本地怎么运行起来 1、首先确定项目对应的node和npm版本 node下载地址 Index of /dist/https://nodejs.org/dist/ node 与 npm版本对应关系 Node.js — Node.js Releases 2、node卸载的时候&#xff0c;会自动把对应的npm卸载掉 情况1&…

光控资本:股票后边带个u是啥意思,常见股票后缀字母还有哪些?

股票后面带有字母U标明该股票发行人到目前为止还没有盈利&#xff0c;这是根据上交所发布的《关于科创板股票及存托凭证生意相关事项的奉告》中的规则&#xff0c;在上市后实现初度盈利&#xff0c;这个标识就会消除掉。一般是在科创板上市的股票会有U的标明&#xff0c;且一般…

河南做网站与SEO:如何提升搜索引擎排名

河南做网站与SEO&#xff1a;如何提升搜索引擎排名 在当今数字化时代&#xff0c;越来越多的企业意识到互联网的重要性&#xff0c;特别是在河南这样一个快速发展的地区&#xff0c;建立一个优秀的网站已经成为企业发展的必要条件。而在建立网站的同时&#xff0c;SEO&#xff…

【算法】链表:206.反转链表(easy)

系列专栏 《分治》 《模拟》 《Linux》 目录 1、题目链接 2、题目介绍 3、解法&#xff08;快慢指针&#xff09; 解题步骤&#xff1a; 关键点&#xff1a; 复杂度分析&#xff1a; 4、代码 1、题目链接 206. 反转链表 - 力扣&#xff08;LeetCode&#xff09; …

Flutter中使用FFI的方式链接C/C++的so库(harmonyos)

Flutter中使用FFI的方式链接C/C库&#xff08;harmonyos&#xff09; FFI plugin创建和so的配置FFI插件对so库的使用 FFI plugin创建和so的配置 首先我们可以根据下面的链接生成FFI plugin插件&#xff1a;开发FFI plugin插件 然后在主项目中pubspec.yaml 添加插件的依赖路径&…

滑动窗口->dd爱框框

1.题目&#xff1a; 2.题解&#xff1a; 2.1为什么用滑动窗口优化&#xff1a; 因为元素都是大于0的 所以&#xff1a;当找到大于等于x的值时&#xff0c;right可以不用返回 两个指针都往后走&#xff1b;因此可以使用滑动窗口优化暴力解法 2.2&#xff1a;滑动窗口具体使用步…

在掌控板中加载人教版信息科技教学指南中的educore库

掌控板中加载educore库 人教信息科技数字资源平台&#xff08;https://ebook.mypep.cn/free&#xff09;中的《信息科技教学指南硬件编程代码说明》文件中提到“本程序说明主要供教学参考。需要可编程主控板须支持运行MicroPython 脚本程序。希望有更多的主控板在固件中支持ed…

【PyTorch】图像分割

图像分割是什么 Image Segmentation 将图像每一个像素分类 图像分割分类 超像素分割&#xff1a;少量超像素代替大量像素&#xff0c;常用于图像预处理语义分割&#xff1a;逐像素分类&#xff0c;无法区分个体实例分割&#xff1a;对个体目标进行分割全景分割&#xff1a;…

2.点位管理|前后端如何交互——帝可得后台管理系统

目录 前言点位管理菜单模块1.需求说明2.库表设计3.生成基础代码0 .使用若依代码生成器最终目标1.创建点位管理2.添加数据字典3.配置代码生成信息4.下载代码并导入项目 4.优化菜单——点位管理1.优化区域管理2.增加点位数3. 合作商4.区域管理中添加查看详情功能5.合作商添加点位…