自从我提出这个问题以来,我对这个话题有了更多的了解,所以我会尝试自己回答。
描绘协议栈的最简单方法是将一封信包裹在一系列信封中。每个信封在将信件送到收件人的过程中扮演着不同的角色,并且在整个过程中会根据需要添加和移除信封。
应用层
这封信本身就是一个应用层请求。例如,您在浏览器中输入了“StackOverflow.com”并按下回车键。您的浏览器需要向 StackOverflow 服务器询问其主页。所以它写了一封信说,“亲爱的 StackOverflow,你能把你的主页发给我吗?”
如果信件的作者是您的浏览器,那么信件的收件人是在 StackOverflow 上运行的 Web 服务器程序。浏览器希望 Web 服务器以网页形式“回写”响应。浏览器和服务器都是应用程序 - 在特定计算机上运行的程序。
因为浏览器使用 HTTP,所以它使用 HTTP 来发出请求:这封信的内容类似于“GET http://stackoverflow.com”。浏览器还会记下它上次从 StackOverflow 获得的任何 cookie 信息(“还记得我吗?你告诉我我的登录 ID 是 X”)并添加一些称为“标题”的杂项标记信息(例如“我是 Firefox”和“我可以接受 HTML 或文本”和“如果您使用 gzip 压缩内容,我可以接受”)。所有这些信息都将帮助服务器了解如何个性化或自定义其响应。
至此,浏览器基本完成。它把这封信交给操作系统,然后说:“你能帮我发这个吗?”操作系统说,“当然”。然后它会做一些工作来连接到 StackOverflow(稍后会详细介绍),然后告诉浏览器,“我正在处理它。顺便说一下,这是我为你制作的一个小邮箱,称为套接字。当我收到了 StackOverflow 的回复,我会把它的信放在那里,你可以像阅读文件一样阅读它。”然后浏览器愉快地等待响应。
IP 层
要将请求从浏览器发送到 StackOverflow,操作系统必须做几件事。
首先,它必须查找 StackOverflow.com 的地址——特别是 IP 地址。它使用 DNS(我不会在这里介绍)来执行此操作。一旦知道了 IP 地址,它就会知道如何将请求包装在称为 IP 层的“信封”之一中。
为什么我们需要 IP 层?嗯,从前,我们没有。
为什么我们需要 IP
您是否看过一部老电影,其中有人通过要求接线员连接他们来拨打电话?操作员将把从第 1 个人的房子的电线物理连接到第 2 个人的房子的电线。在协议栈发明之前,连接计算机很像打电话:您需要一条专用线从点到点。
因此,例如,如果斯坦福大学的计算机科学家想与哈佛的计算机科学家交换数据,他们会花一大笔钱在两地之间租用专用线路(“租用线路”)。任何进入一端的数据都会在另一端可靠地输出。但是,这非常昂贵:想象一下,您要为每个要连接的地方支付单独的线路!
人们意识到这不会扩大规模。我们需要一种方法来建立一个所有用户共享的网络,就像一个巨大的电线蜘蛛网遍布整个地图。这样,每个用户只需要一个网络连接,就可以通过它访问任何其他用户。
但这带来了一个问题。如果每个人的通信都在同一条线上,那么数据将如何到达正确的位置?想象一下,一堆信件倾倒在传送带上。显然,每封信都需要寄给某人,否则就无法送达。
这就是 IP 的基本思想:每台机器都需要有一个唯一标识它的 IP 地址。消息被放置在 IP 数据包中,就像带有地址和返回地址的信封。
因此,一旦操作系统查找了 Stackoverflow.com 的 IP 地址,它就会将 HTTP 请求放入 IP 信封中。如果它是一封“长信”,对于一个信封来说太大了,操作系统会将其切成碎片并放入多个 IP 信封中。每个信封都写着“FROM: (你的 IP 地址); TO: (服务器的 IP 地址。” 像 HTTP 请求一样,IP 数据包还有一些其他杂项头信息,我们不会在这里介绍,但基本的想法只是“到”和“从”。
所以,此时,这封信已准备就绪,对吧?
IP 的混乱
不完全是。这封信很容易丢失!看,有了IP,我们不再有一条从一个地方到另一个地方的专线。如果我们这样做了,我们就可以确保我们的信件会送达:只要线路没有断线,一切都会顺利进行。
但是使用 IP,每个人的数据包都会被倾倒在传送带上并随身携带。传送带通向称为“路由器”的小型分拣站。如果您将路由器想象成物理邮件中心,您可以想象一个在纽约市。
“这是一封寄往墨西哥城的信。我不知道具体怎么去那里,但是休斯顿的车站应该可以更近一些,所以我会寄到那里。啊,这是一封信去亚特兰大。我会把它寄给夏洛特;他们应该可以再往前走一步。”
一般来说,这个系统工作正常,但不如拥有自己的专线可靠。几乎任何事情都可能在途中发生:传送带可能断裂或着火,其上的所有东西都可能丢失。或者一个人可能会陷入一段时间的困境,以至于它的数据包很晚才送达。
除此之外,因为这些传送带和车站是每个人都使用的,所以没有人会特别对待任何人的信件。那么,如果路由器收到的信件超出了它的处理能力,会发生什么?有一段时间,它可以将它们堆叠在一个角落(可能在 RAM 中),但最终,它会耗尽空间。
然后它所做的可能看起来令人震惊:它开始将它们扔掉。
是的。而已。你可能会认为,至少给你回个便条,说:“对不起,我们无法送达你的信。”但事实并非如此。如果你仔细想想,如果路由器不堪重负,那可能是因为线路上的流量已经太多了。添加道歉说明只会使问题变得更糟。所以它会扔掉你的数据包,并且不会告诉任何人。
显然,这是我们的 HTTP 请求的问题。我们需要它到达那里,我们也需要可靠地返回响应。
为了确保它到达那里,我们需要某种“交货确认”服务。为此,在放入 IP 数据包之前,我们将在 HTTP 请求周围包裹另一个信封。该层称为 TCP。
TCP
TCP 代表“传输控制协议”。它的存在是为了控制原本混乱、容易出错的交付过程。
如前所述,TCP 让我们可以在这个杂乱无章的交付系统中添加一些“交付确认”。在我们将 HTTP 请求包装在 IP 数据包中之前,我们首先将其放入 TCP 数据包中。每个人都有一个编号:数据包 1 of 5、2 of 5 等等。(编号方案实际上更复杂,计数字节而不是数据包,但我们暂时忽略它。)
TCP的基本思想是这样的:
- 首先,客户端和服务器(在本例中为您的操作系统和 StackOverflow 服务器的操作系统)进行“握手”以建立“连接”。这两个词都需要引号,因为“握手”实际上是来回传递一些消息,证明数据包可以成功往返,而“连接”实际上无非是双方决定他们将跟踪数据包在它们之间流动。
- 接下来,它们来回发送数据包;客户端可能会请求一个网页,而服务器可能会将其发回(尽可能多的数据包)。
- 当一侧收到数据包时,它会发回确认消息,说“到目前为止,我已经收到了您的数据包,直到数据包 100”等等。如果一方发送数据包并且有一段时间没有听到确认消息,它将假定它们已丢失并重新发送。
(当事情到达另一端时获得确认比在路由器沿途丢弃东西时获得错误报告要好,原因有两个。一个是确认通过工作连接返回,而错误会进一步阻塞非工作连接。另一个是我们不必信任中间路由器来做正确的事情;客户端和服务器是最关心这个特定对话的人,所以他们是负责确保它有效。)
除了确保所有数据都到达另一端之外,TCP 还确保在将接收到的数据送回堆栈之前将其放回正确的顺序,以防较早的数据包被重新发送并稍后到达,或者数据包中间走更长的路,或者其他什么。
基本上就是这样 - 拥有这种交付确认会使不可靠的 IP 网络变得可靠。
为什么不直接内置到 IP 中?
UDP
嗯,确认有一个缺点:它会使事情变慢。如果遗漏了什么,就必须重复。在某些情况下,这会浪费时间,因为您真正想要的是实时连接。例如,如果您正在通过 IP 进行电话交谈,或者您正在通过互联网玩实时游戏,您想知道现在发生了什么,即使这意味着您错过了一秒钟前发生的一些事情。如果你停下来重复一些事情,你就会与其他人失去同步。在这种情况下,您可以使用称为 UDP 的 TCP 表亲,它不会重新发送丢失的数据包。 UDP代表“用户数据报协议”,但很多人认为它是“不可靠的数据协议”。这不是侮辱。有时可靠性不如保持最新重要。
由于这两个都是有效的用例,IP 协议在可靠性问题上保持中立是有道理的;使用它的人可以选择是否增加可靠性。
TCP 和 UDP 都在请求中添加了另一条重要信息:端口号。
端口号
请记住,我们的原始请求来自浏览器,并且将发送到 Web 服务器程序。但是 IP 协议只有指定计算机的地址,而不是在它们上运行的应用程序。装有 StackOverflow 网络服务器的机器也可能有其他服务器程序正在监听请求:数据库服务器、FTP 服务器等。当该机器收到请求时,它如何知道应该由哪个程序处理?
它会知道,因为 TCP 请求上有一个端口号。这只是一个数字,没什么花哨的,但按照惯例,某些数字被解释为意味着某些事情。例如,使用端口号 80 是表示“这是对 Web 服务器的请求”的传统方式。然后服务器机器的操作系统会知道将该请求交给 Web 服务器程序,而不是 FTP 服务器程序。
当 TCP 数据包开始流回您的计算机时,它们还会有一个端口号,让您的机器知道要响应哪个程序。该数字将根据您的机器最初创建的套接字而有所不同。
等等,什么是套接字?
套接字
还记得之前浏览器要求操作系统发送请求吗?该操作系统表示,它将为收到的任何回复设置一个“邮箱”。该 bin 称为套接字。
你可以把套接字想象成一个文件。文件是操作系统提供的接口。它说:“你可以在这里读取和写入数据,我将负责弄清楚如何将其实际存储在硬盘驱动器或 USB 密钥或其他任何东西上。”唯一标识文件的是路径和文件名的组合。换句话说,您只能在同一个文件夹中拥有一个同名文件。
同样,套接字是操作系统提供的接口。它说,“你可以在这里写请求并阅读响应。”唯一标识一个套接字的东西是四个东西的组合:
因此,您只能在系统上拥有一个具有所有这些相同组合的套接字。请注意,您可以轻松地将多个套接字打开到相同的目标 IP 和端口 - 例如 StackOverflow 的 Web 服务器 - 只要它们都有不同的源端口。操作系统将通过为每个请求选择任意源端口来保证他们这样做,这就是为什么您可以让多个选项卡或多个浏览器同时请求同一个网站而不会混淆;返回的数据包都说明了它们要前往您计算机上的哪个端口,这让操作系统知道“啊,这个数据包是用于 Firefox 中的选项卡 3”或其他任何内容。
到目前为止的总结
我们一直将协议视为包裹在信件周围的一系列信封。在我们的示例中,这封信是一个 HTTP 请求,它被封装在 TCP 中,然后是 IP。 IP 数据包被发送到正确的目标计算机。那台计算机删除了 IP“信封”并在里面找到了一个 TCP 数据包。 TCP 数据包有一个端口号,它让操作系统知道在哪个端口收集其信息。它回复说它收到了该数据包,并将其内容(HTTP 请求)放入正确的套接字中,以便适当的程序读自。当该程序向套接字写入响应时,操作系统会将其发送回请求者。
所以我们的“堆栈”是:
- 一个 HTTP 请求(一个“信”)。这是应用层。
- 包装在 TCP 数据包(“信封”)中。这是传输层。
- 包装在 IP 数据包(“信封”)中。这是 IP 层。
了解此堆栈是完全可定制的,这一点很重要。所有这些“协议”都只是标准的做事方式。如果您认为接收计算机知道如何处理 IP 数据包,您可以将任何您想要的内容放入 IP 数据包中;如果您认为接收应用程序知道如何处理,您可以将任何您想要的内容放入 TCP 或 UDP 数据包中它。
您甚至可以在 HTTP 请求中添加其他内容。你可以说里面的一些JSON数据就是“电话号码交换协议”,只要两端都知道怎么处理就可以了,你只是添加了一个更高级别的协议。
当然,你可以在堆栈中的“高”程度是有限制的——也就是说,你可以在 HTTP 中放置一个较小的信封,在其中放置一个较小的信封,等等,但最终你不会有任何空间变小;你不会有任何实际内容的位。
但是您可以轻松地在堆栈中“降低”;您可以在现有的周围包裹更多的“信封”。
其他协议层
曾经常见的环绕 IP 的“信封”是以太网。例如,当您的计算机决定向 Google 发送 IP 数据包时,它会按照我们目前所描述的那样包装它们,但要发送它们,它会将它们提供给您的网卡。然后,网卡可以将 IP 数据包包装在以太网数据包(或令牌环数据包,如果你有一个古老的设置)中,将它们寻址到你的路由器并将它们发送到那里。您的路由器删除那些以太网“信封”,检查 IP 地址,确定下一个最近的路由器是谁,包装另一个发往该路由器的以太网信封,然后发送数据包。
也可以包装其他协议。也许两个设备只是无线连接,因此它们将以太网数据包包装在 Wi-Fi、蓝牙或 4G 协议中。也许你的包裹需要穿越一个没有电的村庄,所以有人将包裹打印在纸上,上面有编号的页面,骑着自行车穿过城镇,然后按照页码的顺序将它们扫描到另一台计算机中。瞧!打印到 OCR 协议。或者,我不知道,TCP over carrier pigeon 会更好。
结论
协议栈是一项美丽的发明,它运行良好,我们通常认为它是理所当然的。
这是抽象功能的一个很好的例子:每一层都有自己的工作要做,并且可以依赖其他人来处理其余的。
-
应用程序层只关心应用程序之间的通信:“Firefox 想与 StackOverflow.com 上的网络服务器通信。”
-
传输层只关心从一个应用程序向另一个应用程序正确传递数据包流:“来自机器 1 上的端口 123 的所有数据包都需要到达机器 2 上的端口 80” .
-
IP 层 只关心路由单个数据包:“此数据包需要到达以下 IP 地址。”
-
链路层只关心从一个路点到下一个路点获取数据包:“这个以太网数据包需要从网卡到路由器。”
-
物理层只关心信号传输:“这些脉冲需要通过这条线路发送。”
(虽然这些层术语是从OSI 借来的,但 OSI 实际上是与 TCP/IP 竞争的标准,包括 TCP/IP 不使用的“会话层”和“表示层”之类的东西。OSI旨在成为一个更健全和标准化的替代方案,以取代杂乱无章的 TCP/IP 堆栈,但在仍在讨论中的同时,TCP/IP 已经在工作并被广泛采用。)
因为可以根据需要混合和匹配层,所以堆栈足够灵活,几乎可以满足我们能想到的任何用途,因此它可能会存在很长时间。希望现在你能更加欣赏它。