一次 Web 请求背后的网络协议全过程
「从输入 URL 到页面显示,中间发生了什么?」 这是互联网领域的一道经典面试题,考察候选人对计算机网络的整体认知深度。本文以一次完整的 Web 页面请求为例,将 DHCP、ARP、DNS、TCP、HTTP 等核心协议串联起来,自底向上逐步拆解数据在网络中传输的完整过程,帮助读者建立系统化的网络知识体系。
「从输入 URL 到页面显示,中间发生了什么?」 这是互联网领域的一道经典面试题,考察候选人对计算机网络的整体认知深度。本文以一次完整的 Web 页面请求为例,将 DHCP、ARP、DNS、TCP、HTTP 等核心协议串联起来,自底向上逐步拆解数据在网络中传输的完整过程,帮助读者建立系统化的网络知识体系。
UDP为无连接不可靠的数据报协议,不同于TCP提供的面向连接的可靠字节流。
客户端不需要与服务器事先建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定服务器的地址作为参数。
服务器不接受来自客户的连接,而是只管调用recvfrom函数,等待来自某个客户的数据到达。recvfrom将与所接收的数据报一道返回客户的协议地址,因此服务器可以把相应发送给正确的客户。
前两篇分别介绍了IO多路复用的select()和poll()。
select()解决了传统的网络编程中,一个线程只能服务一个客户端连接的问题,但是最多只能同时监听1024个文件描述符,并且效率比较低。poll()解决了select()监听文件描述符个数限制的问题,但是并没有解决效率的问题。本篇介绍epoll(),既解决了描述符个数限制的问题,又解决了效率问题。
前一篇介绍了IO多路复用中的select(),但是select()的缺点很明显,监控描述符的个数不能超过1024个,所以后来为了解决这个问题,引入了poll(),理论上poll()监听的描述符个数不受限制,但是实际能监控的描述符个数和机器自身的硬件配置有关系。
在Linux系统下,select()为最早支持IO多路复用的系统调用。该接口相比poll()、epoll()来说,更为简单,但是效率较低,而且最大只支持FD_SETSIZE(1024)个描述符。如果对并发数量的要求高于1024,请考虑使用poll()或者epoll()。
Libevent 的 evbuffer 功能实现了一个字节队列,针对将数据添加到末尾和从前面删除数据进行了优化。
Evbuffers 通常用于执行缓冲网络 IO 的“缓冲”部分。它们不提供调度 IO 或在 IO 准备好时触发 IO 的功能:这是 bufferevents 所做的功能。
这一章节讲述一些bufferevent的高级特性,对初学者来说并不是必须的。如果你刚刚学习如何使用bufferevent,可以跳过本章节。
有时,你需要编写一个与自己通信的网络程序。例如,您可以编写一个程序来通过某个协议建立隧道用户连接,而该程序有时也希望通过该协议建立自己的连接隧道。当然,您可以通过打开与您自己的侦听端口的连接并让您的程序使用自己来实现这一点,但是,让您的程序通过网络堆栈与自己对话会浪费资源。
相反,您可以创建一对成对的bufferevent,以便写入一个的所有字节都被另一个接收(反之亦然),但不使用socket。
大多数时候,除了响应事件之外,应用程序还希望执行一些数据缓冲。例如,当我们想要写入数据时,通常的模式运行如下:
<event2/util.h>头文件定义了许多函数,您可能会发现这些函数对使用 Libevent 实现可移植应用程序很有帮助。 libevent 在内部使用这些类型和函数。
event是libevent的基本操作单元,每一个event代表一系列条件的集合,包括:
在你使用libevent的函数之前,你应该先创建一个或多个event_base结构,每一个event_base拥有event集合,并且轮询它们看哪个处于active状态。
大多数编程初学者使用的是阻塞的IO函数调用,也称为同步IO,当你调用它的时候,除非函数操作完成或者函数超时然后网络栈放弃的时候才会返回。举个例子,当你在TCP连接上调用connect()的时候,操作系统发送一个SYN数据包至对方主机,除非接收到了对方主机返回的SYN,ACK数据包才会返回至你的代码控制流程,或者是超时了,系统决定放弃的时候也会返回。