You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

106 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 43 预习 | Socket通信之网络协议基本原理
上一节我们讲的进程间通信其实是通过内核的数据结构完成的主要用于在一台Linux上两个进程之间的通信。但是一旦超出一台机器的范畴我们就需要一种跨机器的通信机制。
一台机器将自己想要表达的内容,按照某种约定好的格式发送出去,当另外一台机器收到这些信息后,也能够按照约定好的格式解析出来,从而准确、可靠地获得发送方想要表达的内容。这种约定好的格式就是**网络协议**Networking Protocol
我们将要讲的Socket通信以及相关的系统调用、内核机制都是基于网络协议的如果不了解网络协议的机制解析Socket的过程中你就会迷失方向因此这一节我们有必要做一个预习先来大致讲一下网络协议的基本原理。
## 网络为什么要分层?
我们这里先构建一个相对简单的场景,之后几节内容,我们都要基于这个场景进行讲解。
我们假设这里就涉及三台机器。Linux服务器A和Linux服务器B处于不同的网段通过中间的Linux服务器作为路由器进行转发。
![](https://static001.geekbang.org/resource/image/f6/0e/f6982eb85dc66bd04200474efb3a050e.png)
说到网络协议,我们还需要简要介绍一下两种网络协议模型,一种是**OSI的标准七层模型**,一种是**业界标准的TCP/IP模型**。它们的对应关系如下图所示:
![](https://static001.geekbang.org/resource/image/92/0e/92f8e85f7b9a9f764c71081b56286e0e.png)
为什么网络要分层呢?因为网络环境过于复杂,不是一个能够集中控制的体系。全球数以亿记的服务器和设备各有各的体系,但是都可以通过同一套网络协议栈通过切分成多个层次和组合,来满足不同服务器和设备的通信需求。
我们这里简单介绍一下网络协议的几个层次。
我们从哪一个层次开始呢从第三层网络层开始因为这一层有我们熟悉的IP地址。也因此这一层我们也叫IP层。
我们通常看到的IP地址都是这个样子的192.168.1.100/24。斜杠前面是IP地址这个地址被点分隔为四个部分每个部分8位总共是32位。斜线后面24的意思是32位中前24位是网络号后8位是主机号。
为什么要这样分呢?我们可以想象,虽然全世界组成一张大的互联网,美国的网站你也能够访问的,但是这个网络不是一整个的。你们小区有一个网络,你们公司也有一个网络,联通、移动、电信运营商也各有各的网络,所以一个大网络是被分成个小的网络。
那如何区分这些网络呢?这就是网络号的概念。一个网络里面会有多个设备,这些设备的网络号一样,主机号不一样。不信你可以观察一下你家里的手机、电视、电脑。
连接到网络上的每一个设备都至少有一个IP地址用于定位这个设备。无论是近在咫尺的你旁边同学的电脑还是远在天边的电商网站都可以通过IP地址进行定位。因此**IP地址类似互联网上的邮寄地址是有全局定位功能的**。
就算你要访问美国的一个地址也可以从你身边的网络出发通过不断的打听道儿经过多个网络最终到达目的地址和快递员送包裹的过程差不多。打听道儿的协议也在第三层称为路由协议Routing protocol将网络包从一个网络转发给另一个网络的设备称为路由器。
路由器和路由协议十分复杂,我们这里就不详细讲解了,感兴趣可以去看我写的另一个专栏“趣谈网络协议”里的[相关文章](https://time.geekbang.org/column/article/8729)。
总而言之第三层干的事情就是网络包从一个起始的IP地址沿着路由协议指的道儿经过多个网络通过多次路由器转发到达目标IP地址。
从第三层我们往下看第二层是数据链路层。有时候我们简称为二层或者MAC层。所谓MAC就是每个网卡都有的唯一的硬件地址不绝对唯一相对大概率唯一即可类比[UUID](https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E5%94%AF%E4%B8%80%E8%AF%86%E5%88%AB%E7%A0%81))。这虽然也是一个地址,但是这个地址是没有全局定位功能的。
就像给你送外卖的小哥不可能根据手机尾号找到你家但是手机尾号有本地定位功能的只不过这个定位主要靠“吼”。外卖小哥到了你的楼层就开始大喊“尾号xxxx的你外卖到了
MAC地址的定位功能局限在一个网络里面也即同一个网络号下的IP地址之间可以通过MAC进行定位和通信。从IP地址获取MAC地址要通过ARP协议是通过在本地发送广播包也就是“吼”获得的MAC地址。
由于同一个网络内的机器数量有限通过MAC地址的好处就是简单。匹配上MAC地址就接收匹配不上就不接收没有什么所谓路由协议这样复杂的协议。当然坏处就是MAC地址的作用范围不能出本地网络所以一旦跨网络通信虽然IP地址保持不变但是MAC地址每经过一个路由器就要换一次。
我们看前面的图。服务器A发送网络包给服务器B原IP地址始终是192.168.1.100目标IP地址始终是192.168.2.100但是在网络1里面原MAC地址是MAC1目标MAC地址是路由器的MAC2路由器转发之后原MAC地址是路由器的MAC3目标MAC地址是MAC4。
所以第二层干的事情,就是网络包在本地网络中的服务器之间定位及通信的机制。
我们再往下看第一层物理层这一层就是物理设备。例如连着电脑的网线我们能连上的WiFi这一层我们不打算进行分析。
从第三层往上看第四层是传输层这里面有两个著名的协议TCP和UDP。尤其是TCP更是广泛使用在IP层的代码逻辑中仅仅负责数据从一个IP地址发送给另一个IP地址丢包、乱序、重传、拥塞这些IP层都不管。处理这些问题的代码逻辑写在了传输层的TCP协议里面。
我们常称TCP是可靠传输协议也是难为它了。因为从第一层到第三层都不可靠网络包说丢就丢是TCP这一层通过各种编号、重传等机制让本来不可靠的网络对于更上层来讲变得“看起来”可靠。哪有什么应用层岁月静好只不过TCP层帮你负重前行。
传输层再往上就是应用层例如咱们在浏览器里面输入的HTTPJava服务端写的Servlet都是这一层的。
二层到四层都是在Linux内核里面处理的应用层例如浏览器、Nginx、Tomcat都是用户态的。内核里面对于网络包的处理是不区分应用的。
从四层再往上就需要区分网络包发给哪个应用。在传输层的TCP和UDP协议里面都有端口的概念不同的应用监听不同的端口。例如服务端Nginx监听80、Tomcat监听8080再如客户端浏览器监听一个随机端口FTP客户端监听另外一个随机端口。
应用层和内核互通的机制就是通过Socket系统调用。所以经常有人会问Socket属于哪一层其实它哪一层都不属于它属于操作系统的概念而非网络协议分层的概念。只不过操作系统选择对于网络协议的实现模式是二到四层的处理代码在内核里面七层的处理代码让应用自己去做两者需要跨内核态和用户态通信就需要一个系统调用完成这个衔接这就是Socket。
## 发送数据包
网络分完层之后,对于数据包的发送,就是层层封装的过程。
就像下面的图中展示的一样在Linux服务器B上部署的服务端Nginx和Tomcat都是通过Socket监听80和8080端口。这个时候内核的数据结构就知道了。如果遇到发送到这两个端口的就发送给这两个进程。
在Linux服务器A上的客户端打开一个Firefox连接Ngnix。也是通过Socket客户端会被分配一个随机端口12345。同理打开一个Chrome连接Tomcat同样通过Socket分配随机端口12346。
![](https://static001.geekbang.org/resource/image/98/28/98a4496fff94eb02d1b1b8ae88f8dc28.jpeg)
在客户端浏览器我们将请求封装为HTTP协议通过Socket发送到内核。内核的网络协议栈里面在TCP层创建用于维护连接、序列号、重传、拥塞控制的数据结构将HTTP包加上TCP头发送给IP层IP层加上IP头发送给MAC层MAC层加上MAC头从硬件网卡发出去。
网络包会先到达网络1的交换机。我们常称交换机为二层设备这是因为交换机只会处理到第二层然后它会将网络包的MAC头拿下来发现目标MAC是在自己右面的网口于是就从这个网口发出去。
网络包会到达中间的Linux路由器它左面的网卡会收到网络包发现MAC地址匹配就交给IP层在IP层根据IP头中的信息在路由表中查找。下一跳在哪里应该从哪个网口发出去在这个例子中最终会从右面的网口发出去。我们常把路由器称为三层设备因为它只会处理到第三层。
从路由器右面的网口发出去的包会到网络2的交换机还是会经历一次二层的处理转发到交换机右面的网口。
最终网络包会被转发到Linux服务器B它发现MAC地址匹配就将MAC头取下来交给上一层。IP层发现IP地址匹配将IP头取下来交给上一层。TCP层会根据TCP头中的序列号等信息发现它是一个正确的网络包就会将网络包缓存起来等待应用层的读取。
应用层通过Socket监听某个端口因而读取的时候内核会根据TCP头中的端口号将网络包发给相应的应用。
HTTP层的头和正文是应用层来解析的。通过解析应用层知道了客户端的请求例如购买一个商品还是请求一个网页。当应用层处理完HTTP的请求会将结果仍然封装为HTTP的网络包通过Socket接口发送给内核。
内核会经过层层封装从物理网口发送出去经过网络2的交换机Linux路由器到达网络1经过网络1的交换机到达Linux服务器A。在Linux服务器A上经过层层解封装通过socket接口根据客户端的随机端口号发送给客户端的应用程序浏览器。于是浏览器就能够显示出一个绚丽多彩的页面了。
即便在如此简单的一个环境中,网络包的发送过程,竟然如此的复杂。不过这一章后面,我们还是会层层剖析每一层做的事情。
## 总结时刻
网络协议是一个大话题,如果你想了解网络协议的方方面面,欢迎你订阅我写的另一个专栏“趣谈网络协议”。这个专栏重点解析在这个网络通信过程中,发送端和接收端的操作系统都做了哪些事情,对于中间通路上的复杂的网络通信逻辑没有做深入解析。
如果只是为了掌握这一章的内容这一节我们讲的网络协议的七个层次你不必每一层的每一个协议都很清楚只要记住TCP/UDP->IPv4->ARP这一条链就可以了因为后面我们的分析都是重点分析这条链。
另外,前面那个简单的拓扑图中,网络包的封装、转发、解封装的过程,建议你多看几遍,了熟于心,因为接下来,我们就能从代码层面,看到这个过程。到时候,对应起来,你就比较容易理解。
了解了Socket的基本原理下一篇文章我们就来看一看在Linux操作系统里面Socket系统调用的接口是什么样的。
![](https://static001.geekbang.org/resource/image/8c/37/8c0a95fa07a8b9a1abfd394479bdd637.jpg)