DNS查询lookupdns程序分析-上—StateThreads示例程序介绍

作者:罗上文,微信:Loken1,公众号:FFmpeg弦外之音

lookupdns 是 StateThreads 官网提供的一个示例程序,用协程来并发查询 多个域名 IP 信息,用法如下:

lookupdns srs.xianwaizhiyin.net ffmpeg.xianwaizhiyin.net

1-1

lookupdns 的调试环境搭建请阅读《StateThreads调试环境搭建

lookupdns 程序主要由两个文件组成 lookupdns .cres.c,整体的函数流程如下:

1-2

上图查询了两个域名的 dns 信息,由于使用了协程,所以两个 dns 请求几乎是同一时间发出去的,我们可以用 tcpdump + wireshark 抓包看看,命令如下:

sudo tcpdump port 53 -w dns_st.pcap

提示:dns 协议用的默认端口是 53,dns.pcap 可在 GitHub 上进行下载。

1-2-2

从上图可以看出,不是先查询完 srs.xianwaizhiyin.net 再查 ffmpeg.xianwaizhiyin.net 的,而是一起发出查询。从 9.35 ~ 9.40,查询两次 dns 一共花了 0.05 秒。

如果我们修改一下代码,不使用协程,看看整个通信过程是怎样的,代码如下:

1-3

tcpdump 抓包如下:

sudo tcpdump port 53 -w dns_notst.pcap

dns.pcap 可在 GitHub 上进行下载。

1-4

可以看到,如果不使用协程,需要等到 srs.xianwaizhiyin.net 的查询结果返回,才能再发出第二个请求。从0 ~ 0.07,查询两次 dns 一共花了 0.07 秒,比使用协程慢了一点。

这就是协程的魅力。


虽然 lookupdns 程序开了 2 个协程,但是他整个进程还是只有 1 个线程的。多个协程是占用一个线程里面的时间片的。单个协程占用的内存是低于单个线程的,而且协程切换的上下文消耗会少一些。

上下文切换其实也是执行的一段汇编指令/机器码,假设 从协程A 切换到 协程B 需要执行 10 条汇编指令,而 从 线程A 切换到 线程B 需要执行 20 条汇编指令,那就可以说,线程切换的消耗比协程切换高一倍。


那是不是只有 StateThreads 协程这一种方法,才能实现上面这种单线程并发查询 dns 的效果呢?

其实不是,还有另一种方法,就是自己调用 epoll 来实现这个功能,伪代码如下;

int socket_fd1 = socket(PF_INET, SOCK_DGRAM, 0)
int socket_fd2 = socket(PF_INET, SOCK_DGRAM, 0)

sendto(socket_fd2,www.baidu.net)
sendto(socket_fd1,www.xianwaizhiyin.net)

//监听两个 fd 的状态
epoll(socket_fd1,socket_fd2)

//DNS服务器返回数据了,fd 的状态会变化,然后调 recvform() 读取数据
recvform(socket_fdx)

上面这样,我们自己调 epoll 也是可以实现单线程并发查询 dns 的,而且上面的两次 sendto 中间是没有任何东西的。是连续调用的。不需要切换协程来执行第二次 sendto,所以他完成 一次DNS查询 所执行的指令数量 应该是比 StateThreads 协程实现的 DNS 查询更少的,有兴趣的读者可以自己做实验验证一下。


但是上面这种 epoll 的用法也有他的局限,在解析复杂协议的时候会让代码的可读性降低(回调地狱),因为他把 request (请求) 跟 response (回调) 分离了。

DNS 域名查询他的协议是很简单的,数据很少,一个 UDP 包就能包含所有的域名信息,他不需要从 UDP 通道多次读数据,他不像 RTMP 那么复杂。

当需要解析复杂协议的时候,StateThreads 协程的优势就会体现出来,具体后面可以看《SRS是怎么解析connect包的》,他只需要一次 expect_message() 调用就完成了 多个 chunk 的合并 跟 connect 请求的解析。

1-3


这个优势简单来说是这样的,比如一些复杂协议,头部是 2 个字节,我们需要读够 2 个字节才能解析出来头部,然后再去读 body 的内容,body 大部分时候也要读多次。

假如是 纯 epoll 的写法,假如 UDP 通道里面只有 1 个字节到达,那就会激活 epoll ,然后执行回调函数,然后回调函数判断不够 2 字节,又会重新让 epoll 监听阻塞等待数据。

一些基于 epoll 封装的框架 libevent 会对这个流程进行简化,但还是要靠回调函数去执行任务,只要有回调,这个逻辑就分离开了,无法清晰看到它的全部的逻辑。

推荐读者阅读一下 ZLMediaKit 或者其他项目对 RTMP 协议的解析代码,然后对比 SRS 的 expect_message() ,看看代码可读性是否有提升。


推荐阅读肖志宏分享的《RTC级联和QUIC协议》视频,在第23分钟有对协程的优势进行讲解。肖志宏在视频里面讲了一个 Traditional EDSM 的问题,需要进行专门的状态机管理,但是协程不需要进行 专门的状态机管理。

思考:EDSM 专门的状态机管理 会 抵消 协程切换的上下文开销 吗?这是两种方式,A 还是 B 好?


StateThreads 协程的底层也用了 epoll ,这个我们后面会讲到。


现在我们已经基本了解了 协程的好处,下面让我们继续学习 lookupdns 程序的并发原理,请看《lookupdns程序分析-下


版权所属 xianwanzhiyin.net 罗上文 2023 all right reserved,powered by Gitbook该文件修订时间: 2024-01-09 22:01:16

results matching ""

    No results matching ""