【计算机网络】笔记

这里写目录标题


塞发生
有的小伙伴分不清 IP(网络层) 和 MAC (数据链路层)之间的区别和关系。

其实很容易区分,在上面我们知道 IP 的作用是主机之间通信用的,而 MAC 的作用则是实现「直连」的两个设备之间通信,而 IP 则负责在「没有直连」的两个网络之间进行通信传输。

第一章、浏览器委托数据

虽然我们经常使用浏览器来访问web服务器,但实际上浏览器还可以用来在FTP服务器上上传下载文件、也可以当一个电子邮件客户端,所以它能够根据不同的URL来判断应该使用哪种功能。比如访问web服务器时用http:,访问FTP服务器时使用ftp:。

第一步、解析URL

输入URL之后,浏览器首先要对URL进行解析,然后才能生成发送给Web服务器的请求消息。比如一个http请求,http://www.baidu.com/dir1/file.html就是请求这个域名下的dir1目录下的file.html文件。
如果是http://www.baidu.com/dir1/这样末尾是一个[/],代表后面的文件被省略了,一般这种情况下我们会提前在服务器上设置好默认访问的文件名,比如index.html这样。

第二步、生成请求消息

解析过后,我们就需要生成访问web服务器的请求消息了。简单来说请求消息包括两个部分,对哪个文件干什么事儿,哪个文件已经确定了,我们对文件的操作有get、post、put、delete等等,最常用的是get和post,一般我们访问web服务器获取网页数据时就是get方法,在表单上填上数据再发给web服务器就是post方法。然后具体来说,请求消息的内部第一行是请求行,请求行里包括了请求的方法、请求的路径、http版本号。第二行叫做消息头,这里面放着一些请求的附加信息,比如日期、语言、编码格式等等。第三行是空行。第四行开始就是消息体了,这里面就是需要发送的数据。对于GET方法来说,只用请求行和消息头web服务器就已经知道要做什么操作了,POST方法时表单填的信息就在消息体里。

第三步、用DNS查IP地址

生成了HTTP消息之后,我们就可以委托操作系统来把消息发送给对应的WEB服务器了。但这就来了个问题,我只知道服务器的域名,IP我不知道啊。那这时就需要DNS服务器了。
DNS是域名服务系统。我们的计算机上一般都是带着DNS解析器的,它包含在操作系统的Socket库中,这个库是一个用于调用网络功能的程序组件集合。当我们的浏览器调用DNS解析器时,解析器生成一条[告诉我www.baidu.com的IP地址]这种消息,因为解析器不具备使用网络收发数据的功能,所以它会把这条信息委托给操作系统的协议栈来执行,协议栈是操作系统内部的网络控制软件,协议栈就会通过网卡把消息发送给最近的DNS服务器,DNS服务器的IP地址已经是提前设置好的,不用再去查询了,DNS服务器查到之后就会返回具体的IP地址。
那互联网里有成千上万的DNS服务器,我们需要准确找到要访问的WEB服务器的信息是归哪一台DNS服务器管。DNS服务器上有一张表保存着域名和IP地址的对应关系。对于一个域名来说,它是分为多个域的,越右边代表它的层级越高,比如www.baidu.com,可以说是com部baidu科的www这样,管理下级域的DNS服务器的IP地址会注册到上层域的DNS服务器里,查的时候就通过上级DNS服务器一层层查到下级DNS服务器这样。除此之外,互联网里还有一个根域,它里面保存着com、jp等DNS服务器的信息,互联网里所有DNS服务器里都保存着根域服务器的信息。所以只要客户端找到任意一台DNS服务器,就能通过它找到根DNS服务器,然后再顺藤摸瓜找到下层的DNS服务器了。然后DNS服务器的话还有一个缓存功能,里面记着之前查询过的域名,有就直接返回,没有再去查根域DNS服务器。

第四步、委托协议栈发送信息

好了,现在完事具备了,就差发送信息了。数据收发的话主要是四步,我们需要委托操作系统内部的协议栈来向这个IP地址发送数据。简单来说,就是调用Socket库里的组件,在两台机器之间建立一条数据通道,一端收一端发。建立管道的关键就在于管道两端的出入口,也叫做套接字,我们需要先创建套接字,然后再把套接字连接起来形成管道,收发数据完成后断开管道并删除套接字。

第二章、用电信号传输TCP/IP 数据

第一步:创捷套接字

客户端需要调用Socket库中的socket程序组件,创建完成之后协议栈会返回一个描述符,应用程序把这个描述符放到内存中,这个描述符的作用是用来识别不同的套接字的,因为计算机可能会同时访问多个不同的web服务器,这就会有多个不同的套接字。一般来说服务器一方的应用程序在启动时就会创建好套接字然后进入等待连接的状态,而客户端则是在需要时再创建。

第二步:连接阶段

连接阶段就是委托协议栈把客户端创建的套接字和服务器的套接字连接起来,应用程序会调用Socket库里的connect组件来完成这个工作,调用connect时需要传三个参数:描述符、IP地址、端口号,这个端口号就是用来确定连接对象的套接字,浏览器访问web服务器一般默认都是80,这时已经规定好的我们不用去管。这个时候客户端已经知道自己应该连接哪个套接字了,连接成功后,协议栈会把对方的IP地址和端口号保存在套接字里面,然后开始传输数据。
客户端会生成一个SYN=1的TCP包发送给服务器,这个TCP包的头部还会包含客户端向服务器传数据时使用的初始序号,还要服务器向客户端发送数据时需要用的窗口大小。服务器接到包之后,会返回一个SYN=1的TCP包,这个包的头部也包含了序号和窗口大小,另外还有一个表示确认已经收到包的ACK号。客户端收到包之后,回再返回一个包含确认ACK号的TCP包。连接阶段结束,可以进行数据收发了。

第三步:传输阶段

客户端传输数据会调用用Socket库里的write组件,当服务器接收到消息之后会返回响应,客户端需要调用Socket库的里read程序组件来接收。
传输开始时,TCP会把请求消息切分成一定大小的块,然后在每一块前面加上TCP头部,然后发给服务器,服务器收到后会返回ACK号。

第四步:断开阶段

收发阶段结束之后,web服务器会调用Socket库里的close组件来先发起断开过程,服务器会发送一个FIN=1的TCP包,然后客户端返回一个表示确认收到的ACK号。然后双方再交换一组方向相反的FIN=1的TCP包和包含ACK号的TCP包,再等待一段时间,套接字被删除,整个过程结束。

IP和以太网包的收发

发送包
包是由头部和数据两部分组成的,对于TCP/IP包来说,它有两个头部,分别是MAC头部和IP头部。IP头部包含根据IP地址把包发往目的地所需的控制信息。MAC头部包含通过以太网的局域网把包传输到最近的路由器所需的控制信息。包的目的地IP地址就写在IP头部里,转发模块会根据这个IP地址在路由表中进行查询,去找下一个路由的位置。然后IP协议会委托以太网协议把包传输过去,IP协议会查找下一个路由的MAC地址,但这个时候我们其实是不知道具体的MAC地址是多少的,这就需要ARP地址解析协议了,它首先会先查一下自己的ARP缓存有没有,如果没有就广播所有设备[这个IP地址是谁的,把你的MAC地址告诉我],得到回复后把得到的MAC地址写到MAC头部中,然后以太网协议把包发到对应路由器上。比如封好的包会以0和1组成的数字信息交给网络硬件网卡,网卡把这些数字信息转换为电信号或者光信号,然后再通过网线发送出去,到达集线器或者路由器这些转发设备之后,再被转发出去一步步直到接收方。
接收返回包
当服务器返回网络包后,MAC模块会把信号转换成数字信息,然后检查包的MAC头部里的MAC地址和自己的MAC一致不,不一致就扔了,一致就留下然后通知计算机收到了一个包,网卡驱动会把它交给TCP/IP协议栈来处理,IP模块会先检查包的IP头部确认格式是否正确,如果格式OK就开始看接收方的IP地址和客户端网卡的地址一致不,一致就接收,不一致就通过ICMP消息告诉发送方。那么接收的时候可能还会有个分片重组,因为传输时一般都是把大包分割成小包传输的,那接收时就需要重组成大包。那这个时候MAC模块和IP模块的工作都已经结束了,接下来包就给了TCP模块,TCP模块会根据IP头部里接收方和发送方的IP地址,以及TCP头部中的接收方和发送方端口号来查找对应的套接字。找到套接字之后就执行包里的具体内容。
所以,IP(路由器)负责把包送到通信对象这一整个过程,而其中把包传输到下一个路由器的过程是由以太网(交换机)来负责的。

OSI 的七层模型

OSI是Open System Interconnect,也就是开放式系统互联,它是国际标准化组织ISO在80年代发布的一个网络互连模型。它把通信协议中必要的部分分成了 7 层,由下到上分别为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。它们的每一层向下使用服务,向上提供服务。不同节点的对等层按照协议来实现它们之间的通信。OSI七层模型最大的优点就是把服务、接口还有协议这三个概念明确的区分开了,通过模型可以让不同系统的网络之间实现可靠的数据传输.

1、物理层
物理层是模型的最底层,它的主要功能是利用传输介质为数据链路层提供物理连接传输比特流。
此时传输的数据是比特流。

2、数据链路层
数据链路层是OSI模型的第二层,它负责建立和管理节点间的链路。比如从物理层过来的0和1没有意义,必须要指定解读的方式。还有就是把从网络层接到的数据分割成能被物理层传输的帧。也就是提供可靠的通过物理介质传输数据的方法。
此时传输的数据是帧。

3、网络层
网络层是OSI模型的第三层,它的主要功能是建立、维护网络的连接,把数据从源端经过若干个中间节点安全传送到目的端。
数据链路层的目的是解决同一网络内节点之间的通信,而网络层主要解决不同子网间的通信。
大白话来说网络层其实就是建立了主机到主机的通信。
此时传输的数据是包。

4、传输层
传输层是OSI模型的第4层,模型下3层的任务是数据通信,上3层的任务是数据处理。传输层主要起承上启下的作用,它向用户提供可靠的端到端的差错和流量控制,保证报文的正确传输。
我的理解就是我们现在已经有了MAC地址和IP地址,但是还需要一个参数来表示这个数据包到底是这个节点的哪个进程在使用,那这个参数其实就是端口了,那有了这个端口,我们就能实现端到端通信了。
此时传输的数据是tcp报文段 、udp用户数据报。

5、会话层
会话层是OSI模型的第5层,它的主要功能是组织协调两个会话进程之间的通信 。经过下面几层,我们已经能够建立主机端到端的通信了,但是还需要明确什么时候来建立连接、建立后要保持多久的连接、什么时候断开连接这些问题,那会话层其实就是做了这个工作。

6、表示层
表示层是OSI模型的第六层,它的主要功能是充当应用程序和网络之间的翻译角色,它要处理用户信息的表示问题,如数据的编码、数据格式转换、加密解密等问题。

7、应用层
应用层是OSI参考模型的最高层,它是用户和网络的直接接口,它会提供各种应用层协议来让用户能够与网络进行交互式联系。也就是为应用软件提供接口,让应用程序能够使用网络服务。

TCP/IP模型

TCP/IP模型分为4层结构,从下到上分别依次是网络接口层、网络层、传输层、应用层、
对于同一台设备上的进程来说,它们之间通信有很多种方式,比如有管道、消息队列、共享内存等等,而对于不同设备上的进程间通信,就需要网络通信,因为设备是多样性的有很多种,为了要兼容各种设备上的进程进行通信,就协商出了一套通用的网络协议。这个网络协议是分层的,每一层都有各自的作用和职责。
应用层
首先最上层是我们能直接接触到的就是应用层,我们电脑或手机使用的各种软件和app都是在应用层实现的。它专注于为我们用户来提供应用功能,比如 HTTP、FTP、Telnet等。
应用层是不会去关心数据是如何传输的,当两个不同设备的应用需要通信的时候,应用就把数据传给下一层的传输层。就类似我们寄快递的时候,只需要把包裹交给快递员,由他负责运输快递,我们不需要关心快递是如何被运输的。
传输层
传输层是为应用层提供网络支持的,它会接收来自用户层的数据包。
在传输层会有两个比较重要的传输协议,分别是 TCP 和 UDP。
TCP 的全称叫传输控制协议,大部分应用使用的正是 TCP 传输层协议,比如 HTTP协议。TCP 相比 UDP 有很多特性,比如流量控制、拥塞控制、超时重传等,这些都是为了保证数据包能可靠地传输给对方。
而UDP 相对来说就简单一些,它只负责发送数据包,而不保证数据包是否成功能抵达接收方。它的实时性和传输效率比较高,但不能保证可靠性。
网络层
传输层主要是用来服务应用的,也就是实现应用到应用间的通信,而实际的传输功能是由网络层来负责的。网络层里面使用比较多的是 IP 协议,IP 协议会把传输层的报文加上 IP 包头组装成 IP 报文,然后进行路由寻址发往这个包的目的地。
网络接口层
网络接口层主要工作在网卡这个层次,它负责在以太网、WiFi 这样的底层网络上发送原始数据包,使用 MAC 地址来标识网络上的设备。网络接口层会在数据包的最前面,也就是IP 头部之前加上 MAC 头部,并封装成数据帧发送到网络上。

HTTP 是什么?

HTTP 超文本传输协议,也就是HyperText Transfer Protocol。
关于超文本传输协议,可以这么来看,首先它是一个在计算机世界里两点之间传输数据的约定和规范。只不过它传输的是超文本数据,那所谓超文本数据指的是它是文字、图片、视频等的混合体,最关键有超链接,能从一个超文本跳转到另外一个超文本。所以概括性地来讲,HTTP超文本传输协议是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。

HTTP状态码

http状态码可以分为五类:
1xx 类状态码是临时性响应,它属于提示信息,实际用到的比较少。
2xx 类状态码表示服务器成功处理了客户端的请求。

比如常见的[200 OK」表示一切正常。
[204 No Content」它和200 OK 基本相同,但响应头里面没有 body 数据。

3xx 类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,其实通常就是重定向。

比如「301」永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次来访问,浏览器会自动重定向到新的 URL。
「302」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。
「304」表示资源没有被改过,客户端可以继续使用缓存资源。

4xx 类状态码表示客户端发送的报文有误,服务器无法处理,这就已经是错误码了。

比如「403 Forbidden」表示服务器禁止访问这个资源,并不是客户端的请求出错。
还要最常见的「404 Not Found」表示请求的资源在服务器上不存在或未找到,所以服务器给不了我们。

5xx 类状态码表示客户端请求的报文没问题,但是服务器处理时内部发生了错误,属于服务器端的错误码。

「502 Bad Gateway」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
「503 Service Unavailable」表示服务器当前很忙,暂时无法响应客户端,类似“网络服务正忙,请稍后重试”的意思。

HTTP 常见字段

host字段,客户端发送请求时,用host来指定服务器的域名。
Content-Type 字段,用于服务器响应时告诉客户端,本次响应的数据是什么格式。
Content-Length 字段,服务器返回数据时,用 Content-Length 来表明本次响应的数据长度。

GET 和 POST 有什么区别?

GET 和 POST 其实都是 HTTP 的请求方法。

get请求一般用来请求获取资源,这个资源可以是静态文本、页面、图片视频等等。
post请求一般是根据请求对指定资源做出处理,比如提交数据、创建数据。

get请求的参数会显示在地址栏,安全性低,而且浏览器可能也会对参数长度进行限制
post请求则是将传递的参数放在请求体里面,不会在地址栏显示,安全性比get请求高,参数没有长度限制。

get请求刷新服务器或者回退没有影响
post请求回退时会重新提交数据请求。

GET 和 POST 方法都是安全和幂等的吗?

在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。
所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。

那GET 方法是安全而且幂等的,因为它是「只读」操作,无论读多少次,服务器上的数据都是安全而且不会变的。
POST 的语义是根据请求对指定的资源做出处理。POST 不安全,不幂等。

而且从另外一个角度讲,get请求的参数会显示在地址栏上,这的确可能会导致安全性低,但是HTTP 传输的内容都是明文的,虽然在浏览器地址拦看不到 POST 提交的 body 数据,但是只要抓个包就都能看到了。
所以,要避免传输过程中数据被窃取,就要使用 HTTPS 协议,这样所有 HTTP 的数据都会被加密传输。

HTTP 缓存

HTTP 缓存有两种实现方式,分别是强制缓存和协商缓存。

强缓存指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器这边。
强缓存是利用Cache-Control和Expires这两个 HTTP 响应头部字段实现的,Cache-Control 的优先级高于 Expires。
当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 Cache-Control并设置过期时间大小;
浏览器第二次访问这个资源时,会先通过请求资源的时间和 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;服务器再次收到请求后,会再次更新 Response 头部的 Cache-Control。

协商缓存一般是在没有命中强制缓存的时候,才会进行协商缓存。我们有些请求的响应码是 304,这个就是服务器告诉浏览器可以使用本地缓存的资源,这种其实就是协商缓存。
协商缓存由 Last-Modified / IfModified-Since, Etag /If-None-Match实现,每次请求需要让服务器判断一下资源是否更新过,从而决定浏览器是否使用缓存,如果是,则返回 304,否则重新完整响应。

HTTP 优缺点

HTTP 最突出的优点是「简单、灵活和易于扩展、应用广泛和跨平台」。HTTP 基本的报文格式是 header + body,头部信息也是 key-value 的形式,易于理解。
HTTP 协议里有两个重要的特性分别是无状态和明文传输。
无状态
无状态的好处就是服务器不会去存 HTTP 的状态,所以不需要额外的资源来记录状态信息,这能减轻服务器的负担。但缺点是,由于服务器没有记忆能力,那它在完成有关联性的操作时会比较麻烦。
比如电商登录、下单、结算这系列操作都需要知道用户的身份,但服务器不知道这些请求是有关联的,每次都要验证身份,那这肯定不行。对于无状态的问题,简单的解决方法就是 Cookie。

明文传输
明文意味着在传输过程中的信息比较方便阅读,这就为我们调试带了极大的便利性。缺点就是信息都暴露了。在传输的过程中,信息可能会被窃取。

HTTP 性能

HTTP的性能主要是看连接、请求和应答的这个过程。
早期 HTTP/1.0版本时有一个很大的问题,就是每发起一个请求,都要新建一次 TCP 连接,这会增加许多开销。
在1.1 版本里新增了长连接的通信方式,只要任意一端没有明确提出断开连接,就保持 TCP 连接状态。这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。
还有就是管道网络传输。然后因为1.1 版本采用了长连接的方式,也改进了请求的模式,早先的请求的方式是在同一个 TCP 连接里面,发送了 A 请求,只有收到服务器的会应后才能再发出 B 请求。那现在是可以连续发多个请求,不用再请求阻塞了,可以减少整体的响应时间。但也有个问题就是服务器的响应问题,如果服务面器在处理前面的请求时耗时比较长,那么后面请求的处理都会被阻塞住。

HTTP 与 HTTPS

HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。
HTTPS 在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。

HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。
而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。

HTTP 默认端口号是 80,HTTPS 默认端口号是 443。

HTTPS 协议需要向证书权威机构申请数字证书,来保证服务器的身份是可信的

对称加密与非对称加密

对称加密:加密和解密使用相同的秘钥。使用简单,速度快,但双方使用相同的钥匙,安全性得不到保证。

非对称加密:需要两个秘钥,公钥和私钥。公钥可以任意分发而私钥保密,比较安全但速度慢。

HTTPS

HTTP 由于是明文传输,所以安全上存在三个风险:
1、窃听风险,比如通信链路上可以获取通信内容,用户信息泄漏。
2、篡改风险,比如改变网站,强制植入垃圾广告。
3、冒充风险,比如冒充支付网站,骗取用户钱财。
HTTPS主要使用三个方法来解决安全问题。
1、信息混合加密
HTTPS 的混合加密指的是对称加密和非对称加密结合方式,主要是为了保证信息的机密性,它会在通信建立前采用非对称加密的方式来交换「会话秘钥」。之后的通信过程中全部使用对称加密的「会话秘钥」方式来加密明文数据。
2、摘要算法 + 数字签名
为了保证传输的内容不被篡改,计算机会用摘要算法来计算出内容的唯一哈希值,然后同内容一起传输给对方。对方收到后,也会对内容计算出哈希值,只有这两个哈希值相同才说明内容没有被篡改。
通过哈希算法可以确保内容不会被篡改,但是缺少对客户端收到的消息是否来源于服务端的证明。那为了避免这种情况,计算机里会用非对称加密算法来解决,私钥由服务端保管,然后服务端会向客户端颁发对应的公钥。如果客户端收到的信息,能被公钥解密,就说明该消息是由服务器发送的。
3、数字证书
数字证书指的是服务器把自己的公钥注册到数字证书认证机构CA里,CA用自己的私钥为服务器的公钥数字签名然后颁发数字证书。客户端拿到服务器的数字证书之后用CA的公钥检查这个数字证书的真实性。如果确认无误,就从数字证书里拿出来服务器的公钥,用公钥对报文加密然后发出去,最后服务器用自己的私钥来解密信息。

HTTPS连接过程

那么在进行 HTTP 通信前,需要先进行 TLS 握手
1、首先,由客户端向服务器发起加密通信请求,也就是 ClientHello 请求。
客户端会向服务器发送自己支持的 TLS 协议版本、自己生成的随机数和支持的密码套件列表,比如RSA 加密算法。
2、服务器收到客户端请求后,会向客户端发出响应,也就是 SeverHello。服务器回应的内容有确认 TLS 协议版本、服务器生产的随机数、确认的密码套件列表,比如 RSA 加密算法、服务器的数字证书。
3、客户端收到服务器的回应之后,首先通过浏览器的 CA 公钥,确认服务器的数字证书的真实性。然后客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,并且向服务器发送一个随机数、
并告诉服务器以后的信息都将用「会话秘钥」加密通信。客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端校验。
服务器和客户端有了这三个随机数,接着就用双方协商的加密算法,各自生成本次通信的「会话秘钥」。
4、服务器收到客户端的第三个随机数之后,通过协商的加密算法,计算出本次通信的「会话秘钥」。
然后向客户端发送加密通信算法改变通知,表示随后的信息都将用「会话秘钥」加密通信。
服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端校验。至此,整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。

HTTPS连接过程与一定可靠吗

可能会有这么一种情况,客户端通过浏览器向服务端发起 HTTPS 请求时,被「假基站」转发到了一个「中间服务器」,于是客户端是和「中间服务器」完成了 TLS 握手,然后这个「中间服务器」再与真正的服务端完成 TLS 握手。
从客户端的角度看,其实并不知道网络中存在中间服务器这个角色。那么中间服务器就可以解开浏览器发起的 HTTPS 请求里的数据,也可以解开服务端响应给浏览器的 HTTPS 响应数据。
但这有个前提,就是用户点击接受了中间人服务器的证书。
因为中间服务器与客户端的 TLS 握手过程中,会发送了自己伪造的证书给浏览器,而这个伪造的证书是能被浏览器识别出是非法的,于是就会提醒用户该证书存在问题。

HTTP演进版本

HTTP/1.1 与 HTTP/1.0

1.1版本使用长连接的方式改善了 1.0 短连接造成的性能开销。
1.1版本支持管道网络传输,也就是只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。

但是服务器是按请求的顺序响应的,如果服务器响应慢,会导致客户端一直请求不到数据,也就是队头阻塞;
1.1请求和响应头部发送时不会压缩,这就导致头部的信息越来越多,然后延迟增大。
发出多个请求时,即使header相似,也还是会发送,造成的浪费较多;
请求只能从客户端开始,服务器只能被动响应。

HTTP/2 与 HTTP/1.1

头部压缩
HTTP/2 会压缩Header,同时发出多个请求,如果它们的Header是相似的,那么协议会自动消除重复的部分。

二进制格式
HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,这增加了数据传输的效率。

并发传输
在 HTTP/1.1 里的同一个连接中,HTTP 只有完成一个请求响应,才能处理下一个请求响应,这就容易造成队头阻塞的问题。
而 HTTP/2 引出了 Stream 概念, 1 个 TCP 连接会包含多个 Stream,每个Stream 里可以包含多个Message,Message 对应 HTTP/1 中的请求或响应。不同的 HTTP 请求对应的 Stream ID 不一样,接收端可以通过 Stream ID 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream,所以 HTTP/2 可以并行交错地发送请求和响应。

服务器推送
HTTP/2 里服务端不再是被动地响应,可以主动向客户端发送消息。

http2缺陷

HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。

http3

HTTP/2 队头阻塞的问题是因为 TCP,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!
HTTP/3 主要是基于 UDP 协议在「应用层」实现了 QUIC 协议,它具有类似 TCP 的连接管理、拥塞窗口、流量控制的网络特性,相当于将不可靠传输的 UDP 协议变成“可靠”的了,所以不用担心数据包丢失的问题。

HTTP优化

1、缓存技术
客户端把第一次请求以及响应的数据保存在本地磁盘上,其中将请求的 URL 作为 key,而响应作为 value,两者形成映射关系。
2、压缩技术
通常 HTTP 的响应的数据会比较大,我们可以考虑对响应的资源进行压缩,这样就可以减少响应的数据大小,从而提高网络传输的效率。

HTTPS优化

升级CPU
升级TLS

既然有 HTTP 协议,为什么还要有 RPC?

从发展历史来说,RPC 其实比 HTTP 出现的要早,所以应该是既然已经有了RPC,为什么还要有HTTP,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,对外一般用 HTTP 协议,而内部集群的微服务之间则采用 RPC 协议进行通讯。
最开始时 TCP传输的数据是个没有边界的数据流,因为收发的这些二进制串之间没有边界,所以就会出现粘包问题。于是就有了各种协议来定义消息格式,那HTTP 和各类 RPC 协议就是在 TCP 之上定义的应用层协议。RPC 本质上不算是协议,而像是一种调用方式。目的是希望我们能像调用本地方法那样去调用远端的服务方法。RPC 有很多种实现方式,不一定非得基于 TCP 协议。

既然有 HTTP 协议,为什么还要有 WebSocket?

我们都知道最常用的 HTTP/1.1版本,虽然是基于 TCP 的协议,但它是半双工的。而websocket协议本质上也是一个基于tcp的协议,它实现了浏览器和服务器的全双工通信。
例如这样一种场景:扫码登录公众号平台,前端网页是根本不知道用户扫没扫,它会搁一两秒就去向后端服务器询问,这其实也就是定时轮询,这样有个问题就是请求太多了,消耗带宽,增加服务器的负担。
一种方法是长轮询,比如我们把HTTP 请求的超时时间设置大一点,比如 30 秒,在这 30 秒内只要服务器收到了扫码请求,就立马返回给客户端网页。如果超时,那就立马发起下一次请求。那这样就减少了 HTTP 请求的个数。
但是例如网页游戏这种,客户端和服务端之间需要频繁交互的复杂场景,砍我一刀999这种游戏,那用http的半双工方式轮询占的带宽也就太大了,所以就出来了websocket协议,轻松解决服务器频繁主动推送数据到客户端的场景。

TCP

TCP 是一个面向连接的、可靠的、基于字节流的传输层通信协议。
因为IP 层是不可靠的,它并不保证网络包数据的安全交付,所以如果需要保障网络数据包的可靠性,那么就需要由上层传输层的 TCP 协议来负责。TCP能保证接收端接收的网络包是无损按序的。

UDP

也就是用户数据报协议,它在IP的数据报服务之上增加了复用和分用以及差错检测功能。

TCP 与 UDP 区别

TCP 和 UDP 区别:

1.连接
TCP 是面向连接的传输层协议,传输数据前先要建立连接。
UDP 是不需要连接,即刻传输数据。
2. 交互个数
TCP 是一对一通信。
UDP 支持一对一、一对多、多对多的交互通信
3. 可靠性
TCP 有拥塞控制和流量控制机制,数据可以无差错、不丢失、不重复、按序到达。
UDP 是尽最大努力交付,不保证可靠交付数据。
4. 首部开销
TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
UDP 首部只有固定不变的 8 个字节,开销较小。
5. 传输方式
TCP 是流式传输,面向字节流。
UDP 是一个包一个包的发送,面向报文。
6.适用场景
TCP适用于需要可靠传输的场景,比如文件传输。
UDP适用于对可靠性要求不高的实时应用,比如视频会议,直播等。

TCP 和 UDP 可以使用同一个端口吗

可以。
传输层的端口号作用,主要是为了区分同一个主机上不同应用程序的数据包。
当主机收到数据包后,会在 IP 包头的「协议号」字段分辨这个数据包是 TCP的还是UDP的,然后确定送给哪个模块(TCP/UDP)处理,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理。
TCP 和 UDP在内核中是两个完全独立的软件模块,所以它们 各自的端口号也相互独立,如 TCP 有一个 80 号端口,UDP 也可以有一个 80 号端口,二者并不冲突。

TCP三次握手

TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过三次握手来进行的。
在这里插入图片描述
一开始,客户端和服务端都处于 CLOSE 状态。先是服务端主动监听某个端口,处于 LISTEN 状态
在这里插入图片描述
客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的「序号」字段中,同时把 SYN 标志位置为 1,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 SYN-SENT 状态。
在这里插入图片描述
服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYN 和 ACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。
在这里插入图片描述

客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次「确认应答号」字段填入 server_isn + 1 ,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于 ESTABLISHED 状态。

服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态。

从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的,这也是面试常问的题。

一旦完成三次握手,双方都处于 ESTABLISHED 状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了。

为什么是三次握手?不是两次或四次?

主要来说,三次握手主要是为了保证通信双方具有接收和发送的能力,并且三次握手也是防止旧的重复连接初始化造成混乱的主要措施。
第一次握手:客户端发送网络包,服务端收到了,这样服务端就知道客户端的发送能力、服务端自己的接收能力是正常的。
第二次握手:服务端发包,客户端收到了,这样客户端就知道服务端和客户端的接收、发送能力都是正常的。但此时服务端还不能确定客户端的接收能力是否正常。
第三次握手:客户端发包,服务端收到了。 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
因此,需要三次握手才能确认双方的接收与发送能力是否正常。
如果是用两次握手,可能会出现历史连接问题:

比如客户端发出连接请求,但这个请求报文因为网络结点原因滞留,服务器长时间没有收到,所以客户端再传了一次连接请求,这次正常服务器收到了然后返回确认,这样建立了连接。数据传输完毕后,释放连接。如果这时第一个经过滞留的请求到达了服务器,那服务端就会误以为客户端又发出了一个新的连接请求,于是就向客户端发出确认报文段,同意建立连接。客户端并没有想要建立连接就会忽略服务端发来的确认,而服务端要等待客户端发送数据,浪费资源。而采用三次握手就可以解决这个连接的问题,减少不必要的资源浪费。

第一次握手丢失

当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到 SYN_SENT 状态。
如果第一次握手丢失,客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「超时重传」机制,重传序列号一样的 SYN 报文。Linux里可以控制重传的次数,时间的话每次超时的时间是上一次的 2 倍,如果到了最大重传次数还是没有服务器的回应,客户端就断开连接。

第二次握手丢失

当服务端收到客户端的第一次握手后,就会回 SYN-ACK 报文给客户端,这个就是第二次握手,此时服务端会进入 SYN_RCVD 状态。
第二次握手其实有两个目的 :

报文里的 ACK是对第一次握手的确认;
报文里的 SYN,是服务端发起建立 TCP 连接的表示;

所以如果第二次握手丢失,客户端等不到第二次握手的确认报文,它就会觉得自己的 SYN 报文丢失了,就会触发超时重传机制,重传 SYN 报文。

对于服务端来说,因为第二次握手里包含服务端的 SYN 报文,它等不到客户端返回的ACK报文,所以服务端这边也会触发超时重传机制,重传 SYN-ACK 报文。

第三次握手丢失

第三次握手指的是客户端收到服务端的 SYN-ACK 报文后,回给服务端一个 ACK 报文,然后客户端进入到 ESTABLISH 状态。
如果第三次握手丢失了,服务端那边迟迟收不到来自客户端的确认报文,它就会触发超时重传机制,重传 SYN-ACK 报文,直到它收到了第三次握手,或者达到最大重传次数。

为什么每次TCP连接的初始序列号要不同

主要是为了避免TCP接收到历史报文
比如有一种情况,客户端和服务端建立了一个 TCP 连接,在客户端发送数据包A的时候被网络阻塞了,然后超时重传了这个数据包,但这时服务端设备断电重启,之前和客户端建立的连接已经消失了,于是在收到客户端的数据包的时候就会发送 RST 报文。
然后,当客户端又与服务端建立另一个TCP连接时,数据包A经过网络阻塞后到达了服务器,因为这个数据包的序列号正好是在服务端的接收窗口内,所以这个数据包会被服务端正常接收,这就会造成数据错乱。
所以如果每次建立连接客户端和服务端的初始化序列号都「不一样」,就有大概率因为历史报文的序列号「不在」对方接收窗口,从而很大程度上避免了历史报文。

初始序列号 ISN 是如何产生的?

初始序列号基于时钟计时器递增的,主要是基于两部分生成的:ISN = M + F。

M 是一个计时器,这个计时器每隔 4 微秒加 1。

F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。

什么是 SYN 攻击

TCP 连接建立是需要三次握手的,假设有攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,都无法得到 ACK 应答,长此以往就会占满服务端的半连接队列,这样后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。

避免 SYN 攻击方式,可以有以下四种方法:
调大 netdev_max_backlog;
增大 TCP 半连接队列;
开启 tcp_syncookies;
减少 SYN+ACK 重传次数

TCP四次挥手

在这里插入图片描述
客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的FIN报文,然后客户端进入 FIN_WAIT_1 状态。
服务端收到FIN报文后,会向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
等待服务端处理完数据后,也会向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。

注意是主动关闭连接的,才有 TIME_WAIT 状态
每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。

为什么挥手需要四次?

关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
服务端收到客户端的 FIN 报文时,先回一个 ACK 应答报文表示我收到了你的FIN报文,但服务端现在可能还有数据需要处理和发送,等服务端不再发送数据时,就也会发送 FIN 报文给客户端来表示现在OK,我可以现在关闭连接了。
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,因此是需要四次挥手。

第一次挥手丢失

当客户端调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。

正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2状态。

如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。

当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会在等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到第二次挥手,那么直接进入到 close 状态。

第二次挥手丢失

当服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的连接进入到 CLOSE_WAIT 状态。

如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。如果还是没能收到服务端的第二次挥手(ACK 报文),那么客户端就会断开连接。

第三次挥手丢失

当服务端收到客户端的 FIN 报文后,内核会自动回复 ACK,同时连接处于 CLOSE_WAIT 状态。

此时,内核是没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。

服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。

如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。如果还是没能收到客户端的第四次挥手(ACK报文),那么服务端就会断开连接。客户端处于 FIN_WAIT_2 状态是有时长限制的,如果 tcp_fin_timeout 时间内还是没能收到服务端的第三次挥手(FIN 报文),那么客户端就会断开连接。

第四次挥手丢失

当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态。

在 Linux 系统,TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。

当服务端没有收到 ACK 报文前,还是处于 LAST_ACK 状态。

如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。如果还是没能收到客户端的第四次挥手(ACK 报文),那么服务端就会断开连接。客户端在收到第三次挥手后,就会进入 TIME_WAIT 状态,开启时长为 2MSL 的定时器,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接。

为什么需要 TIME_WAIT 状态?

主动发起关闭连接的一方,才会有 TIME-WAIT 状态。

需要 TIME-WAIT 状态,主要是两个原因:
四元组:在TCP协议中,如何去确定一个客户端连接的组成要素。包括:源IP地址,目标IP地址,源端口号,目标端口号。

防止历史连接中的数据,被后面相同四元组的连接错误的接收;
比如服务端有一个在关闭连接之前发送的 报文A被网络延迟了。
当服务端以相同的四元组重新建立新连接后,被延迟的报文A这时抵达了客户端,而且这个报文的序列号刚好在客户端接收窗口内,那么客户端会正常接收这个数据报文,但其实这个数据报文是上一个连接残留下来的,这样就产生数据错乱的问题。
为了防止历史连接中的数据,被后面相同四元组的连接错误的接收,因此 TCP 设计了 TIME_WAIT 状态,状态会持续 2MSL 时长,这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。

为什么 TIME_WAIT 等待的时间是 2MSL?

首先MSL 是 Maximum Segment Lifetime,报文最大生存时间,超过这个时间报文将被丢弃。TCP 报文基于是 IP 协议的,IP 头里有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,TTL每经过一个路由器就减 1,当TTL减为 0 时数据报就会被丢弃,同时发送 ICMP 报文通知源主机。
所以 MSL 至少要 >= TTL 消耗到 0 的时间,以此确保报文已经自然消亡了。
TTL 的值一般是 64,Linux 把 MSL 设置为 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。
所以TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后,接收方又会向对方发送响应,所以一来一回需要等待 2 倍的时间。
比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 FIN 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。

服务器出现大量 TIME_WAIT 状态的原因

首先如果服务器出现大量的 TIME_WAIT 状态的 TCP 连接,就是说明服务器主动断开了很多 TCP 连接。这可能是因为HTTP 没有在请求header里使用Keep-Alive长连接或者HTTP 长连接的请求数量达到上限。如果没有使用长连接,那么每次传数据的时候都要三次握手建立连接,传数据,然后四次挥手断开连接。

服务器出现大量 CLOSE_WAIT 状态的原因有哪些?

CLOSE_WAIT 状态是「被动关闭方」才会有的状态,而且如果「被动关闭方」没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。

所以,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。

如果已经建立了连接,但是客户端突然出现故障了怎么办?

客户端出现故障指的是客户端的主机发生了宕机,或者断电的场景。发生这种情况的时候,如果服务端一直不会发送数据给客户端,那么服务端是永远无法感知到客户端宕机这个事件的,也就是服务端的 TCP 连接将一直处于 ESTABLISH 状态,占用着系统资源。

为了避免这种情况,TCP 搞了个保活机制。这个机制的原理是这样的:

定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序来取消连接。

如果已经建立了连接,但是服务端的进程崩溃会发生什么?

TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。

重传

在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。但是网络太复杂,很容易会出现数据包丢失的情况,那这种情况下TCP会用重传机制。常见的重传机制有超时重传、快速重传、SACK、D-SACK。

超时重传

超时重传指的是在发送数据时,设定一个定时器,当超过指定的时间还没有收到对方的 ACK 确认应答报文,就会重发该数据。这个时间就是RTO(Retransmission Timeout 超时重传时间)。超时重传时间 RTO 的值应该略大于报文往返 RTT 的值,因为如果超时时间 RTO 较大时,重发就慢,丢了老半天才重发,没有效率,性能差;
当超时时间 RTO 较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。

快速重传

快速重传不以时间为驱动,而是以数据驱动重传。
例如有这样一种情况,发送方连续发出了 1,2,3,4,5 份数据,
接收方收到了第一个数据,所以 Ack 回 2;
但 数据2丢失了一直送不到,当Seq3 到达后,发送方还是 Ack 回 2,代表我现在需要第二个数据,我还没有收到;
当后面的 数据4 和 数据5 都到之后,接收方依旧 Ack 回 2,因为 Seq2 还是没有收到;
而这时发送端累计已经收到了三个 Ack = 2 的确认,它就知道了噢 数据2 还没有成功送到,就会在定时器过期之前,重传丢失的 Seq2。
最后当接收方收到了 Seq2后,此时之前的 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。
所以,快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。
但快速重传有个问题,假设发送方发了 6 个数据,编号的顺序是 Seq1 ~ Seq6 ,其中Seq2、Seq3 都丢失了,那么接收方在收到 Seq4、Seq5、Seq6 时,也都会发送 ACK2 给发送方,代表我又收到了你的三个数据,但我需要的是第二个数据,我还没有收到。当发送方收到这三个ACK2时,它知道了第二个数据还没有成功送达,但是它并不清楚这三个 ACK2 是接收方收到哪几个报文而回复的,是345?还是456。所以它就要做一个抉择,是只重传 Seq2 一个报文,还是重传 Seq2 之后已发送的所有报文?如果只选择重传 数据2 这一个报文,那么因为Seq3 也丢失了,后面收到三个重复的 ACK3 才能再触发重传。
如果选择重传 Seq2 之后已发送的所有报文,虽然能同时重传已丢失的 Seq2 和 Seq3 报文,但是 Seq4、Seq5、Seq6 的报文是已经被接收过了,对于重传 Seq4 ~Seq6 折部分数据相当于做了一次无用功,浪费资源。所以就由此创造了另一个重传机制:SACK( Selective Acknowledgment), 选择性确认。

SACK( Selective Acknowledgment), 选择性确认。

这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将接收方已收到的数据的信息发送给「发送方」,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。

滑动窗口

首先为什么要引入窗口,我们都知道 TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。这个模式就有点像两个人聊天,你说了一句我才能说下一句,所以数据包的往返时间越长,通信的效率就越低。
为解决这个问题,TCP 引入了窗口的概念。所谓窗口就是指无需等待确认应答,而可以继续发送数据的最大值。
窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。
假设窗口大小为 3 个 TCP 段,那么发送方就可以「连续发送」 3 个 TCP 段,并且中途若有 ACK 丢失,可以通过「下一个确认应答进行确认」。例如发送方连续发了6个报文,但 ACK 5 确认应答报文丢失了,只要发送方收到了 ACK 6 确认应答,就意味着 6 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答。
TCP 头里有一个字段叫 Window,也就是窗口大小。
这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。
所以,通常窗口的大小是由接收方的窗口大小来决定的。
接收窗口的大小是约等于发送窗口的大小的。
因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。

流量控制

流量控制指的是发送方在发送数据量时,要考虑接收方处理能力。如果发送的太多接收方处理不过来就会导致触发重发机制,从而导致网络流量的无端的浪费。
为了解决这种现象发生,TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。

拥塞控制

流量控制是避免「发送方」的数据填满「接收方」的缓存,但是并不知道网络的中发生了什么。
在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 如果再重传数据,就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大…

所以当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据量。拥塞控制也就是避免「发送方」的数据填满整个网络。
为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念。
拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。
发送窗口就是拥塞窗口和接收窗口中的最小值。

拥塞窗口 cwnd 变化的规则:

只要网络中没有出现拥塞,cwnd 就会增大;
但网络中出现了拥塞,cwnd 就减少;

拥塞控制主要是四个算法:

慢启动
拥塞避免
拥塞发生
快速恢复

首先慢启动发包的个数是指数性的增长。也就是收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个。
当收到 2 个的 ACK 确认应答后, cwnd 增加 2,于是就可以比之前多发2 个,所以这一次能够发送 4 个。这样指数增长。一般来说 ssthresh 的大小是 65535 字节
当 cwnd < 慢启动门限ssthresh 时,使用慢启动算法。
当 cwnd >= 慢启动门限ssthresh 时,就会使用「拥塞避免算法」。

当拥塞窗口 cwnd 「超过」慢启动门限 ssthresh 就会进入拥塞避免算法。
那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1。
接上前面的慢启动的栗子,现假定 ssthresh 为 8:
当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长。
所以,我们可以发现,拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶段,但是增长速度缓慢了一些。
就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。
当触发了重传机制,也就进入了「拥塞发生算法」

拥塞发生:当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:

超时重传
快速重传

当发生了「超时重传」,则就会使用拥塞发生算法。
这个时候,ssthresh 和 cwnd 的值会发生变化:
ssthresh 设为 cwnd/2,
cwnd 重置为 1 (是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)
接着,就重新开始慢启动,慢启动是会突然减少数据流的。这真是一旦「超时重传」,马上回到解放前。但是这种方式太激进了,反应也很强烈,会造成网络卡顿。

发生快速重传的拥塞发生算法
还有更好的方式,前面我们讲过「快速重传算法」。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。
TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:
cwnd = cwnd/2 ,也就是设置为原来的一半;
ssthresh = cwnd;
进入快速恢复算法

快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。

正如前面所说,进入快速恢复之前,cwnd 和 ssthresh 已被更新了:
拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
重传丢失的数据包;
如果再收到重复的 ACK,那么 cwnd 增加 1;
如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;
在这里插入图片描述
也就是没有像「超时重传」一夜回到解放前,而是还在比较高的值,后续呈线性增长。

TCP 半连接队列和全连接队列

在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:

半连接队列,也称 SYN 队列;
全连接队列,也称 accept 队列;
服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来。
不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回 RST 包。

TCP的缺陷

1、升级TCP比较困难
TCP 协议是诞生在 20世纪70年代的,它一直在不断的实现更多的新特性。
因为TCP 协议是在内核中实现的,所以应用程序只能使用而不能修改,如果要想升级 TCP 协议,那么只能升级内核。而升级内核这个工作比较麻烦,因为内核升级涉及到底层软件和运行库的更新,升级之后我们的服务程序就需要回归测试是否兼容新的内核版本,所以服务器的内核升级也比较保守和缓慢。
2、网络迁移需要重新建立 TCP 连接
基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。那么比如当我们移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立 TCP 连接。
而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络有卡顿,因此连接的迁移成本是很高的。

TCP的优化

可以从三个角度来对TCP进行优化
TCP 三次握手的性能提升;
TCP 四次挥手的性能提升;
TCP 数据传输的性能提升;

三次握手优化

可以分别针对客户端和服务端优化。
三次握手建立连接的首要目的是「同步序列号」。
只有同步了序列号才有可靠传输,TCP 许多特性都依赖于序列号实现,比如流量控制、丢包重传等,这也是三次握手中的报文称为 SYN 的原因,SYN 的全称就叫 Synchronize Sequence Numbers(同步序列号)。
首先它将发送 SYN 包,于是客户端的连接就会处于 SYN_SENT 状态。
客户端在等待服务端回复的 ACK 报文,正常情况下,服务器会在几毫秒内返回 SYN+ACK ,但如果客户端长时间没有收到 SYN+ACK 报文,则会重发 SYN 包,重发的次数由 tcp_syn_retries 参数控制,默认是 5 次:
通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后。没错,每次超时的时间是上一次的 2 倍。
当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就会终止三次握手。
所以,总耗时是 1+2+4+8+16+32=63 秒,大约 1 分钟左右。我们可以根据网络的稳定性和目标服务器的繁忙程度修改 SYN 的重传次数,调整客户端的三次握手时间上限。比如内网中通讯时,就可以适当调低重试次数,尽快把错误暴露给应用程序。

四次挥手的性能提升

TCP 传输数据的性能提升

TCP面向字节流、UDP面向报文

之所以会说 TCP 是面向字节流的协议,UDP 是面向报文的协议,是因为操作系统对 TCP 和 UDP 协议的发送方的机制不同,也就是问题原因在发送方。
当用户消息通过 UDP 协议传输时,操作系统不会对消息进行拆分,在组装好 UDP 头部后就交给网络层来处理,所以发出去的 UDP 报文中的数据部分就是完整的用户消息,也就是每个 UDP 报文就是一个用户消息的边界,这样接收方在接收到 UDP 报文后,读一个 UDP 报文就能读取到完整的用户消息。操作系统在收到 UDP 报文后,会将其插入到队列里,队列里的每一个元素就是一个 UDP 报文,这样当用户调用 recvfrom() 系统调用读数据的时候,就会从队列里取出一个数据,然后从内核里拷贝给用户缓冲区。

当用户消息通过 TCP 协议传输时,消息可能会被操作系统分组成多个的 TCP 报文,也就是一个完整的用户消息被拆分成多个 TCP 报文进行传输。

这时,接收方的程序如果不知道发送方发送的消息的长度,也就是不知道消息的边界时,是无法读出一个有效的用户消息的,因为用户消息被拆分成多个 TCP 报文后,并不能像 UDP 那样,一个 UDP 报文就能代表一个完整的用户消息。因此,我们不能认为一个用户消息对应一个 TCP 报文,正因为这样,所以 TCP 是面向字节流的协议。当两个消息的某个部分内容被分到同一个 TCP 报文时,就是我们常说的 TCP 粘包问题,这时接收方不知道消息的边界的话,是无法读出有效的消息。要解决这个问题,要交给应用程序。

粘包问题

粘包的问题出现是因为不知道一个用户消息的边界在哪,如果知道了边界在哪,接收方就可以通过边界来划分出有效的用户消息。

一般有三种方式分包的方式:

固定长度的消息;比如规定一个消息的长度是 64 个字节,当接收方接满 64 个字节,就认为这个内容是一个完整且有效的消息。
特殊字符作为边界;我们可以在两个用户消息之间插入一个特殊的字符串,这样接收方在接收数据时,读到了这个特殊字符,就把认为已经读完一个完整的消息。如果刚好消息内容里有这个特殊字符,我们要对这个字符转义,避免被接收方当作消息的边界点而解析到无效的数据。
自定义消息结构。我们可以自定义一个消息结构,由包头和数据组成,其中包头包是固定大小的,而且包头里有一个字段来说明紧随其后的数据有多大。

DNS协议

DNS 域名解析

ARP协议

在传输一个 IP 数据报的时候,确定了源 IP 地址和目标 IP 地址后,就会通过主机「路由表」确定 IP 数据包下一跳。然而,网络层的下一层是数据链路层,所以我们还要知道「下一跳」的 MAC 地址。

由于主机的路由表中可以找到下一跳的 IP 地址,所以可以通过 ARP 协议,求得下一跳的 MAC 地址。
主机会通过广播发送 ARP 请求,这个包中包含了想要知道的 MAC 地址的主机 IP 地址。
当同个链路中的所有设备收到 ARP 请求时,会去拆开 ARP 请求包里的内容,如果 ARP 请求包中的目标 IP 地址与自己的 IP 地址一致,那么这个设备就将自己的 MAC 地址塞入 ARP 响应包返回给主机。操作系统通常会把第一次通过 ARP 获取的 MAC 地址缓存起来,以便下次直接从缓存中找到对应 IP 地址的 MAC 地址。

不过,MAC 地址的缓存是有一定期限的,超过这个期限,缓存的内容将被清除。

RARP

RARP 协议正好相反,它是已知 MAC 地址求 IP 地址
例如将打印机服务器等小型嵌入式设备接入到网络时就经常会用得到。

通常这需要架设一台 RARP 服务器,在这个服务器上注册设备的 MAC 地址及其 IP 地址。然后再将这个设备接入到网络,接着:

该设备会发送一条「我的 MAC 地址是XXXX,请告诉我,我的IP地址应该是什么」的请求信息。
RARP 服务器接到这个消息后返回「MAC地址为 XXXX 的设备,IP地址为 XXXX」的信息给这个设备。
最后,设备就根据从 RARP 服务器所收到的应答信息设置自己的 IP 地址。

DHCP

DHCP 动态获取 IP 地址。
DHCP 客户端进程监听的是 68 端口号,DHCP 服务端进程监听的是 67 端口号。

这 4 个步骤:

客户端首先发起 DHCP 发现报文(DHCP DISCOVER) 的 IP 数据报,由于客户端没有 IP 地址,也不知道 DHCP 服务器的地址,所以使用的是 UDP 广播通信,其使用的广播目的地址是 255.255.255.255(端口 67) 并且使用 0.0.0.0(端口 68) 作为源 IP 地址。DHCP 客户端将该 IP 数据报传递给链路层,链路层然后将帧广播到所有的网络中设备。
DHCP 服务器收到 DHCP 发现报文时,用 DHCP 提供报文(DHCP OFFER) 向客户端做出响应。该报文仍然使用 IP 广播地址 255.255.255.255,该报文信息携带服务器提供可租约的 IP 地址、子网掩码、默认网关、DNS 服务器以及 IP 地址租用期。
客户端收到一个或多个服务器的 DHCP 提供报文后,从中选择一个服务器,并向选中的服务器发送 DHCP 请求报文(DHCP REQUEST进行响应,回显配置的参数。
最后,服务端用 DHCP ACK 报文对 DHCP 请求报文进行响应,应答所要求的参数。
如果租约的 DHCP IP 地址快期后,客户端会向服务器发送 DHCP 请求报文:

服务器如果同意继续租用,则用 DHCP ACK 报文进行应答,客户端就会延长租期。
服务器如果不同意继续租用,则用 DHCP NACK 报文,客户端就要停止使用租约的 IP 地址。
可以发现,DHCP 交互中,全程都是使用 UDP 广播通信。
咦,用的是广播,那如果 DHCP 服务器和客户端不是在同一个局域网内,路由器又不会转发广播包,那不是每个网络都要配一个 DHCP 服务器?

所以,为了解决这一问题,就出现了 DHCP 中继代理。有了 DHCP 中继代理以后,对不同网段的 IP 地址分配也可以由一个 DHCP 服务器统一进行管理。

NAT

IPv4 的地址是非常紧缺的,在前面我们也提到可以通过无分类地址来减缓 IPv4 地址耗尽的速度,但是互联网的用户增速是非常惊人的,所以 IPv4 地址依然有被耗尽的危险。

于是,提出了一种网络地址转换 NAT 的方法,再次缓解了 IPv4 地址耗尽的问题。

简单的来说 NAT 就是同个公司、家庭、教室内的主机对外部通信时,把私有 IP 地址转换成公有 IP 地址。
比如有两个客户端 192.168.1.10 和 192.168.1.11 同时与服务器 183.232.231.172 进行通信,并且这两个客户端的本地端口都是 1025。

此时,两个私有 IP 地址都转换 IP 地址为公有地址 120.229.175.121,但是以不同的端口号作为区分。于是,生成一个 NAPT 路由器的转换表,就可以正确地转换地址跟端口的组合,令客户端 A、B 能同时与服务器之间进行通信。

ICMP

ICMP 全称是 Internet Control Message Protocol,也就是互联网控制报文协议
ICMP 主要的功能包括:确认 IP 包是否成功送达目标地址、报告发送过程中 IP 包被废弃的原因和改善网络设置等。

在 IP 通信中如果某个 IP 包因为某种原因未能达到目标地址,那么这个具体的原因将由 ICMP 负责通知。
比如主机 A 向主机 B 发送了数据包,由于某种原因,途中的路由器 2 未能发现主机 B 的存在,这时,路由器 2 就会向主机 A 发送一个 ICMP 目标不可达数据包,说明发往主机 B 的包未能成功。

ICMP 的这种通知消息会使用 IP 进行发送 。

因此,从路由器 2 返回的 ICMP 包会按照往常的路由控制先经过路由器 1 再转发给主机 A 。收到该 ICMP 包的主机 A 则分解 ICMP 的首部和数据域以后得知具体发生问题的原因。

ICMP 大致可以分为两大类:

一类是用于诊断的查询消息,也就是「查询报文类型」
另一类是通知出错原因的错误消息,也就是「差错报文类型」

TCP和ping区别

首先应用使用TCP传输数据的时候,数据要带上TCP头。网络层带上IP头,数据链路层带上 MAC头等一系列操作后。进入网卡的发送队列 ring buffer ,顺着网卡就发出去了。而ping命令进入在网络层会加上ICMP和IP头,在数据链路层加上MAC头,也是顺着网卡发出。因此 本质上ping 跟 普通应用发消息 在程序流程上没太大差别。

为什么拔了网线,127.0.0.1 还是能ping通

ping 是应用层命令,它的功能比较简单,就是尝试发送一个小小的消息到目标机器上,判断目的机器是否可达,其实也就是判断目标机器网络是否能连通。
ping应用的底层,用的是网络层的ICMP协议。
有网的情况下,ping 最后是通过网卡将数据发送出去的。
从应用层到传输层再到网络层。这段路径跟ping外网的时候是几乎是一样的。到了网络层,系统会根据目的IP,在路由表中获取对应的路由信息,而这其中就包含选择哪个网卡把消息发出。

当发现目标IP是外网IP时,会从"真网卡"发出。

当发现目标IP是回环地址时,就会选择本地网卡。
之所以127.0.0.1叫本地回环地址,可以理解为,消息发出到这个地址上的话,就不会出网络,在本机打个转就又回来了。**所以断网,依然能 ping 通 127.0.0.1。

什么是127.0.0.1

这是个 IPV4 地址。

IPV4 地址有 32 位,一个字节有 8 位,共 4 个字节。

其中127 开头的都属于回环地址,也是 IPV4 的特殊地址。
Pv4 的地址是 32 位的,2的32次方,大概是40+亿。地球光人口就76亿了,40亿IP这点量,塞牙缝都不够,实际上IP也确实用完了。

所以就有了IPV6, IPv6 的地址是 128 位的,大概是2的128次方≈10的38次方。据说地球的沙子数量大概是 10的23次方,所以IPV6的IP可以认为用不完。

IPV4以8位一组,每组之间用 . 号隔开。

IPV6就以16位为一组,每组之间用 : 号隔开。如果全是0,那么可以省略不写。

127.0.0.1 和 localhost 以及 0.0.0.0 有区别吗

首先 localhost 就不叫 IP,它是一个域名,就跟 “baidu.com”,是一个形式的东西,只不过默认会把它解析为 127.0.0.1 ,当然这可以在 /etc/hosts 文件下进行修改。

所以默认情况下,使用 localhost 跟使用 127.0.0.1 确实是没区别的。

其次就是 0.0.0.0,执行 ping 0.0.0.0 ,是会失败的,因为它在IPV4中表示的是无效的目标地址。但它还是很有用的,我们启动服务器的时候,一般会 listen 一个 IP 和端口,等待客户端的连接。

如果此时 listen 的是本机的 0.0.0.0 , 那么它表示本机上的所有IPV4地址。也就是如果服务器 listen 的是 0.0.0.0,那么此时用127.0.0.1和本机地址都可以访问到服务。

Cookie和Session

cookie小饼干
它是浏览器保存在本地的一小块数据,一般不超过4KB。cookie 的出现是因为 HTTP 是无状态的一种协议,换句话说,服务器记不住客户端的信息,可能我们每刷新一次网页,就要重新输入一次账号密码进行登录。那这肯定就不太好了,所以cookie 的作用就好比服务器给这台机器贴个标签,然后这个机器再次向服务器再发请求时,服务器就能够通过 cookie 认出这台机器。

session会话
我们开一个浏览器,访问链接做一些操作,然后关闭浏览器,那这整个过程就称之为一个会话。
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器会把客户端信息记录在服务器上。这样客户端的浏览器再次访问服务器时只需要从这个Session中查找这个浏览器的信息就可以了。

保存位置不同:cookie保存在浏览器端,session保存在服务端。
存储容量不同:cookie最多可以存放4kB,session则没有限制。
存储内容不同:cookie只能存储字符串,session存储结构类似于hashtable的结构,可以存放任何类型。
安全级别不同:Cookie 存储在客户端,它的内容容易被窃听或伪造,所以 Cookie 通常用来存储一些不敏感的数据;而 Session 信息存储在服务器端,安全级别会更高一些。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值