gethostbyname失败一次之后便总是失败

在进行网络编程时遇到gethostbyname函数多次失败的情况,原因是库函数仅读取一次DNS存储文件并将其缓存。当DNS文件更新后,函数开始返回错误。为了解决这一问题,可以通过调用res_init()强制更新DNS缓存,尽管这会降低程序效率。
网络编程时,gethostbyname失败一次之后便总是失败,查找了原因,发现是实现的库函数只读一次存储dns的文件,放到缓存里。因此当dns存储文件更新之后,gethostbyname就会失败。解决方式是通过调用res_init()强制更新一下,但是效率肯定受影响了。
<think>根据日志分析,两次INFO之间耗时30秒,但代码片段中没有显示明显的外部循环。不过日志中显示多次出现`preview_simple_add_batch_auth: Unauth Channel`的错误,时间间隔大约5秒(从14:46:25到14:46:55共6次,间隔5秒)。同时,在出现DNS错误后进入了重连保护模式。 当前代码片段中,阻塞点在于`gethostbyname`函数,该函数在DNS解析时是同步阻塞的。默认情况下,如果DNS服务器没有响应,这个函数可能会阻塞较长时间(通常为30秒左右,取决于系统配置和DNS超时设置)。 在代码中,当`inet_addr`转换失败(返回INADDR_NONE)时,会调用`gethostbyname`进行域名解析。如果这个解析耗时很长,就会导致整个线程阻塞在这里。 因此,尽管代码中没有循环,但一次`gethostbyname`调用就可能阻塞30秒(如果系统DNS超时设置为30秒)。从日志中可以看出,30秒的间隔恰好发生了一次长时间的阻塞。 具体流程: 1. 第一次连接尝试(可能快速失败,例如DNS解析失败) 2. 然后进入重试(connecttimes:4),但日志显示尝试了多次连接(每次间隔5秒),直到30秒后再次出现`RTMP chn [0] CONNECT...........`。 然而,关键点在于:在连接过程中,当调用`gethostbyname`时,如果DNS服务器无响应,该函数会阻塞直到超时(通常为5秒*重试次数,但具体取决于系统配置)。在Linux系统中,默认超时行为如下: - 超时时间由`/etc/resolv.conf`中的`options timeout`设置(默认通常为5秒) - 重试次数由`options attempts`设置(默认通常为2次) 因此,总超时时间为timeout * attempts = 5*2=10秒。 但日志显示阻塞了大约30秒,这可能是由于: a) 系统配置的DNS超时较长(例如timeout=15, attempts=2,则30秒) b) 代码中可能不止一次调用`gethostbyname`(例如在重试循环中多次调用,但代码片段中只显示一次) c) 或者,在调用`gethostbyname`之前,系统可能已经尝试了多次(比如尝试了多个DNS服务器,每个服务器都超时) 然而,代码片段中只有一处调用`gethostbyname`(在if条件内)。但根据日志,在30秒内出现了多次相同的错误,说明这个代码块可能被多次执行(例如在一个重试循环中)。因此,需要检查调用这段代码的上下文是否有循环。 如果调用这段代码的函数被循环调用,且每次调用都试图解析同一个主机名,那么每次解析都会阻塞10-30秒,从而累计阻塞时间。 如何确认? - 查看日志中`Problem accessing the DNS`出现的次数。如果多次出现,说明多次调用该代码块。 - 日志中第一次出现DNS错误是`ERROR: Problem accessing the DNS. (addr: ns8.indexforce.com)`,然后进入了重连保护模式,之后又出现了一次`INFO: ....RTMP chn [0] CONNECT...........`(间隔30秒)。 因此,可能的情况是: 1. 第一次连接尝试:进行DNS解析,阻塞30秒(因为DNS解析超时),然后失败。 2. 之后,程序进入重试逻辑(可能是循环重试),但重试间隔设置为5秒。在30秒内,重试了多次(从日志看大约6次),每次重试都尝试连接,但连接前可能又执行了DNS解析?这取决于代码结构。 但是,在提供的代码片段中,DNS解析只发生在设置`service->sin_addr`时。如果连接失败后,重新连接时再次调用这段代码,那么每次连接尝试都会重新解析DNS,如果每次都阻塞30秒,那么总时间会很长。 然而,日志显示每5秒出现一次错误,说明每次重试的间隔是5秒。所以,阻塞30秒是在一次连接尝试中发生的(即一次DNS解析耗时30秒),然后后续重试间隔5秒,但后续的重试可能没有再进行长时间的DNS解析(因为可能使用了之前解析失败的缓存?或者没有再次解析?)。 如何解决? - 避免在每次重试时都进行DNS解析。可以缓存解析结果,如果解析失败,则在短时间内不再尝试解析。 - 使用异步解析或设置解析超时。 具体修改建议: 1. 设置全局DNS缓存,避免重复解析。 2. 使用非阻塞的DNS解析(如之前提到的异步方法)。 3. 如果使用`gethostbyname`,考虑设置系统级的DNS超时(但这不是可移植的方法)。 另一种思路:检查代码中是否有循环,导致该段代码被多次执行。例如,在连接失败后,程序可能进入循环重试,每次重试都重新解析DNS。如果是这样,可以将DNS解析移出循环,只解析一次。 结论:当前代码片段中的`gethostbyname`在DNS服务器无响应时可能阻塞长达30秒,这是导致30秒延迟的直接原因。此外,如果调用这段代码的函数被循环调用,且每次循环都执行DNS解析,则总时间会成倍增加。 修改核心:减少DNS解析的阻塞时间或避免重复解析。 解决方案示例(伪代码): ```c static struct in_addr cached_addr = {0}; static time_t last_resolve_time = 0; static char last_hostname[256] = ""; if (strcmp(last_hostname, hostname) == 0 && (time(NULL) - last_resolve_time) < 300) { // 使用缓存 service->sin_addr = cached_addr; } else { // 进行解析 if (inet_addr(hostname) == INADDR_NONE) { struct hostent *host = gethostbyname(hostname); if (host) { cached_addr = *(struct in_addr *)host->h_addr; strncpy(last_hostname, hostname, sizeof(last_hostname)-1); last_resolve_time = time(NULL); service->sin_addr = cached_addr; } else { // 解析失败 } } } ``` 这样,在5分钟内对同一个主机名只解析一次。 另外,如果无法修改全局状态,可以将缓存放在调用该函数的上一层。 注意:多线程环境下缓存需要加锁。 §§相关问题§§ 1. 如何在C语言中安全地实现一个线程安全的DNS缓存? 2. 除了gethostbyname,还有哪些替代函数可以进行DNS解析并允许设置超时? 3. 在重试网络连接时,如何避免重复进行DNS解析? 4. Linux系统下如何配置DNS解析超时时间和重试次数? 5. 在嵌入式系统中,如何优化DNS解析以减少网络连接延时? </think>根据日志和代码分析,**30秒阻塞的直接原因是`gethostbyname`函数的同步阻塞行为**。即使没有外层循环,该函数在DNS解析失败时仍会持续等待直到系统级超时(通常5-30秒)。以下是关键验证和解决方案: ### 阻塞机制验证 1. **`gethostbyname`的阻塞本质**: ```c struct hostent *host = gethostbyname(hostname); // 同步调用 ``` - 该函数是**完全同步的**:调用线程会在此处冻结,直到DNS服务器响应或超时 - Linux默认超时行为: - 首次查询超时:5秒 - 重试间隔:2秒 - 最大重试次数:2次 - **总超时 = 5 + 2×2 = 9秒**(但实际可能因网络波动达30秒) 2. **匹配日志的证据**: ```log [14:46:25-567] ERROR: Problem accessing the DNS. (addr: ns8.indexforce.com) [14:46:55-578] INFO: ....RTMP chn [0] CONNECT........... ``` - 间隔 ≈ **30秒** → 符合多次DNS重试的累积时间 - 错误`Name or service not known`表明DNS解析彻底失败 --- ### 阻塞原理图解 ```mermaid sequenceDiagram participant App as 应用程序 participant glibc as glibc库 participant DNS as DNS服务器 App->>glibc: gethostbyname("ns8.indexforce.com") Note right of glibc: 线程冻结在此处 glibc->>DNS: 查询请求 (UDP 53端口) DNS-->>glibc: 无响应(第1次) Note over glibc,DNS: 等待5秒超时 glibc->>DNS: 重试请求(第2次) DNS-->>glibc: 无响应(第2次) Note over glibc,DNS: 等待5秒超时 glibc->>DNS: 重试请求(第3次) DNS-->>glibc: NXDOMAIN错误(域名不存在) glibc-->>App: 返回NULL(耗时≈30秒) ``` --- ### 解决方案(直接修改代码) #### 方法1:强制设置DNS超时(Linux特有) ```c #include <sys/time.h> #include <resolv.h> // 在gethostbyname前插入 res_init(); // 重置解析器状态 _res.retrans = 1; // 查询超时(秒) _res.retry = 1; // 最大重试次数 → 总超时=1+1=2秒 struct hostent *host = gethostbyname(hostname); ``` #### 方法2:替换为异步DNS解析 ```c #include <sys/select.h> #include <arpa/nameser.h> int dns_timeout = 2; // 自定义超时(秒) int fd = res_ninit(&_res); // 非标准但广泛支持 _res.retrans = dns_timeout; _res.retry = 1; // 执行异步查询 if (res_nquery(&_res, hostname, C_IN, T_A, NULL, 0) < 0) { RTMP_Log(RTMP_LOGERROR, "DNS query failed"); } ``` #### 方法3:线程级超时控制 ```c #include <pthread.h> #include <signal.h> void* dns_thread(void* arg) { char* hostname = (char*)arg; return gethostbyname(hostname); } struct hostent* safe_gethostbyname(const char* hostname, int timeout_sec) { pthread_t tid; struct hostent* result = NULL; pthread_create(&tid, NULL, dns_thread, (void*)hostname); // 设置超时等待 struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += timeout_sec; if (pthread_timedjoin_np(tid, (void**)&result, &ts) != 0) { pthread_cancel(tid); // 超时强制终止 return NULL; } return result; } // 调用方式 struct hostent *host = safe_gethostbyname(hostname, 2); // 2秒超时 ``` --- ### 关键修改建议 1. **立即生效的补丁**: ```c // 在gethostbyname前添加超时设置 res_init(); _res.options |= RES_DEBUG; // 可选:开启调试日志 _res.retrans = 1; _res.retry = 1; ``` 2. **长期优化方案**: - 用`getaddrinfo`替代`gethostbyname`(支持超时控制) - 增加DNS缓存层,避免重复查询 - 对关键主机名预解析(程序启动时完成DNS查询) 3. **验证手段**: ```bash # 测试域名解析时间 time host ns8.indexforce.com # 查看系统DNS配置 cat /etc/resolv.conf ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值