向DNS服务器发送请求的程序?不是gethostbyname!
很多人在阅读《网络是怎样连接的》第一章时,会混淆"DNS客户端"和"DNS解析器函数"这两个概念。这篇文章帮你彻底搞清楚。
问题背景
在《网络是怎样连接的》第一章最后,有这样一个问题:
"向DNS服务器发送请求消息的程序叫什么?"
很多读者在看到这个问题时,第一反应是:gethostbyname
这个答案是错的。
正确答案是什么?
正确答案是:DNS客户端(DNS Client)或 DNS解析器(DNS Resolver)
为什么gethostbyname不是正确答案?
1. 概念层级不同
┌─────────────────────────────────────────┐
│ 应用程序(浏览器、curl、ping等) │
├─────────────────────────────────────────┤
│ DNS客户端(DNS Client) │ ← 向DNS服务器发送请求的程序
├─────────────────────────────────────────┤
│ gethostbyname() / getaddrinfo() │ ← 函数接口
├─────────────────────────────────────────┤
│ 操作系统网络库 │
├─────────────────────────────────────────┤
│ DNS服务器 │
└─────────────────────────────────────────┘
2. 性质不同
| 对比项 | DNS客户端 | gethostbyname |
|---|---|---|
| 是什么 | 一个程序或组件 | 一个函数 |
| 独立性 | 可以独立运行 | 必须被程序调用 |
| 职责 | 负责与DNS服务器通信 | 提供查询接口给应用 |
| 例子 | nslookup、dig、系统解析器 | C库函数、编程接口 |
3. 关键区别
DNS客户端:
- 是一个程序或组件
- 负责实际构建DNS查询报文
- 通过网络发送给DNS服务器
- 接收并处理DNS响应
- 可以独立运行(如
nslookup)
gethostbyname:
- 是一个函数(API)
- 被应用程序调用
- 内部会调用DNS客户端来完成查询
- 只是接口,不是实际发送请求的程序
举个例子
场景1:使用nslookup
$ nslookup www.example.com
这里,nslookup 就是DNS客户端程序,它直接向DNS服务器发送请求。
场景2:浏览器访问网站
// 浏览器内部代码(伪代码)
struct hostent *he = gethostbyname("www.example.com");
这里,浏览器是应用程序,它调用 gethostbyname() 函数,而 gethostbyname() 内部会调用操作系统的DNS客户端,DNS客户端才真正向DNS服务器发送请求。
为什么会混淆?
原因1:教材表述模糊
《网络是怎样连接的》中提到:
"应用程序使用DNS解析器(resolver)来查询DNS服务器"
这里的"DNS解析器"既可以指DNS客户端程序,也可以指解析器函数,容易造成混淆。
原因2:对"程序"理解不深
很多人认为"程序"就是"代码",所以函数也是程序的一部分。但实际上:
- 程序(Program):可以独立运行的完整软件
- 函数(Function):程序内部的代码单元
原因3:API与实现边界模糊
在现代编程中,API调用和实际通信的边界越来越模糊,开发者很少直接与DNS客户端打交道,而是通过库函数间接使用,导致对底层概念不清晰。
从技术层面深入理解
DNS客户端的工作流程
1. 接收域名查询请求
↓
2. 检查本地缓存(/etc/hosts, DNS cache)
↓
3. 构造DNS查询报文(UDP/TCP)
↓
4. 发送到DNS服务器(端口53)
↓
5. 等待并接收响应
↓
6. 解析响应,返回给调用者
gethostbyname的工作流程
1. 被应用程序调用,传入域名
↓
2. 检查/etc/hosts文件
↓
3. 调用系统DNS客户端
↓
4. DNS客户端执行上述流程
↓
5. 将结果封装成struct hostent返回
类比理解
可以把DNS查询比作点外卖:
-
DNS客户端 = 外卖平台的小程序
- 直接处理订单
- 与餐厅(DNS服务器)通信
-
gethostbyname = 点餐按钮
- 只是用户(应用程序)操作的接口
- 点击后由小程序(DNS客户端)真正处理订单
现代发展:gethostbyname已过时
gethostbyname() 是一个古老的API(IPv4时代),现在已经被 getaddrinfo() 取代:
// 推荐使用
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
getaddrinfo() 的优势:
- 支持IPv6和IPv4
- 更安全的内存管理
- 更好的错误处理
但无论用哪个函数,它们都只是接口,真正向DNS服务器发送请求的始终是DNS客户端。
总结
| 问题 | 答案 |
|---|---|
| 向DNS服务器发送请求的程序叫什么? | DNS客户端(DNS Client) |
| gethostbyname是什么? | DNS解析器函数(API) |
| 两者关系 | gethostbyname内部调用DNS客户端 |
记忆技巧
记住这个简单的事实:
函数不会自己发网络包,程序才会。
gethostbyname 是函数,它不会自己发网络请求;
DNS客户端是程序(或组件),它才真正向DNS服务器发送请求。
最后,再强调一遍:
向DNS服务器发送请求消息的程序叫 DNS客户端,不是 gethostbyname!
gethostbyname 只是被应用程序调用的函数接口,它内部会使用DNS客户端来完成真正的DNS查询。
如果这篇文章对你有帮助,欢迎点赞收藏~后续我会持续更新这本书的读书笔记 🚀