网络是怎样连接的
淘宝手机客户端浏览商品是怎么获取到服务器数据的?这里会衍生出以下一些问题。
上图一些列问题反映了很多知识点,接下来会一个一个通俗点描述其工作原理。
从下面三个方面可以更加有一个直观认识
- 客户端
- 网络传输
- 数据中心
1. 客户端
不管是手机客户端还是pc,客户端发送内容到服务器都需要经过下面几个过程:
- 生成http请求消息
- 向DNS服务器查询服务器的ip地址
- 委托协议栈发送消息,创建套接字Socket,发送数据,断开服务器连接
1.1 生成http请求消息
Http请求消息是应用程序生成的,而且有一些第三方库很方便应用开发者生成请求消息内容,比如android应用程序可以用okhttp,ios应用程序可以用NSURLSession,go提供的net库等。但是http请求消息体都有其标准格式,如下图所示:
http请求消息生成之后,由于应用本身不具备将消息发送到网络功能,这需要将这个任务交给操作系统来实现,但是交给操作系统之前,比如要将域名解析为对应的IP地址。如果本来就是通过IP直接访问,那么这个步骤就省略了。
1.2 向DNS服务器查询服务器的ip地址
这里先介绍下IP地址是什么? 如下图所示:
主机号部分的比特全部为0或者全部为1时代表以下两种特殊的含义。
- 主机号部分全部为0代表整个子网而不是子网中的某台设备。
- 主机号部分全部为1代表向子网上所有设备发送包,即广播.
域名解析过程入下图www.lab.glasscom.com所示,肯定会先访问最近的一台DNS服务器,如果最近的DNS服务器没有存放域名对应信息。由于最近的DNS服务器没有存放www.lab.glasscom.com对应的信息,我们需要从顶往下找。最近的 DNS 服务器中保存了根域DNS服务器的信息,因此它会将来自客户端的查询消息转发给根域DNS服务器,根域服务器中也没有www.lab.glasscom.com这个域名,但根据域名结构可以判断这个域名属于com域,因此根域 DNS 服务器会返回它所管理的com域中的DNS服务器的IP地址。接下来最近的DNS服务器又会向com域的DNS服务器发送查询消息com域中也没有www.lab.glasscom.com这个域名的信息,和刚才一样com域服务器会返回它下面的 glasscom.com域的DNS服务器的IP地址。以此类推,只要重复前面的步骤,就可以顺藤摸瓜找到目标DNS服务器。整个过程如下图所示。
对于客户端来说,其实还有一部就是本地DNS缓存,本地DNS缓存也就是在/etc/hosts文件,这也是为啥我们可以在hosts文件中重定向域名对应的ip地址。
传统的DNS存在哪些问题:
- 域名缓存问题,本地DNS可能缓存了域名解析结果会导致DNS解析变更没有及时生效。
- 域名转发问题,运营商偷懒,将域名解析请求转发给其他运营商解析,导致跨运营商访问。
- 出口NAT问题,NAT有可能导致权威DNS服务对NAT过的地址产生误解导致跨运营商访问。
- 解析延迟问题,DNS可能遍历多个DNS服务器才知道解析结果。
为了解决这4个问题,腾讯最初提出了HTTPDNS,HTTPDNS工作原理如下图所示:
其主要思想就是给自己服务器自建DNS,自己能控制DNS解析到具体哪个服务器的ip,自定义缓存失效策略,负责均衡策略等。HTTPDNS具体实现机制可以阅读下这篇文章 腾讯千亿级HttpDNS服务是怎样炼成的
1.3 套接字Socket
在建立Socket时候,往往意识不到中间经历过多少局域网,多少路由器。在网络层,Socket需要指定到底是ipv4还是ipv6。下面以TCP为例说下整个Socket套接字过程。 TCP的服务器需要先监听一个端口,一般是先调用bind函数给socket ip地址和端口号。为什么要端口号了?应用程序当一个网络包来的时候,内核要通过TCP头里面的这个端口来找到应用程序,然后把包丢给应用程序。那么为什么又需要IP地址了,有时候一台机器有多个网卡,也就对应多个IP地址,可以选择监听不同的网卡,这样只有发给发送这个网卡的包才会转发给你。
在内核中会为每个socket维护两个队列,一个是已经建立连接的队列,这个队列的三次握手都已经经过了,处于established状态;还有一个是没有完全建立连接的队列,这个队列三次握手还没有完成,处于syn_rcvd状态。 客户端调用connect函数发起连接后,开始三次握手,内核会给客户端分配一个临时端口,一旦握手成功,服务器accept就返回另外一个socket。也即是监听的socket和真正用来传输数据的socket是两个,一个是监听socket另外一个叫已连接socket。
服务器端最大连接数理论上限 = 客户端ip数 * 客户端端口数 = 2的32次方 * 2的16次方,但是这是理论上限,由于操作系统文件描述符限制,socket都是文件,虽然我们可以修改这个文件描述符数目,但是文件描述符都是占用内存的,所以服务器的最大并发远达不到这个数量级。
为了解决Socket套接字限制,出来了三种IO模型(BIO, NIO, AIO),可以看看这篇文章简单解释。 https://www.jianshu.com/p/d23f6d261a04
socket连接后,需要经历三次握手,那三次握手具体是怎么操作的了,请看下图动画所示:
这里有一个问题,为什么tcp是三次握手,而不是2次,或者4次5次6次了? 如果是两次握手,那么必然是不需要最后一次,这会导致server端发给客户端的包server不知道有没有收到,也就不能确定这个连接是不是有效的。对于大于3次握手也是可以的,但是这真的没有必要。
TCP发送数据完需要关闭socket通道,就需要经历四次挥手,如下图所示:
从上图看断开连接过程比建立连接过程复杂多了,为啥了?这就好像编程时候申请内存容易但是释放内存难。
- 主动方A发送断开连接的请求(即FIN报文)给被动方B时,仅仅代表A不会再发送数据报文了,但A仍可以接收数据报文。
- 被动方B此时有可能还有相应的数据报文需要发送,因此需要先发送ACK报文,告知主动方“我知道你想断开连接的请求了”。这样A便不会因为没有收到应答而继续发送断开连接的请求(即FIN报文)。
- B在处理完数据报文后,便发送给主动方FIN报文;这样可以保证数据通信正常可靠地完成。发送完FIN报文后,B进入LAST_ACK阶段(超时等待)。
- 如果A及时发送ACK报文进行连接中断的确认,B就直接释放连接,进入可用状态。
- 如果A没有TIME_WAIT的存在,或者说,停留在TIME_WAIT上的时间很短,则A很快就进入了CLOSED状态,也即是说,如果此时新建一个连接,源随机端口如果被复用,在connect发送SYN包后,由于B仍认为这条连接还在等待ACK,但是却收到了SYN,则被动方会回复RST。
这里涉及到的概念比较多,可以参考 https://www.jianshu.com/p/44655bff60a4 这里其中有两个很重要的概念对于定位问题非常重要,第一是Time_Close,第二个是Time_Wait,可以通过下面两个命令查看系统资源试用情况
➜ ~ netstat -a | grep -i CLOSE_WAIT | wc -l
➜ ~ netstat -a | grep -i TIME_WAIT | wc -l
如果系统的close_wait 过多,很大可能说明应用程序有问题。推荐看看这篇文章
应用程序在调用write发送数据时候并不指定发送数据的长度,在操作系统协议栈看来这就是二进制数据而已,操作系统协议栈并不是收到数据马上发送出去,它会先数据放到一个缓冲区,如果每次应用程序收到数据包就将数据马上发送出去,这可能导致大量小包,导致网络拥堵。判断是不是要将缓冲区的数据发送出去,一般依赖MTU和时间两个要素来控制,MTU是一个网路包的最大长度,以太网一般是1500(包含协议头),MTU和时间都是由操作系统控制。
2. 网络传输
本节探索网络包进入互联网之前和在互联网传输过程中经历的过程,网络包从客户端计算机出发经过集线器,交换机和路由器,最终进入互联网,实际上家用路由器包含了集线器和交换机功能。
计算机程序处理包括存储的都是二进制数据,二进制数据是怎么到达远在天边的服务器的了?要彻底弄清楚这个之前,我们要先知道以下三类协议有啥区别,都是干啥的。
- TCP/UDP协议等
- IP协议
- 以太网协议
TCP/UDP协议:可以理解为是受咋们程序控制的,他的作用就是用来将数据包裹起来传递给对方,类似快递打包过程。 IP协议:要将快递从A送到B,中间肯定经历不同的站点,IP作用就是用来寻找下一个站点用的。 以太网协议:这个就是用来真正传输数据的数据链路,可以用飞机送,可以用绿皮车等。
2.1 快递打包
快递打包过程就是请求体生成过程,不同协议不同的格式规划,这个是操作系统协议栈处理的事情,不同协议格式都有自己的规范,感兴趣的可以参阅网上文章查看。
2.2 寻找站点
上图很形象描述了IP网络包传输方式,其实就是每经过一个路由器,这个路由器负责填写下一个路由器ip地址和mac地址。 这里还有一个疑问,它是怎么知道下一跳的ip和mac地址的了(注意这里只会修改包的mac,源ip和目标ip在整个路由过程中都不会变化)。
下一跳的ip地址是通过路由表查到的,下一跳ip的mac地址是通过ARP地址解析协议拿到的。这里衍生出另外两个问题:
- 路由器加入到网络后,路由表怎么维护的?
- ARP 获取mac地址是广播给谁,拿不到ip对应的mac地址咋办?
问题一:有两类算法,第一种是距离矢量路由算法,这个算法很笨,就是每个路由器知道所有其他路由器的距离。他每隔几秒把自己知道的告诉邻居,然后获取从邻居相应的信息。另外一个是链路状态路由算法,这个算法基本思想是,当一个路由器启动时候,首先向邻居发送hello信息,所有邻居都回复,然后计算和邻居的距离,接下来将自己和邻居的链路状态广播出去,发送到整个网络的每个路由器中,这样每个路由器都能够收到它和邻居之间的关系信息,这样每个路由器都能构建一个完整的网络拓扑图。
这两个算法都有自己的应用场景,基于链路状态路由算法的OSPF路由协议,和基于距离矢量路由算法的BGP(Border Gateway Protocol)路由协议,OSPF广泛应用于数据中心。上面的两个算法都是基于所有路由器连接在一起,但是在真实网络中,这是不可能的。为了解决全世界的网络都连在一起这个问题,人们将网络按区块分、层级分,同一区域内的数据包在区域内路由,不再同一区域的包才发往其他区域。区域之间的路由通过边界路由器,这样就是网络里面的自治系统,BGP协议负责将全世界的自治系统连接起来。
下图描述了一个自治系统里面的数据包流转流程。
上图R1-R6都是路由器,每个路由器都有4个接口,每个接口都绑定一个ip地址。这里假设Sender发一个包往Receiver
- Sender要发送数据包给Receiver,通过路由表查询到,其下一跳路由器地址是R1上的1接口绑定的IP(后面表示为R1-1-ip)。Sender通过ARP协议获得了R1-1-ip处的MAC地址,通过查询转发表,得到数据包要去往R1-1-ip,需要通过其与R1的唯一个接口传输出去。
- R1-1接收到数据包时,通过查询路由表,查到数据包要发送往Receiver需要经过R4-1-ip,即其下一跳地址是R4-1-ip,R1通过ARP协议得到R4-1的Mac地址,然后通过转发表查询到数据包要从R1发往R4,需要通过R1-4这个接口,于是数据包通过R1-4这个接口连接的链路,发送到了R4。
- R4收到数据包以后,由于Receiver所在的网络与自己的R4-4接口连接,于是数据包通过R4-4最终传输到Receiver中。
自治系统通俗点说就是网络世界的一个王国,中国电信和中国移动,中国电信通过BGP学习到中国移动的路由信息,就会把用户访问中国移动服务器的请求转发给中国移动的边界路由器。
问题二:可以参考这个分享 https://zhuanlan.zhihu.com/p/28771785。
数据包路由过程中有三个很重要的概念
- TTL(timve to live),TTL是经过路由器的跳数,每经过一个路由器,TTL都要减1,如果TTL为0还没有达到目的地,数据包就被扔掉。
- MTU,它表示一个网络包的最大长度,以太网一般是1500字节,数据报经过不同的路由器会有不同的MTU。
- RTT,一个数据包从发送到收到回执的往返时间。
网络世界有一个很重要的ICMP互联网控制报文协议(Internet Control Message Protocol),我们经常用的ping和traceroute都是基于它实现的。Ping原理是构建一个ICMP报文,源主机如果收到ICMP应答包,则可以计算数据包时延。如果收不到则说明目标主机不可达。我们不能认为Ping不通代表网络不通, 因为有些中间设备禁止Ping,这个时候telnet就派上用场了。 traceroute可以追踪到达目的地经过的路由器。traceroute是通过修改发出去的UDP数据包的TTL值来实现的,比如修改成1,到达第一个路由器并受到ICMP报文回应就知道第一个路由器地址。但是有些路由器就是傲慢,它错了也不返回回应。traceroute还可以设置数据包不分片传输来确定路径MTU大小,原理是发送数据包时候故意设置不分片,当收到ICMP差错报文时候就知道发出去的数据包太大了,我们可以逐步缩小数据包大小来确定路径MTU大小。
2.3 链路数据传输
宽带上网数据链路传输分为两类
- 电话线/光纤通信
- 无线通信(wifi/2G/3G/4G/5G)
宽带上网最早是拨号上网,拨号上网用的是电话线网络数据传输,而且一旦你拨号上网就不能打电话了,而且费用贵速度慢。后台开始有了ADSL上网,ADSL也是基于电话线上网,只是充分发挥了电话线的作用,将速度提高到20Mbps,不过再怎么发挥电话线作用,铜线毕竟是铜线,速度还是有天然限制的。于是就有了后面的光纤通讯。光纤通讯原理可以看这个视频
那通常我们小区里面光纤入户到底是啥概念了,下图描述了接入互联网主干网络的主要部分。
对于无线4g上网流程如下图所示。
手机通过4G上网第一是查找信号,这首先会搜索附近的信号塔eNodeB,连接到eNodeB后,eNodeB请求MME要认证手机,接下来就是手机鉴权阶段(手机是不是工信部认证了,手机啥套餐,是不是欠费了)。如果认证通过了就告诉SGW创建一个会话,SGW向PGW建立一个会话,PGW是出口网关,出口网关里面有一个计费单元,他就是用来计算我们每个月流量费用的😄。这里SGW是本地运营商设备,PGW是手机卡所述运营商设备,如果你在美国,手机先通过eNodeB,先要找手机卡所在运营商的HSS问下有没有欠费等,然后连接到本地运营商设备SWG后,本地运营商还需要和手机卡那个运营商建立一个隧道,然后通过PGW上网。比如如果手机允许的话,你可以在手机上插入一个国外的手机卡,这样就不用翻墙了,不过手机制造商一般都有运营商限制。
3. 数据中心
数据包从链路层流到数据中心边界路由器,边界路由器将数据包路由到接入路由器然后经由核心路由器转发到应用服务器的接入路由器,最终到达应用服务器。本节主要从下面几个方面从宏观认识数据中心。
- 数据中心拓扑结构。
- 虚拟机网络。
- 容器网络。
整个数据中心的结构图如下图所示:
对于云计算,我们申请的云服务器ECS是一种简单高效、处理能力可弹性伸缩的计算服务,而构建ECS服务的最底层肯定是物理机,云计算厂商不能说用户申请机器就购买服务器并部署,这个实现不了即买即用需求。为了达到ECS要求,计算机虚拟化技术就派上用场了,
虚拟化技术:将一台计算机虚拟为多台逻辑计算机。在一台计算机上同时运行多个逻辑计算机,每个逻辑计算机可运行不同的操作系统,并且应用程序都可以在相互独立的空间内运行而互不影响。
虚拟化技术细节不是本篇内容😄,我们需要了解虚拟化网络,也就是网络包到达应用服务器连接的路由器怎么将数据送到服务器应用程序。
当数据包经由路由器的一个端口到达物理交换机,最后到达的是外网网关节点上的SLB节点(这个公网ip地址,这个公网的IP地址是通过云平台的边界路由器暴露给外面)。从手机端到SLB就是一个端到端的TCP/UDP连接。一般服务器会部署SLB多节点集群,可以通过DNS解析到不同的SLB节点,如果想实操阿里云可以参阅这篇阿里云高可用架构之“CDN+WAF+SLB+ECS”。
SLB收到数据后经由交换机到达应用服务器,下图是两个物理机连接到交换机的结构图。
上图中br0是虚拟网桥,虚拟网桥类似于虚拟交换机,如果一个网络包到达br0,如果没有openvswitch对网络包的控制,那么数据就相当如广播到整台物理机的所有租户里面,显然这是不安全。openvswitch实现了SDN(软件定义网络)。对于租户在控制台的设置都可以通过openvswitch来实现。
上图br1作用又是什么了,br0是控制虚拟机网络的网桥,而br1是控制物理机网络的网桥,通过两个网桥来控制原因是将虚拟机网络和物理机网络隔离控制。br1之间的连接一般都是通过覆盖网络解决,因为如果通过VLAN ID实现满足不了需求,VLAND ID是12位,最大容量是4096,物理机器的通信和隔离可以通过“隧道通信”机制实现,典型的实现方案是VXLAN。VXLAN协议原理可以参考这篇文章 vxlan 协议原理简介
基于虚拟机的云中网络很复杂,中间包含各种软件层次的网络包传递,比如虚拟网卡。后来一种更轻量级的云简化了整体结构,比如Kubernetes。Kubernetes 作为容器编排主流的技术,这里面内容太负责,下面主要是看看容器之间通信,将整篇文字理顺。容器网络实现方式有多种,我们看看应用广泛的Flannel容器网络。 Flannel容器网络有如下两个特点:
- 每个node上的docker容器分配相互不冲突的IP地址。
- 节点之间建立一个覆盖网络。
Flannel容器覆盖网络和虚拟机容器覆盖网络很相似,原理就是隧道机制。Flannel支持UDP,VXLAN,HOST-GW等数据路由方式。假设我们采用VXLAN工作模式,其工作原理如下图所示。
上图容器1和容器2是同一个deployment位于同一个node上的两个POD之间通信,那实际上是同一个subnet间通信,此时经过 docker0网桥即可。如果是同一个deployment的位于两个node上的两个POD之间通信,比如容器1和容器2,因为他们是位于不同的物理机,当节点2启动并加入flannel网络后,节点1的flannel就会加入一条到到目标flannel设备的路由规则,规则包括节点2 flannel ip地址对应的mac地址,是否还记得上面提得到ARP?但是这里不是通过ARP实现的,而且直接在节点启动时候设置到其他节点上。有了目的mac地址还不行,因为mac地址不具备路由功能,所以节点1里面还有目的mac地址对应的宿主机ip地址。这个是由宿主机的flanneld进程维护的。上图名称flannel.1中1的作用是识别数据帧是不是该由自己处理,只有VNI值相同才能相互通讯,默认从1开始。所以一个包从172.1.15.2向发往172.1.16.3,包先被路由到docker0网桥,然后数据包被路由到flannel.1的VTEP设备(VXLAN Tunnel End Point,虚拟隧道端点),也就是隧道的一端。我们要到隧道的另外一端172.1.16.1,而这个目的VTEP设备的信息对应的mac地址在一个表中已经有了,下一步就是要知道这个mac地址对应的主机的ip地址,这个信息节点2在加入flannel时候也有了,也即是我们有了下面两个路由很重要的信息。
- 目标主机的IP地址
- 目标VTEP设备的MAC和IP地址。
把这些信息封装成如下帧结构通过eth0网卡发出去,接收方按照相反方式解包就达到目的地了。
通过VXLAN实现虚拟机之间的通讯还是蛮复杂的,后来出来一个容器网络的龙头老大,Calico项目。flannel通过宿主机的flanneld来维护路由信息,Calico是通过第二节中所说的BGP协议来维护路由信息,其原理图如下图所示:
其图中的BGP负责维护网络图谱,它将网络路由信息直接插入到宿主机的iptable路由表里面,节点里面没有网桥设备,容器和宿主机之间直接通过Veth Pair这个虚拟网卡通讯,Veth Pair一端插在容器里面,一端插在宿主机里面。
参考内容
- 书籍《网路是怎么连接的》,这本书非常好,将计算机网络介绍得通俗易懂。
- 极客时间里面的《趣谈网络协议》,刘超老师说的非常好,值得学习。