#include "lib_acl.h" #include "sys_patch.h" #ifdef ACL_BCB_COMPILER #pragma hdrstop #endif #include "dns.h" #define MSG_SEND acl_msgio_send /* DNS查询线程向主线程发送IO消息,因为多个线程都要用一个消息句柄发送消息,所以需要加锁 */ static void reply_lookup_msg(DNS_CTX *dns_ctx, DNS_SERVER *dns) { const char *myname = "reply_lookup_msg"; /* 加锁 */ acl_pthread_mutex_lock(&dns->lock); /* 向主线程发送DNS查询结果消息 */ if (MSG_SEND(dns_ctx->mio, DNS_MSG_LOOKUP_RESULT, dns_ctx, sizeof(DNS_CTX)) < 0) { acl_msg_error("%s: send msg error, domain(%s)", myname, dns_ctx->domain_key); } /* 解锁 */ acl_pthread_mutex_unlock(&dns->lock); /* 释放由 msg_lookup 函数分配的内存 */ acl_myfree(dns_ctx); } /* DNS查询线程,查到DNS结果会通过IO消息将数据传递给非阻塞式主线程的消息队列 */ static void lookup_thread(void *arg) { const char *myname = "lookup_thread"; #ifdef USE_THREAD_POOL DNS_CTX *dns_ctx = (DNS_CTX *) arg; DNS_SERVER *dns = dns_ctx->dns; #else DNS_CTX *dns_ctx = (DNS_CTX *) arg; DNS_SERVER *dns = dns_ctx->dns; #endif int error = 0, n, i; dns_ctx->dns_db = acl_gethostbyname(dns_ctx->domain_key, &error); if (dns_ctx->dns_db == NULL) { acl_msg_error("%s: gethostbyname error(%s), domain(%s)", myname, acl_netdb_strerror(error), dns_ctx->domain_key); dns_ctx->ip_cnt = 0; reply_lookup_msg(dns_ctx, dns); return; } n = acl_netdb_size(dns_ctx->dns_db); dns_ctx->ip_cnt = n > DNS_IP_MAX ? DNS_IP_MAX : n; for (i = 0; i < dns_ctx->ip_cnt; i++) { snprintf(dns_ctx->ip[i], sizeof(dns_ctx->ip[i]), "%s", acl_netdb_index_ip(dns_ctx->dns_db, i)); dns_ctx->port[i] = 0; /* 对于DNS查询到的结果,采用默认的端口号 */ } reply_lookup_msg(dns_ctx, dns); } /* 主线程消息处理函数: 主线程通过此函数创建DNS线程进行DNS查询过程 */ static int msg_lookup(int msg_type acl_unused, ACL_MSGIO *mio, const ACL_MSGIO_INFO *info, void *arg) { DNS_CTX *dns_ctx; dns_ctx = (DNS_CTX *) acl_mycalloc(1, sizeof(DNS_CTX)); memcpy(dns_ctx, acl_vstring_str(info->body.buf), ACL_VSTRING_LEN(info->body.buf)); /* 设置DNS句柄 */ dns_ctx->dns = (DNS_SERVER *) arg; /* 设置消息句柄,线程池中的查询线程通过向此句柄发送消息以通知 * 主线程有关DNS的查询结果 */ dns_ctx->mio = mio; /* 创建单独的线程进行阻塞式DNS查询过程 */ #ifdef USE_THREAD_POOL acl_pthread_pool_add(dns_ctx->dns->wq, lookup_thread, dns_ctx); #else lookup_thread(dns_ctx); #endif return (1); } /* 主线程消息处理函数: 主线程通过此函数接收DNS查询线程发送的DNS查询结果消息 */ static int msg_lookup_result(int msg_type acl_unused, ACL_MSGIO *mio acl_unused, const ACL_MSGIO_INFO *info, void *arg) { DNS_CTX *dns_ctx; DNS_SERVER *dns = (DNS_SERVER*) arg; /* 获得DNS查询线程的结果消息 */ dns_ctx = (DNS_CTX *) acl_vstring_str(info->body.buf); /* 将查询结果放入DNS缓存中 */ if (dns_ctx->ip_cnt > 0) dns_cache_push(dns->dns_cache, dns_ctx->dns_db); /* 释放由 lookup_thread 分配的 dns_db 对象 */ if (dns_ctx->dns_db) acl_netdb_free(dns_ctx->dns_db); dns_ctx->dns_db = NULL; /* 回调请求任务函数 */ dns_ctx->callback(dns_ctx); return (1); } /* 创建DNS查询句柄 */ DNS_SERVER *dns_server_create(ACL_AIO *aio, int timeout) { const char *myname = "dns_server_create"; DNS_SERVER *dns; int max_threads = 200; int idle_timeout = 60 /* 此值目前不起作用,需要修改一下ACL库才可 */; if (aio == NULL) acl_msg_fatal("%s(%d): aio null", myname, __LINE__); /* acl 库的DNS缓存模块需要加锁,因为查询线程池都要访问该同一资源 * 仅让 acl 的DNS缓存模块的缓存时间为60秒,因为本程序模块也有缓存控制 * 因为本代码自己已经实现了DNS缓存,所以不需要ACL库的DNS缓存处理 * acl_netdb_cache_init(60 , 1); */ dns = (DNS_SERVER *) acl_mycalloc(1, sizeof(DNS_SERVER)); dns->aio = aio; /* 本地的DNS缓存模块需要加锁,因为只有一个线程访问其资源 */ dns->dns_cache = dns_cache_create(timeout, 0); /* 监听 IO 事件消息 */ dns->listener = acl_msgio_listen(aio, NULL); if (dns->listener == NULL) acl_msg_fatal("%s: listen error", myname); /* 注册 IO 事件消息及处理函数:用于监听新的查询请求 */ acl_msgio_listen_reg(dns->listener, DNS_MSG_LOOKUP, msg_lookup, dns, 1); acl_msgio_addr(dns->listener, dns->addr, sizeof(dns->addr)); dns->mio = acl_msgio_connect(aio, dns->addr, 0); if (dns->mio == NULL) acl_msg_fatal("%s: connect server(%s) error", myname, dns->addr); /* 注册 IO 事件消息及处理函数:用于处理查询线程返回查询结果 */ acl_msgio_reg(dns->mio, DNS_MSG_LOOKUP_RESULT, msg_lookup_result, dns); /* 初始化DNS共享线程锁 */ acl_pthread_mutex_init(&dns->lock, NULL); /* 需要创建独立的线程程查询DNS(因为查询DNS是阻塞式查询) */ #ifdef USE_THREAD_POOL dns->wq = acl_thread_pool_create(max_threads, idle_timeout); #endif return (dns); } /* 关闭DNS查询句柄 */ void dns_server_close(DNS_SERVER *dns) { acl_msgio_close(dns->listener); acl_pthread_pool_destroy(dns->wq); acl_myfree(dns); } void dns_server_static_add(DNS_SERVER *dns, const char *map, const char *delim, int def_port) { ACL_DNS_DB *dns_db = NULL; ACL_ARGV *argv = NULL; char *ptr; int i, port; #undef RETURN #define RETURN do { \ if (argv != NULL) \ acl_argv_free(argv); \ if (dns_db != NULL) \ acl_netdb_free(dns_db); \ return; \ } while (0) argv = acl_argv_split(map, delim); if (argv == NULL) RETURN; if (argv->argc < 2) RETURN; dns_db = acl_netdb_new(argv->argv[0]); for (i = 1; i < argv->argc; i++) { ptr = strchr(argv->argv[i], ':'); if (ptr != NULL) { *ptr++ = 0; port = atoi(ptr); if (port <= 0) port = def_port; } else { port = def_port; } acl_netdb_add_addr(dns_db, argv->argv[i], port); } /* 设置超时时间为0从而使其永不超时 */ dns_cache_push_one(dns->dns_cache, dns_db, 0); RETURN; } /* 开始查询某个域名 */ int dns_server_lookup(DNS_SERVER *dns, const DNS_CTX *ctx) { const char *myname = "dns_server_lookup"; ACL_MSGIO *mio = dns->mio; DNS_CTX dns_ctx; ACL_DNS_DB *dns_db; /* 只所以采用此方式,是为了保证 dns_server_lookup 参数 ctx为 const 类型 */ memcpy(&dns_ctx, ctx, sizeof(dns_ctx)); /* 先查询DNS缓存表中是否存在本次所查域名 */ dns_db = dns_cache_lookup(dns->dns_cache, ctx->domain_key); if (dns_db) { ACL_ITER iter; int i = 0; acl_foreach(iter, dns_db) { const ACL_HOST_INFO *info; info = (const ACL_HOST_INFO*) iter.data; ACL_SAFE_STRNCPY(dns_ctx.ip[i], info->ip, sizeof(dns_ctx.ip[i])); dns_ctx.port[i++] = info->hport; dns_ctx.ip_cnt++; if (dns_ctx.ip_cnt >= DNS_IP_MAX) break; } /* 对已经DNS缓存表中已经存在的域名直接触发任务回调过程 */ dns_ctx.callback(&dns_ctx); /* 此处之所以需要释放该对象,是因为 dns_cache_lookup 返回的对象为动态 * 分配的(acl_netdb_clone) */ acl_netdb_free(dns_db); return (0); } /* 向查询线程池发送查询指令 */ if (MSG_SEND(mio, DNS_MSG_LOOKUP, &dns_ctx, sizeof(DNS_CTX)) < 0) { acl_msg_error("%s: send msg error, domain(%s)", myname, dns_ctx.domain_key); return (-1); } return (0); }