什么是计算机网络呢?有人说:计算机网络就是百度!还有人说:计算机网络就是 HTTP!如果你也是这么想的,那么希望本篇你能仔细看下去。
那么,计算机网络到底是什么呢?
客观地说,计算机网络就是:一些相互连接的、自治的计算机的集合。
说人话就是:全世界所有通过互联网互相连接的计算机所组成的一个大网。
如果你的电脑联网了,那就是计算机网络的一部分,如果你的电脑没联网,那就不是计算机网络的一部分。
计算机网络简介
计算机网络可以分为:广域网、城域网、局域网和个人区域网。
-
广域网:覆盖几十到几千公里,比如我们日常说的上网,用百度、谷歌等,用的就是广域网。
-
城域网:一般覆盖一个城市,大约 5 到 50 公里,比如有的城市的宽带。
-
局域网:一般覆盖一公里左右,比如一个公司的内网。
-
个人区域网:覆盖范围只有几米,比如自己给自己开热点。
有人说,这些我都不在乎,我就在乎它快不快,看视频卡不卡,这就要提到网络的质量参数了。
判断一个网络的质量有 7 个性能指标。
-
速率:这个很好理解,就是传输速度。直接受到材料的影响,比如,相同条件下有线网肯定比无线网快;而同是有线网络,光纤就比电缆快。
-
带宽:可以简单理解为传输的横切面大小。我们把计算机网络的传输看作是一个水管,速率就是传输的速度,带宽就是水管横切面大小,越大传输得肯定越快。
-
吞吐量:单位时间内通过网络的数据量。这才是真正的传输速度,如果水管生锈了,那么速率是 100,也达不到 100,因为水锈会阻碍一部分传输,而吞吐量就是把所有因素都考虑进去之后,得出的最终的传输速度。
-
时延:把数据从 A 端发送到 B 端所需要的时间。
-
时延带宽积:发送数据时的延迟 x 带宽得出的结果。
-
往返时间:从 A 发数据到 B,到 A 收到 B 的响应所需要的时间。
-
利用率:网络在传输时信道的利用百分比。
我们把传输信号的介质统一称为:信道。
我们知道,网络传输的其实就是信号,或者说是电磁波,电磁波可以直接在空气中传播,可以在电缆中传播,也可以在光纤中传播。所以,空气、电缆、光纤等,都是信道。
我们传输的数据,也就是电磁波,从微观看就是一个一个的电子,电子在金属中的传输速度大于在空气中的传输速度,并且空气中有其他信号干扰,所以有线网就比 Wi-Fi 快。
那么,这些电子就这样传送,它传送的什么玩意儿呢?
只要我们制定一个规则,然后大家都按照这个规则来传递,就都明白了。
这个规则就叫做:计算机网络协议。
计算机网络协议
计算机网络协议是指:为计算机网络中进行数据交换而建立的规则、标准或约定的集合。
比如,我们前面说过的 HTTP,也是计算机网络协议中的一部分,属于应用层协议。
应用层?还分层吗?
分,就像我们的代码也分内核层、Native 层、Framework 层、应用层一样,网络协议也是分层的。
分层就是为了简便,各司其职,分工明确,容易维护。
我们一般把网络协议分为 5 层:物理层、数据链路层、网络层、传输层、应用层。当然,也有其他分法,我们这里就不叨叨了。
这里要理清一个流程,比如 A 向 B 发数据,数据是从 A 的应用层开始,沿着传输层、网络层、链路层、物理层向下,再依次到达 B 的物理层、链路层、网络层、传输层,最后到达 B 的应用层。
就像 A 给 B 送信,A 住在 5 楼,B 住在对面的 5 楼,A 先要下到 1 楼,然后再从 B 住的楼上去,才能送达。
物理层
物理层规定传输媒介的一些特性。
比如说,我们上面说到的电缆和光纤都是传输媒介,当然还有其他的,比如无线信道、同轴电缆等。物理层指的不是这些实实在在的物理上的传输媒介,而是规定它们的协议。那么,这些传输媒介有什么共同点呢?可以分为四个点。
-
机械特性:指明接线器的形状和尺寸、引脚数目、排列方式等。
-
电气特性:指明每条线上的电压范围。
-
功能特性:指明每条线上每一个电平表示的意义。
-
过程特性:指明各种事件的出现顺序。比如,哪根信号线先动,哪根信号线先出现什么电平等。
换句话说,物理层协议将具体的实实在在的传输媒介进行了高度总结,抽象成了四个特点,只要满足这四个特点,就可以作为传输媒介。这样一来,我们不必去死记硬背到底哪些属于传输媒介,而只需要理解这四个特性,只要满足这四个特性的,就可以作为传输媒介。这就是化记忆为理解的手段之一:找共性并总结抽象。
这跟面向对象中的将具体事物提炼成抽象类有异曲同工之妙。
一般来说,我们的信道有三种。
-
单向通信:也叫单工通信,只能从一端到另一端通信,而不能反过来。比如我们收听的收音机广播。
-
双向交替通信:也叫半双工通信,双端都可以收发,但不能同时进行。
-
双向同时通信:也叫全双工通信,双方可以同时发送和接收。
现在,我们有了物理层,可以传输电子信号了,那么电子信号是怎么被识别成数据呢?又怎么保证可靠地到达另一端呢?
数据链路层
数据链路层负责将数据透明地、无差错地进行传输。
我们知道,传输媒介传输的是信号,或者说就是电子,那这怎么可能看得懂呢?很简单,我们传输的电子都会通过电路展示为高低电平,我们用高电平表示 1,低电平表示 0,这样就被翻译成了一串串二进制,而数据链路层就负责把这些二进制封装成帧,你可以将帧理解为一串二进制。
那么,在数据链路层我们的传输单位就从电子信号变成了帧。那么,要怎么保证这些帧正常地到达呢?这就是数据链路层的核心作用了。
首先,链路层会把我们的数据封装成一个一个的数据帧,说白了就是在数据前后添加标记,我们称之为首部和尾部,这样,当接收端收到数据后,就能首部和尾部的标记,来识别数据的开头和结束。
当然,我们传输数据的时候,不会感知到首部和尾部的存在,这是链路层自己添加的,在发送的时候自己添加,在接受的时候自己删除,这对我们来说是透明的。
其次,链路层会对发送的数据进行差错检测,比如最常见的循环冗余检测,如果遇到错误的数据帧,就会丢掉并重传,以此来提供可靠的、无差错的数据传输。
所以,链路层的功能就是:将数据封装成帧,进行透明地、无差错地传递。
那么,我们怎么知道要传输到哪里呢?数据怎么知道对方的地址呢?
网络层
网络层负责编址、寻址和转发。
网络层的核心功能就是寻址,也就是寻找地址。
我们知道,我们发送数据都需要一个 IP 地址。IP 就是一个协议,全称叫做 Internet Protoco,叫做国际互联协议,它可以表示一个地址,比如常见的 192.168.xxx.xx,叫做 IP 地址。
那么,这样的地址怎么能找到对方的主机呢?
我们可以通过地址映射协议,也叫做 ARP(Address Resolution Protocol) 来根据 IP 地址找到对应的主机地址,从而将数据塞给这个主机,或者从这个主机上拿数据。
主机地址是不变的,IP 地址是随着网络变化的。
比如:我的主机叫大灰狼,我连了我家的 wifi123,那么,我连了网,就会自动给我的主机分配一个 IP 地址,假如地址是 192.168.1.1,那么,隔壁喜羊羊就可以顺着这个地址来找到我的主机大灰狼,从而给我发消息;如果我换了另一个网络,我的主机又被重新分配一个新 IP 地址,那么,喜羊羊用老的 IP 地址就找不到我了。
这也就是我们连接不同的网络时,执行ipconfig查看到的 IP 地址不同的原因。
IP 地址有了,我们可以买个域名,比如 https://www.yyds.nb,然后将这个域名绑定到自己的 IP 地址,那么别人访问这个域名的时,就会访问到你的 IP 地址,这个过程叫做域名映射,跟上面的地址映射是异曲同工的。
所以,网络层的两个核心协议就是 IP 和 ARP,IP 协议负责给不同的主机分配 IP 地址,ARP 协议负责根据 IP 地址找到实际的主机地址。
那么,地址知道了,我们怎么传输数据呢?直接硬塞吗?非也!
传输层
传输层的核心作用就是进行数据的传输。
上面说到,我们通过网络层拿到了具体的主机地址,那么,数据传给谁呢?直接塞到系统的 C 盘吗?
肯定不会,应该是传给这个主机中的一个进程,这个进程收到数据后就进行数据的解析处理。那么,数据是怎么被进程获取到呢?
通过端口号!
每个主机上都有不同的端口号,我们可以开启一个进程去监听这个端口,只要监听到这个端口有数据,我们就立刻处理,这样就拿到了数据。
也就是说,数据传输不仅仅需要 IP 地址,还需要有端口号,比如常见的192.168.0.1:8080,其中 8080 就是端口号。最常见的就是 Socket 了。
数据传输有两种传输方式,一种是面向连接的,可靠的 TCP 协议;另一种就是无连接的,不可靠的 UDP 协议。
-
TCP(Transmission Control Protocol):面向连接的,可靠的。
-
UDP(User Datagram Protocol):无连接的,不可靠的。
照这么说,UDP 不就没啥用了吗,不可靠要你干啥?
存在就是道理!
TCP 虽然可靠,但是每次通信都要先建立连接,完事之后还要断开连接,就像打电话一样。
累不累?这个美其名曰三次握手四次挥手。因为提前都互相问了对方能听到吗,所以是可靠的。但是我们也看到了,太费劲!
A:喂,能听到么? B:嗯,可以,And you? A:me too,好,现在我们开始吹。 1 个小时之后要结束了。 A:今天就到这里吧。 B:好的,就到这里吧。 A:好的,挂了。 B:好的,我也挂了。
如果是 UDP 呢?就是如下这样。
A:大风车啊咿呀咿呀转,xxxxxxxx,收到回复! B:嗯!
就这么简单,就跟发短信似的,但是可能 B 没收到,所以不可靠。
所以,我们可以总结一下:TCP 需要连接,费事,但是可靠;UDP 不需要连接,省事,但是不可靠。
应用层
应用层直接给用户提供傻瓜式的服务。
这一层最常见的就是 HTTP 了,也叫做超文本传输协议(Hyper Text Transfer Protocol),大部分都是用来传递 JSON,也可以传递 HTML 页面,所以叫做超文本。
HTTP 是基于 TCP 的,所以也是需要连接的。
一次完整的 HTTP 请求如下,比如我们请求https://xxx.cn。
-
通过域名映射找到https://xxx.cn对应的 IP 地址。
-
通过这个 IP 地址加上默认端口 80 进行 TCP 连接。
-
发送数据请求。
-
等待对方主机返回响应结果,也就是一个 HTML 页面。
-
得到结果,放在浏览器里面进行渲染,我们就看到了具体的页面。
-
关闭 TCP 连接。
关于 HTTP 就不做过多讲解,应用层还有很多其他协议,比如文件传输协议 FTP、电子邮件协议 SMTP。
凡是 P 结尾的,大部分都是协议或原则。
比如,单一职责原则 SRP、开放闭合原则 OCP 等。
其实对于我们来说,我们真正需要了解的就两层,一个是应用层,一个是传输层。
应用层的 HTTP、传输层的 TCP,这俩协议是用得最多的,基本上霸占了“皇后”和“贵妃”的位置,而 HTTP 又是基于 TCP 实现的,那我们就追其根溯其源,剥其衣脱其裳,来彻底了解下这位面试官最喜欢打听的协议吧。
TCP 可靠传输的实现原理
之前我们说过:TCP 的传输是可靠的。那么,这个“可靠”是啥意思呢?其实它包含两个点:
-
传输的数据没有差错;
-
传输的数据不会丢失。
有人说,没有差错肯定就不会丢失啊,第 1 点包含了第 2 点啊。
未必。
我们举个例子,我一秒给你发 100 条信息,并且全发到了,这是没有差错;但是你来不及看完,也就是有漏看,这就是丢失了。
所以,我们可以定义得更精简一点:传输的数据没有差错并且全部被处理。
这怎么可能呢?网络信号肯定有不好的时候,比如隔壁有人下载东西下载得太猛把网下欠费了又不去缴费,那这不就丢失了?你怎么保证可靠?
可以的,我们只需要保证两点:
-
传输出错的数据进行重传;
-
控制传输速度,让接收方来得及处理。
这样,即使原来不可靠,也会变得可靠了。
要实现这两点,我们要了解以下几个协议。
超时重传协议
发送方在发送数据之后的规定时间内,如果没有收到接收方的确认,则重新传送数据。
现在假设 A 向 B 发消息,如果网络正常,就是 A 向 B 发送消息,B 正常收到。
但是如果网络异常呢?A 向 B 发送之后,A 怎么知道 B 是否收到了呢?
这就需要 B 在收到 A 的消息后,向 A 发送一个确认信号,A 收到了 B 的确认信号,才认为 B 正确地收到了 A 发送的消息。
所以,如果网络正常,那么流程就变为:
-
A 向 B 发消息,并等待 B 的确认;
-
B 收到 A 的消息,发送确认消息给 A;
-
A 收到 B 的确认消息,认为 B 正常收到了,继续发送下一个消息。
那么,如果 A 迟迟没有收到 B 的确认消息呢?比如,双方约定了一个时间 T,A 在发送消息后,经过时间 T 还没有收到 B 的确认消息,那么 A 还继续等吗?
不等了!此时 A 就认为 B 没有收到自己发送的消息,于是就重新发送一次,直到收到 B 的确认消息为止。
这就叫做超时重传。那么这个时间又是多少呢?
我们假设,A 发送消息到 B 需要的时间为 t1,B 处理消息需要时间为 t2,B 发送确认消息到 A 需要的时间为 t3,那么这个超时时间 T 肯定要大于 t1+t2+t3。而且考虑到通用性,我们应该计算多次 t1+t2+t3,并且取它们的平均值,而 T 就要大于这个平均值。
当然,有人说了,你这也不准啊,网络可能有时候很差,有时候很好,你这怎么决定呢?
所以,我们要动态计算这个超时时间,我们在上一篇文章讲过自旋锁,自旋锁可以根据具体情形进化为自适应自旋锁。这里也是一样的道理,我们可以根据当前情形动态计算超时时间 T,比如:根据最近的 20 次传输时间取平均值。
其实,核心就是动态计算,而不是固定死的一个值。
当然,超时重传这个操作 TCP 已经帮我们做好了,我们在 API 层是无感知的,无脑调用 API 就行。所以也叫做自动重传递协议,术语就是 ARQ(Automatic Repeat reQuest)。
ARQ 保证我们的数据是无差错地传输到另一端,那么,怎么保证另一端能来得及处理呢?
这就要说到停止等待协议。
停止等待协议
发送方会调整发送速度,来等待接收方处理完之后,再进行发送。
其实,我们上文已经说过了,A 发送一条消息之后,直到收到 B 的确认消息,才会继续发送下一条消息,就好像是 A 等着 B 似的,这就叫停止等待协议。这样一来,B 总是在自己处理完一条消息之后才发送确认信号给 A,A 总是在 B 处理完一条消息之后才发送下一条消息给 B,说白了就像一条隧道,A 开过去,B 再开过来,A 再开过去,如此循环。
等等,这不对劲啊!我们的 TCP 是全双工啊,你这样活脱脱地把它完成了单工通信了,这岂不是白白浪费资源吗?
比如 B 在发送确认消息到 A 的过程中,A 就干等着?A 在发送消息给 B 的过程中,B 也干等着?如果网络不好,A 发送消息发了 10 分钟还没到,B 就等 10 分钟?
我们可以这么干:我们直接在发送端 A 和接收端 B 都开一个缓冲区,A 每次发送的消息都放在自己的缓冲区中,B 每次收到的消息也先放在自己的缓冲区中,A 只要缓冲区不满就继续发消息,直到缓冲区满为止;B 就循环从缓冲区取出消息处理,处理完就发送确认信息给 A,同时继续处理下一条消息;A 收到 B 的确认信号就从缓冲区移除对应的消息,移除之后缓冲区就不满了,就继续发送下一条消息。如此循环,直到消息全部发送完毕为止。
这不正是生产者消费者模式吗,没错!
如此一来,我们的信道可能一直处于忙碌状态,大大提高了信道的利用率。假如,此刻,A 正在给 B 发送第 10 条消息,而 B 正好给 A 回复第 3 条消息的确认信息,此时信道中就同时出现 A 给 B 发、B 也给 A 发的情景,这正是全双工的表现。
那么,这样的话,就不是停止等待了,那这怎么保证对方来得及处理呢?
不,这仍然是停止等待,只不过等待的不是一条消息了,而是一堆消息了。换句话说,没有缓冲区的停止等待,就像有缓冲区但是缓冲区的大小是 1 的情况。有缓冲区的停止等待,等待的是什么呢?等待的是缓冲区不满,所以,当缓冲区满了,照样等待。
但是如果没有缓冲区的停止等待,如果消息错了,我就重新发送,对方再重新处理就行了,而有了缓冲区后,如果中间一条消息出错了怎么办呢?后面的是不是全乱了?
对!后面的肯定都乱了,所以,后面的全部重新传送一遍即可!
比如,缓冲区大小是 5,A 发送了 12345,B 收到了 1245,没收到 3,那么,A 迟迟收不到 B 对 3 的确认信号,A 就会将 345 全部重新发送一遍,直到收到 3 的确认为止。
那如果 B 没收到 3,直接对 4 进行了处理,然后给 A 发送了 4 的确认呢?A 会认为这个确认无效,因为没跟上一个确认连上,所以不做处理。
有人又说了,你这如果中间丢了一个消息,就要把后面全部传送一遍,这效率肯定低了啊。
对,效率肯定低了,但是你想啊,我们的网络大部分都是好的,只有很少一部分时间是异常的,所以,这个选择肯定是利大于弊的。如果真的网络差,那也没办法,效率低是低了,但是消息还是正确的。总而言之,我们权衡利弊之后,还是选择带有缓冲区的停止等待协议。
其实,这个就叫做连续 ARQ 协议,大大提高了信道利用率,提高了传输效率。
连续 ARQ 协议
我们上面说了,使用加缓冲区的停止等待协议,就是连续 ARQ 协议,这其实就是采用流水线方式进行连续发送,提高信道利用率,而不用依次等待上一条消息的确认,毕竟异常的情况是少数,我们就默认是正常的,如果发现错误,再从错误的地方重新发送即可。
其实,连续 ARQ 协议是采用滑动窗口实现的,我们的缓冲区就等于一个窗口,如下所示:
我们假设缓冲区的大小是 5,那么这个窗口的宽度也就是 5,我们把沿着滑动方向的两条边分别称为前沿和后沿,前沿后沿的差值就是缓冲区的大小;刚开始时,TCP 会将窗口内的消息全部发送,当收到一个确认信号时,窗口会整体向前移动,新进入窗口的消息就会被发送。
比如,图中所示,如果收到了消息 1 的确认,那么后沿就向前移动一个单位,如下:
此时,6 进入了窗口,6 就会被发送。如果此时收到了 2 的确认信号,窗口就继续向右边移动,直到全部确认为止。
窗口内的数据都是有缓存的,如果收不到确认,就会把窗口内的数据重新发送一遍。比如,如果超时没有收到 2 的确认,那么就会把窗口内的 23456 全部发送一遍(反正有缓存);如果收到了 2 的确认,那么窗口整体右移,2 就被移出窗口,因为 2 不需要发送了。
这个过程就像窗口,所以也叫做滑动窗口。换句话说,连续 ARQ 协议是基于滑动窗口的思想实现的。
好,到这里,TCP 的数据传输我们都了解了。那么,TCP 是怎么建立连接的呢?
三次握手与四次挥手
TCP 是怎么建立连接的呢?
TCP 的连接采用 CS 模式,也叫做客户端-服务器模式,主动发起连接的叫客户端(C 端),被动等待连接的叫做服务端(S 端)。
刚开始客户端和服务端都处于关闭状态,当需要建立连接时,客户端会主动打开连接,然后请求服务器建立连接,这中间有三次握手的操作,如下。
-
客户端:服务器吗,我需要建立连接(第一次握手)。
-
服务器:我知道了(第二次握手)。
-
客户端:那还不赶紧打开(第三次握手)?
-
服务器打开连接(建立连接)。
那么,为什么要三次握手呢?首先,不握手肯定是不行的,一次握手也是不行的,因为都不能保证对方收到了自己的请求,要保证对方收到了自己的请求,至少要两次握手,一次是请求,一次是对请求的确认。
我们知道,TCP 是全双工的,如果只有两次握手,那么结果就是客户端发送、服务器确认,这只能保证服务器能接收到客户端的消息,而不能保证客户端能接收到服务器的消息,这是不可靠的。所以我们需要三次握手,直到第三次客户端的应答过来了,才能保证客户端确确实实收到了服务器的确认信号,也就是保证了双方都能互相接收到对方的消息,也就保证了可靠性。
还有一点,就是可以避免浪费资源。比如:第一次握手时由于网络问题,发出的信号迟迟没有到达服务器,于是,由于超时重传,客户端再次发送一个连接信号,此时,网络中有两个连接信号了;如果这时候网络突然好了,那么服务器就会一下收到两个连接信号,如果是两次握手,服务器此时就直接打开了两个连接。这两个连接中必定有一个是无用的,白白浪费资源。
如果是三次握手呢?服务器就不会打开连接,而是发送两个确认信号给客户端,客户端收到第一个确认信号时,就告诉服务器打开连接(第三次握手),收到第二个确认信号时,由于已经收到过相同的信号了,就认为是重复的,就直接丢掉。这样服务器就只会收到一个第三次握手的信号,只会建立一个连接,从而避免浪费资源。
有人说,你这不就是让客户端判断是否有重复信号呗?你放在服务器不是也可以吗?
不可以!你要知道,服务器是面向多个客户端的,如果要判断是否有重复信号,那就要保存每一个客户端的请求信息,如果有 10 亿个客户端,这要保存多少信息呢?这个代价太高了,不如分摊到客户端,让客户端自己记录,这样代价低一些。所以,服务器适合做广播,而不太适合做单播。
所以,我们可以归纳三次握手的两个点:保证可靠,避免浪费资源。
那么,TCP 是怎么断开连接呢?
流程大致如下:
-
客户端:我要断开了。
-
服务器:好的。
-
服务器:我也要断开了。
-
客户端:好的。
能不能干净利落点直接断了,就跟断开网线一样?
这肯定不行啊,如果直接断了,这到底是正常断开呢,还是网络异常了呢?所以我们需要通知对方,这就需要两次握手。而我们又知道,TCP 是全双工的,客户端到服务器断了就表示客户端不再向服务器发送消息了,但是,服务器依然可以向客户端发消息啊,因为你是全双工啊。所以,如果要彻底断开,还需要服务器发起一次握手,客户端进行一次应答,这样才能让这个全双工连接彻底断开。这就一共需要四次挥手。
有人说,那你连接的时候为啥三次就行?因为握手的时候,第二次握手的过程同时包含了服务器的应答和服务器的发送,等价于做了两件事,比如:
A:你能听到吗?
B:我能听到。(这句话说明 B 不但收到了 A 的消息,还表明了 B 能发送消息。)
A:好的。(这句话证明了 B 发消息成功,还证明了 A 能收到 B 的消息。)
UDP 协议
UDP 是无连接的、不可靠的,所以没什么可以扯的太多的东西,这里就不废话了。