一. 应用层
具体如何自定义协议
自定义协议分成两个阶段
1.根据需求明确传输哪些信息
2.约定好信息组织的格式
约定信息的组织格式有很多种方法
1.行文本的方式
一个响应有多行构成

2.通过xml格式来约束请求和响应数据

xml用来网络传输,和浏览器怎么显示无关,html约定浏览器怎么显示
优点可读性好
缺点冗余信息太多了,网络传输中消耗过多的带宽(对于服务器来说,带宽是最贵的)
3.json 当下最流行的网络数据格式组织的方案

优点 可读性也是很好的,消耗的带宽比xml更节省
缺点 还是存在冗余信息
4.protobuf
基于二进制的格式对数据进行压缩,不涉及 json/xml 冗余的信息,带宽消耗的最少,可读性就变差了

性能要求高的场景就需要使用,如果性能要求不高,还是建议使用json
应用层这里,除了自定义协议之外,也有一些大佬现成搞好的协议
FTP 文件传输
SSH 远程操作主机
telent 网络调试工具
...............................
HTTP协议【重点】
当前web开发中最核心的协议,使用网站都会使用http,Spring 围绕着http转
https =》http基础上+安全层(S别是安全层 SSL)
理解"应用层协议"
数据从A到达B到达B点后,TCP/IP解决的顺丰的功能,而两端还要对数据进行加工处理或者使用,所以还需要一层协议,不关心通信细节,关心应用细节
这层协议就叫做应用层协议。而应用是有不同场景的,所以应用层协议是有不同种类的,其中经典协议之一就是HTTP
当我们在浏览器中输入一个网址,此时就会给对应的服务器发送一个HTTP请求,对方服务器收到这个请求之后,经过计算处理,就会返回一个HTTP响应

之前别写的 Java socket 代码是在应用层的
HTTP协议格式
HTTP(全称为"超文本传输协议")是一种应用十分广泛的应用层协议

HTTP诞生于1991,目前已经发展为最主流使用的一种应用层协议
HTTP往往是基于传输层的TCP协议实现的.(HTTP1.0,HTTP1.1,HTTP2.0均为TCP,HTTP3基于 UDP实现)

HTTP是一问一答模式的协议
客户端发一个请求,服务器就会返回一个响应
请求和响应一对一
网络通信中的模型:
多问一答 :上传大文件
一问多答: 下载大文件
多问多答: 远程控制
浏览器打开网页的场景 / 手机app加载数据的场景,典型的一问一答场景,使用HTTP就非常合适
HTTP报文格式
需要搭配一个重要的工具进行学习------抓包工具(能够获取到网络的数据包,详细的格式都解析出来了),抓包工具相当于"代理"
正向代理(代表客户端工作)
反向代理(代表服务器干活)
电脑上所有的网络通信都会先发给这个抓包程序,抓包程序再把数据转发给服务器
wireshark 知名的抓包工具,可以抓很多协议,使用起来门槛较高
fiddler 专门抓http的,功能简单



HTTP协议是一个"文本格式的协议"
请求 响应
-
首行 1. 首行
-
请求头 2. 响应头
-
空行 3. 空行
-
正文 4. 正文

HTTP请求
认识URL(Request)
描述网络上唯一资源的位置
组成:
- 协议名称 (URL不是HTTP专属的概念,可以给各种协议提供支持)
- 要访问的服务器的ip地址或者域名
- 端口号 (区分哪个应用程序)
- 带有层次结构的路径(想要访问的是某个主机某个程序的某个资源)
- 查询字符串 Query String (对要访问的资源补充说明,也是键值对结构,键值之间用&,键和值用=分割)

端口号没写的时候,浏览器会自动添加默认的端口号,取决于协议
http =》80 https =》443
访问远端服务器的端口,不是浏览器自身的客户端端口


现在登录信息不会写到url里
#ch1 区分当前这个页面的哪个部分,现在不常见了(文档类的网站)
关于urlencode
URL中本身就有一些特殊的符号,代表不同的特殊含义
query string 的内容是程序员自定义的,万一里面包含了有特殊含义的字符怎么办
转义操作不仅仅是标点符号,对于中文等其他非英语系的文字,也是需要转义的,只不过很多浏览器为了用户看起来方便,显示的时候显示转义之前的,实际上,抓包中能看到是已经转义的数据


没有进行转义就可能会使这里的请求失败
urlencode 把数据的二进制内容每个字节取出,十六进制表示,前面加上%
- 变为 %2b

认识方法(method)

GET和POST是最常见的请求,从语义上来说有区别,但是实际上,开发的时候不一定会严格按照语义来进行区分
GET
获取html、ccs、js 等操作都是get
常用于获取浏览器上的某个资源,在浏览器直接输入URL,此时浏览器就会发送一个GET请求
另外 HTML中的 link、img、script等标签也会触发GET请求


GET请求特点
1.首行的第一部分是GET
2.URL的Query String 可以为空,也可以不为空
3.header 部分有若干个键值结构
4.body部分为空
实际URL的长度取决于浏览器的实现和HTTP服务器端的实现.在浏览器端,不同的浏览器最⼤长度 是不同的,但是现代浏览器⽀持的⻓度⼀般都很长;在服务器端,⼀般这个长度是可以配置的
POST
登录、上传文件 典型的post
上传文件 =》请求带有正文,正文保存了当前上传数据的内容
如果上传的是图片,图片本身是二进制的,通过特殊的方法进行转码(base64 编码,把二进制转化为文本,其实body也是可以直接填二进制数据的)
多用于提交用户输入的数据服务器(例如登录页面)

POST请求特点
1.首行的第一部分是POST
2.URL 的Query String 一般为空
3.header部分有若干个键值结构
4.body部分一般不为空,body内的数据格式通过header中的Content-Type指定,body的长度有header中的Content-Length指定
GET和POST的区别
面试中提到首先抛出结论
GET和POST没有本质区别,经常能够混用
1.语义上的区别
GET语义是获取, POST语义是提交数据
2.携带数据的方式
GET通过 URL 的查询字符串(Query String) 携带数据
POST通过body 携带数据
3.GET请求通常建议设计成幂等的,POST无要求
请求一定的,得到的响应也是一定的 ------幂等
服务器开发中需要考虑的一个环节,尤其是像支付这样的场景
4.GET设计成幂等了,就可以允许GET请求的结果被缓存
POST由于不要求幂等,经常是不幂等的,就认为不能被缓存
实际上现在SET不幂等的情况很常见,因为现在的互联网产品都讲究"个性化推荐"
网上有待商议的区别

其他方法
PUT DELETE
实现Restful风格的api时候会用到,设计服务器接口的一种习惯
增 POST
删 DELETE
改 PUT
查 GET
其实这四个操作中的任意一个,都可以进行增删改查,完全取决于代码是怎么写的
认识请求"报头"(header)
Header的整体的格式也是"键值对"结构,每个键值对占一行,键和值之间使用的分号分割
键/值都是标准规定的 RFC标准文档
Host
表示服务器主机的地址和端口

URL和Host中,绝大部分这两个的属性也是一致的,但是也有一些特殊场景下是不一致的
比如使用了代理人,即使使用了代理人,也可以通过Host来获取到最原始的目标是什么
HTTP协议中,传输的时候可能会涉及到"加密"(HTTPS),url部分不会被加密,被加密的是header和body
服务器收到请求之后也就可以做一个最终检验,验证url中的内容和header中加密的内容是否一致
Content-Length
表示body中的数据长度,单位是字节
HTTP协议,传输层这里基于TCP实现(版本号<=2.0)所谓的HTTP协议就是把字符串构造成HTTP约定的格式
把这一串字符串写到 tcp socket中,对于TCP来说,一个连接上可以发送多个请求,服务器这边收到数据的时候得区分一下,从哪里到哪里是一个完成的http请求数据
没有body的http请求,读到空行就可以认为结束了,有body的http请求先读首行和header读到空行,解析header中的Content-length,根据这里的值,接下来再读取固定字节的长度
UDP中面向的是数据报,读写的基本单位就是一个UDP数据报
某个应用层协议,基于UDP,一个UDP数据就对应一个完成的应用层数据包
调用一次receive操作,就得到一个明确的UDP的数据包
Content-Type
表示请求的body中的数据格式,提示接收方如何解析body中的数据,HTTP里面能够携带的数据种类是比较多的
HTML(超文本标记语言)
作用 :定义网页的结构和内容。比如标题、段落、图片、按钮等元素
示例:text/html
CSS(层叠样式表)
作用:控制网页的外观和布局,包括颜色、字体、间距等。
**示例:**text/css
JS(JavaScript)
作用 :实现网页的交互与动态行为。比如响应用户点击、获取数据、修改页面内容等。
示例:application/javascript
JSON(JavaScript 对象表示法)
作用 :一种轻量级的数据交换格式,常用于前后端之间传递数据。
示例:application/json
图片
image/png image/jpg

这些都是合法的js语句,只是看起来和学的js不一样,本质语法是同一套
代码混淆(用专门的工具把js做出变换,在代码逻辑不变的情况下,把js代码给打乱)
请求和响应都会用到这两个header
如果有body,并且没有这个两个属性(哪怕只有一个)都认为是非法的/错误的http报文
User-Agent(简称UA)

User-Agent 里面表示了用户使用设备的浏览器和操作系统的情况
同一时间内,有些用户的浏览器版本是比较旧的,支持的功能比较少,有些用户浏览器版本更新,支持的功能多
根据用户使用的设备进行区分,通过UA中的浏览器版本/操作系统版本,区分当前用户的设备最多支持哪些特性
UA还有另外一个用途,可以用来区分用户的设备,根据用户的设备返回不同版本的页面
Referer
描述当前页面的来源,这个页面是从哪个页面跳转来的
直接输入url/点收藏栏 打开的页面是没有Referer

Cookie
浏览器展示页面的过程中,页面里虽然可以通过js来实现一些逻辑,但是js代码无法访问硬盘的文件操作系统
因此浏览器引入了cookie机制
cookie就是浏览器允许网页在本地硬盘存储数据的一种机制,不是让网页代码直接访问文件系统,而是做了一层抽象
Cookie这里是按照键值对的方式来存储数据

浏览器保存着这些cookie之后,就会在后续给服务器发送请求的时候把这些cookie键值对放到请求 cookie header中传输给服务器
Cookie 到哪里去:最终发回给服务器
Cookie 从哪里来:也是从服务器这边来(后端开发程序员决定的)

登录和用户认证

查询数据库,验证用户名和密码是否有效
如果匹配成功:
1.生成sessionId
2.生成session对象
3.把用户的关键信息保存到session对象中
4.把sessionId 和session 对象作为键值对保存到内存的hash表中
5.把sessionId 通过Set-Cookie返回给服务器
服务器收到后续请求后,直接通过cookie中的sessionId就可以知道当前这个请求是哪个用户发来的,就不需要用户重新登录
Cookie是可能会过期的,服务器返回Cookie的时候是可以设置有效时间的,如果Cookie中的sesisonId过期了每次是就需要用户重新登录了
有的网站对于安全性要求不高,有效时间就长,反之有效时间就短
用户重新登录的时候,是否需要重新手动输入一遍密码,浏览器的记密码成功解决了

HTTP响应详解
HTTP响应首行:HTTP/版本号 状态码 状态描述
HTTP/ 1.1 200 OK
认识状态码
状态码表示访问一个页面的结果(是访问成还是失败,还是其他的一些情况),状态码都是HTTP标准定义好的
以下为常见的状态码
200 OK
最常见的状态码,表示成功
404 Not Found
访问资源没找到
URL: ip定位到主机,port定位到层序,path定位到程序管理的资源
path访问的资源在服务器上没有
403 Forbidden
访问被拒绝

405 Method Not Allowed
前面我们已经学习了HTTP中所支持的方法,有GET,POST,PUT,DELETE等. 但是对方的服务器不⼀定都支持所有的方法(或者不允许用户使用一些其他的方法).
请求的方法和服务器这边声明的注解不匹配时会出现405
505 Intenal Server Error
服务器出现错误,服务器处理逻辑中的代码抛出异常,但是没有被catch
504 Gateway Timeout
网关(网络的入口) 入口服务器就认为是网关(可能是一个软件也可能是一个专门的机器)
在服务器资源紧张的时候容易触发
303 Move temporarily
重定向
在登陆页面中经常会见到302.用于实现登陆成功后自动跳转到主页.
域名迁移,如果直接迁移了,就会使所有保存旧域名的同学无法访问了,把服务器架设在新域名上,给旧域名设置重定向,可以重定向到新域名
直接访问新域名当然ok,访问旧域名,自动跳转到新域名
状态码整体有很多
2xx 都可以视为成功
3xx 都是重定向
4xx 客户端出错,用户构造的请求有问题
5xx 服务器出错 (java程序员主要关注这个,一定是服务器出问题了,大概率就是代码有bug)
HTTPS
加密
HTTPS = HTTP +S(SSL/TLS)
之前运营商能劫持,数据传输都是明文的,加密,通过密文来传输
明文加密得到密文,密文解密得到明文
1.对称加密
加密和解密使用同一个密钥
2.非对称使用另一个密钥
HTTPS工作原理
之前都是明文传输的

引入对称加密

客户端和服务器最开始通信的时候,就需要一方生成唯一的密钥,在通过网络传输给另一方
服务器要给不同的客户端提供服务,密钥必须是不同的

就需要对密钥进行加密
如果仍然使用对称加密的方式,生成一个key2对称密钥,使用key2对key进行加密,key2还得传给服务器,难道要搞一个key3吗? 无论多少层,总有一层需要明文传输
因此就要引入非对称密钥,为了解决密钥传输的安全性问题,分为公钥和私钥

-
公钥 :相当于信箱上的投信口 。它是公开的,谁都可以知道。任何人都能用它把信(信息)塞进去。但请注意,一旦投进去,连他自己也无法再把信取出来。
-
私钥 :相当于信箱唯一的钥匙 。它必须绝对保密,只有你自己持有。唯一的用途,就是打开信箱,取出里面的信。
直接全用非对称加密可以吗?
对称加密运行速度极快,开销小,适合针对大量数据进行加密
非对称加密运行速度极慢,开销大,加密大量数据的时候非常耗时

对称密钥在被传输时,是用服务器的公钥加密过的
-
客户端发起请求 :你访问一个网站(比如银行网站),浏览器首先拿到服务器的公钥。
-
客户端生成密钥:你的电脑本地随机生成一个临时的对称密钥(比如用来加密后续通信的AES密钥)。这一步完全在你的设备里完成,没有在网上传输。
-
加密传输这个对称密钥 :你的浏览器用服务器的公钥 ,把这个临时生成的对称密钥"锁"进一个盒子里,然后发送给服务器。因为是用公钥加密的,这个盒子只有持有对应私钥的服务器才能打开。
-
服务器解密获取密钥:服务器收到后,用自己的私钥打开盒子,取出里面的对称密钥。
-
后续通信:现在,双方都有了同一个对称密钥,开始用速度快得多的对称加密进行通信
上述这样的流程存在重大的安全隐患,黑客通过特殊手段来获取对称密钥,破坏后续传输的安全性
中间人攻击

为了应对中间人攻击,引入了校验机制
中间人攻击的关键在于客户端无法区分收到的公钥是否是服务器真实的公钥还是黑客篡改的公钥,行办法能够对公钥是否正确进行校验
证书

需要有一个第三方的认证机构(可以信任的组织)。服务器向第三方认证机构申请证书(提交材料) 服务器域名和公钥,第三方认证机构给服务器颁发证书,服务器搭建的时候申请一次就可以了,申请到的证书服务器就保存好
颁发的证书中包含:
- 证件的颁发机构是谁
- 证书的有效期是什么时候
- 服务器的公钥是什么
- 服务器的拥有者(域名)是什么
- 证书的数字签名是什么
数字签名本质上时一个被加密的校验和
校验和如何获取:
1.把证书前四个关键信息作为输入生成校验和(有公式)
2.针对校验和加密,第三方认证机构页生成一对非对称密钥
客户端收到证书,就要进行校验
1.客户端需要针对证书中的其他字段(前四个)使用同样的算法再算一次校验和1
2.通过公证机构的公钥对数字签名进行解密,得到检验和2
3.对比检验和1和检验和2是否相同,如果相同,说明证书没有被修改过,如果不同证书无效
客户端如何保证拿到的公钥是公证机构的,而不是黑客伪造的?
公证机构的公钥不是通过网络传播的,而是操作系统内置的(安装好系统,系统就内置了一系列知名公证机构的公钥),只要安装正版系统就可以信任
如果黑客想要直接修改证书中的公钥为自己的公钥,就会导致客户端计算的校验和和解密出来的校验和对不上,此时客户端就会报错
黑客能否申请一个证书,用自己的证书替换服务器的证书?
证书中包含服务器的域名,黑客申请证书的域名和正经服务器的域名肯定是不同的
浏览器这边可以验证,输入的url的域名和得到证书的域名是不匹配的
黑客通过内置的公证机构的公钥能看到证书的原始值,是否可以修改?
修改公钥,重新计算校验和,还需要重新加密(使用公证机构的私钥来加密)如果黑客拿自己的私钥来加密,就会导致客户端使用公正机构的公钥解密失败
三. 传输层
1.UDP协议
UDP: 无连接、不可靠传输、面向数据报、全双工
先认识协议报格式:


长度属性也是2个字节,表示范围是0~65535,64kb
64kb放在前30年是很充裕的,但放到现在就是非常小的数字,随便一个图片就是几个mb
如何传输一个大文件:
- 应用层代码做拆包操作,一个大的应用层数据包拆成多个小的包,使用多个UDP数据报传输(工作量是巨大的,要写大量的逻辑实现分包和组包的功能,并且需要进行复杂的验证)
- TCP协议,没有数据包长度的验证
校验和,验证数据是否发生修改
HTTPS的数字签名是为了防止黑客篡改(防人)
UDP的校验和不是为了防人,和安全性无关,而是防止传输过程中的"比特翻转"

发送之前先计算一个校验和,把每个数据包饿数据都带入,把数据和校验和一起发送给对端
接收方收到之后重新计算一下校验和,和收到的校验和进行对比(UDP发现校验和不一致就会直接丢弃)
UDP的校验和使用了CRC方法来进行校验(循环冗余校验)把每个字节(除了校验和位置的部分之外)都当做整数进行累加,溢出也没关系,继续加得到最终结果

2.TCP协议
有连接,面向字节流,可靠传输,全双工
TCP协议格式

4位TCP报头长度:表示TCP头部有多少个32位bit(4个字节),4位二进制最大表示15,所以TCP头部最大长度是15*4=60字节;
保留位:UDP的问题是长度不够又不能扩展,TCP的设计者考虑到了这样的问题,在TCP报头处预留了一些"保留位"
16位校验和:用来检验数据是否出现错误
TCP最核心的6个标志位
可靠性:网络通信是非常复杂的,A能够知道B是否收到了
确认应答
保证可靠性的一个关键前提,是发送方知道自己的数据是否被对方收到,需要对方返回一个"应答报文"(acknowledge ack),发送方知道应答报文就可以确认对方收到了
网络上有一个很神奇的操作:后发先至
为了解决TCP在给传输数据的时候进行编号
确认序号只在应答报文中才生效 

TCP是面向字节流,其实在编号的时候,不是按照1条 2条 这样的方式来编的,而是按照"字节"来编号,每个字节都分配一个编号,是连续递增的(载荷部分)
确认序号是把收到数据载荷的最后一个字节序号+1,填写到确认序号中

引入序号之后接收方就可以根据序号对数据进行排序
TCP需要处理后发先至的情况,确保应用程序通过socket api 读到的数据顺序是正确的
TCP在接收方这里会安排"接收缓冲区"(内存,操作系统的内核里),通过网卡读到的数据,先放到接收缓冲区中,后续代码调用read也是从接收缓冲区来读
根据序号排序,序号小的在前面,大的在后面,确保前面的数据已经到了,然后read才能解除阻塞,如果后面的数据先到,read继续阻塞,不会读取数据
超时重传
针对丢包的情况做出处理
为啥会丢包?
网络结构非常复杂,数据经过某个路由器,交换机转发的时候,该路由器/转发集已经非常繁忙了,导致当前需要准发的数据量超出路由器/交换机的转发能里上限
或是堵车了,数据报会消耗更多的时间才能到达对方,更糟糕的情况是数据报太多太多了,路由器/交换机根本处理不过来,接收缓冲区都满了,只能丢弃了(网络上的数据报都是有时效性的)
丢包是不能避免的客观现象
引入超时时间来判定是否丢包,TCP中判定超时时间阈值不是固定的数值,动态改变的
假设当前A ---> B 发送数据,丢包的超时时间阈值为T,当A给B传输发生超时之后,就会延长这个时间的阈值,会继续延长这个时间,不是无休止的,超时次数到达一定程度/等待时间达到一定程度,就认为网络出现严重故障,放弃这次传输
为什么要延长时间阈值
延长等待时间,就是主动给网络一个机会,让它把积压的数据包慢慢处理完。如果是因为拥塞导致的延迟,那么等一段时间后,原来的包和确认包可能就顺利到达了。这是一种"被动退让",避免自己成为加剧问题的源头
重传几次还不成功,有力地证明了当前网络条件极其恶劣(接近饱和或已经故障)。此时,继续以短时间隔疯狂重传只会加速网络崩溃。唯一理性的做法,就是大幅延长等待时间,静观其变,直到网络自行恢复或彻底放弃。


A给B发的数据丢了,还是B返回给A的ack丢了,发送方A是确定不了当前是哪个情况,做法都是进行重传
第二种情况:B已经有了1-1000这个数据了,随后B收到了两份一样的数据,如果TCP不处理,可能会使应用层读到两次一样的数据(扣款数据)
TCP会在内部进行去重操作,接收缓冲区就可以根据序号在接收缓冲区中找一下,如果存在就直接丢弃,如果不存在再放进去
TCP协议中最核心的两个协议就是确认应答和超时重排,保证了TCP能够可靠传输

连接管理
抽象的逻辑上的链接是通信双方各自保存对端的信息
建立连接
TCP通过"三次握手"方式来完成
HTTPS获取证书,验证证书,加密对称密钥,传输对称密钥,确认对称密钥收到......就是SSL协议的"握手流程"
握手操作没有实际的业务,只是打招呼
发送一个不携带业务的数据,通过这个数据和对方"打个招呼"
synchronized 同步的意思 (在加锁这里,同步理解为互斥) TCP中的同步指的是"数据上的同步",A告诉B接下来要和你建立连接,就需要你把我的关键信息保存好,同时你也把你的信息同步发给我


syn和ack合并操作可以提高传输的效率
TCP的状态变化对应这 socket api 的情况

LISTEN :启动服务器 new SeverSocket的时候,就会进入listen状态(服务器准备好了,随时可以有客户端连上来)
SYN_RCVD/SENT 正常情况下肉眼是看不到这两个状态的
ESTABLISHED 表示已经连接了的,客户端和服务器已经建立了连接,接下来就可以传输业务数据
TCP为什么要三次握手,有什么用
1.三次握手,相当于"投石问路"
先初步的探一探网络通信链路是否畅通(网络通畅是可靠传输的前提条件)
2.验证通信双发的发送能力和接收能力是否正常

通过上述讨论,建立连接操作,如果只握两次手,是不够的
握四次手呢? 可以但没必要
3.三次握手过程中可以协商一些关键信息
TCP要协商的一个非常关键的信息就是通信过程中序号从几开始(一般不会从0开始),并且两次连接初始序号是不同的(往往差别会很大)

比如第一次连接,协商好初始序号是1000000.第二次连接,协商好初始序号是8000000
第一次连接的时候,其中有一个数据包转发的时候迷路了,迟迟没有到达B,当第二次连接建立好,已经通信了一段时间后,刚才第一次连接迷路的数据包才到达B,此时B应该处理(由于两次初始序号相差较大可以分辨出)
连接断开
"四次挥手" ,通信双方各自给对方发FIN,我要把你的信息给删了


三次握手中间可以合并,四次挥手呢?
有时候能,有时候不能
ack 和 fin 这两次交互的时机是否相同
ack是内核控制返回的,内核收到FIN,第一时间返回ask,和应用程序无关
第二个FIN 则是代码中调用 socket.close 才会触发(进程结束也能触发),第二个FIN时机和ACK的时机可能不是同一个时机
TIME_WAIT 给最后一个ACK丢包行为做一个托底
谁是主动发起FIN的一方就会进入 TIME_WAIT
谁是被动发起FIN的一方就会进入 CLOSE_WAIT (等待应用程序代码来调用close)

CLOSE_WAIT 正常开发中应该是看不到的,原则上来说感知到对方断开之后就应该尽快执行close
如果发现服务器这边存在大量的 CLOSE_WAIT,持续的还很久,此时意味着你的代码大概率有bug(检查是否执行到了close)
网络传输中随时会丢包,三次握手和四次挥手也一样会丢包,丢包就需要超时重传

如果第一个FIN丢了,A就无法在规定时间内拿到ACK,于是A重新发送FIN,如果第一个ACK丢了,重新发送即可
如果第二个FIN丢了,B也可以再重新发送,在第二个FIN到达A之前,A这里的连接肯定是存在的
但如果B给A发送FIN,A接收到了,A收到之后返回ASK 把连接释放掉,此时ASK丢失无法重新发送
因此引入TIME_WAIT :A这边收到FIN之后不能立即释放连接,而是要等一下,要等一下对方可能重新传FIN(最后一个ASK丢包),多等一会确保对方不会再重新传FIN再释放
等待的时间是 2*MSL(网络上任何两个节点传输过程中消耗的最大时间)通常这个时间会配置成60s,此处 TIME_WAIT等待2mins (这个数值不同系统可能是不同的,可以修改)
三次握手,一定是客户端主动发起的syn(第一次握手一定是客户端开头)
四次挥手,客户端和服务端 都可以主动发起FIN(就看谁先调用close),但是,实践角度来看还是客户端断开连接的可能性更大
四次挥手全部挥完,这个属于"正常情况",当然也有"异常情况"
比如服务器始终不调用close,站在A的角度看,此时A已经给B发送FIN很久了,B也没有进行后续的挥手操作,A就会主动释放连接(也就是把B的信息删除了)
B这边由于代码逻辑有bug,这里的连接暂时存在(还会保存对方的信息,此时也没办法进行正常的数据通信了)
滑动窗口
TCP实现可靠性就会付出一定的代价:效率

这样一发一收的方式性能较低,那么我们一次发送多条数据,就可以大大提高性能(其实是将多个等待时间重叠在一起了)

前几个数据包都是不等待直接往后发
发到一定量之后再等待
能否一直发? 肯定是不科学的,就相当于没有可靠传输了
之后收到一个ack就发吓一条

窗口越大,批量发的数据就越多,效率就越高,但是窗口也不能无线大,太大也会影响到可靠性
(只是亡羊补牢,引入可靠性会使效率产生折损,引入滑动窗口是要让折损更小,不可能效率比UDP还高)
滑动过程中当然也会丢包

不用做任何额外的处理,后一个ack会涵盖前一个ack
丢失了1001~2000,接收缓存留有位置,后续之间填入即可
流量控制
滑动窗口的窗口越大,效率就越高,但是不能无限大,太大会影响可靠性

如果水位已满,此时再蓄水就会溢出(丢包)
流量控制就是给发送方踩刹车,让它发的慢一点
在ack中,依赖一个特殊属性"窗口大小",接收方接收缓冲区的剩余空间大小填入到这个属性中,发送方就会按照这个数字来重新设定发生的滑动窗口大小(滑动窗口的大小是动态变化的)
是否滑动窗口大小的最大数值就是64kb,实则不然

窗口大小 << 窗口扩展因子 (指数增长)

拥塞控制
流量控制是依据接收方处理能力进行限制的,拥塞控制是依据传输链路的转发能力进行限制的
如果不好具体衡量到某个设备,就可以把整个通信链路视为"整体",通过"做实验的方法"找到一个合适的窗口大小
先按照小的窗口(小的速度)发送,如果发的时候很顺利,不丢包,就加大速度,出现丢包再减小速度......动态平衡

这个图就表示了拥塞工程的工作过程:慢启动、指数增长、线性增长、丢包(窗口变为较小值)
延时应答
默认情况下,接收方都是在收到数据报的第一瞬间就返回ack,但是可以通过延时返回ack的方式 提高效率

应用程序能够在处理的限度下,尽可能增加窗口的大小
也不是100%能提高效率,还是要看应用程序消费的速度快不快
**那么所有的包都可以延时应答吗?**肯定不是
数量限制:每隔N个包就应答一次 (ack减少不会影响可靠性,确认序号后一个能够覆盖前一个)
超时限制:超过最大延迟时间就应答一次
综合的,传输的数据密集就按第一个,数据稀疏就按第二个
具体的参数,隔多少包,隔多长时间........这些参数都是可以调整的
捎带应答
TCP已经有了延时应答,基于延时应答引入"捎带应答" 返回业务数据的时候顺带把上次的ack给带回去
如果没有延时应答,返回ack的时机和返回响应的时机是不同的时机,引入延时应答,ack可以往后延时一定时间,恰好这个时候要返回响应数据,此时就可以把ack也带入到响应数据中,一起返回
ack:ack设为1、窗口大小设为接收缓冲区的剩余值、确认序号设为合适的值(都是报头里设置的)设置这些内容不影响响应数据
把两个包合成一个可以起到提高效率的作用
面向字节流
粘包问题:粘的是"应用层数据的包" 通过字节流的方式传输就很容易混淆包与包之间的边界,从而接收方无法区分从哪里到哪里是一个完整的应用层数据包
粘包问题无解,需要在应用层解决,定义好应用层协议,明确包之间的边界
1.约定好包和包之间的分隔符(包的结束标记)
2.约定包的长度
比如约定每个包开头4个字节,表示数据包一共多长
HTTP中两种方案都有体现:
1.GET 请求没有body,使用空行作为结束标志
2.POST请求有body的时候,通过 Content_Length 决定body多长

异常情况的处理
TCP在通信过程中存在特殊情况
1.某个进程崩溃了
进程崩溃和主动退出没有本质区别,进程释放 =》回收文件描述符表的每个资源 =》调用socket的close =》FIN触发四次挥手,进程虽然没了,但是TCP的连接信息还存在(TCP 连接信息存储在内核的内存结构中)此时四次挥手还是可以正常进行的
2.主机关机了
正常流程的关机,本质上还是先杀死所有的用户进程,关机也需要一定的时间(同上)
如果一定时间内没有完成四次挥手

假设B的FIN来的太迟了,导致A已经关机了,意味着B的FIN不是有ACK,B重传FIN也没有ACK,进过几次之后认为对端出现严重问题,此时B主动放弃连接(B把保存的A的信息删除)
最后B仍然可以把连接释放掉
3.主机掉电了

A突然掉电了,B后续发来的数据都没有ACK了,对于B触发超时重传,重传不能解决问题,重传达到一定次数就会触发"重置TCP连接",B主动发送一个复位报文(RST)
RST报文是TCP协议中用于立即终止连接的控制报文,其头部标志位中的RST位设为1。与通过四次挥手正常关闭的FIN不同,RST实现的是"异常中止"------不保证数据可靠交付,直接重置连接

RST报文不需要ACK,也不会等待ACK返回。发送RST的同时,本端会立即清除连接状态(如将socket标记为已关闭,释放资源),无需等待对端的任何响应。

B突然发现A没有声音了,此时B区分不了A是挂了还是暂时休息一会,B只能继续等(不会无线等)B等待一段时间后,就会给ACK传输一个特殊的报文 "心跳包" 不携带业务数据(载荷)只会触达ACK(周期性的,如果没有回复就认为对端挂了)
如果对方有心跳就继续等,如果没有就只能通过RST尝试,还是不行就只能单方面释放连接
保活机制
分布式系统中,心跳包思想方法的使用非常广泛(TCP的心跳周期太长了,分钟接别的)
虽然TCP内置了心跳包,实际开发中,通常还是会在应用层重新实现心跳包的效果
现在通常希望是秒级 甚至毫秒级 就能发现对端是否正常存活,从而触发一些后续的操作
其他

TCP和UDP对比
TCP(可靠传输) 大部分场景下
UDP(效率高) 对于性能要求高,可靠性要求不高的场景下

TCP和UDP都太极端了,是否有介于两者之间的
传输层也有其他的协议,游戏开发,尤其是竞技类游戏,对于性能和可靠性都有要求,也有专门的协议来应对: KCP 。其他协议通常也都是基于UDP的基础上实现可靠性(直接改传输层需要改操作系统的内核,在应用层开发的,实际上协议的角色还是传输层的角色)
三. 网络层
IP协议

IP协议/网络层 做的工作主要有两个
1.地址管理 使用IP地址这样的概念,标识网络上的某个设备的位置
2.路由选择 在两个通信节点之间,规划出一个合理的路径
4位版本:IPv4 IPv6
4位首都长度 :IP协议的报头也是可以变长的(选项) 都是4字节
8位服务类型:决定了IP协议的工作方式

也可以用api 来设置,开发中很少直接涉及到
16位总长度 : 一个IP数据报 报头+载荷长度
IP协议内促内置了拆包和组包功能

8位生存时间:一个IP数据报能够在网络上传输的最大时间,单位是"次数"
IP数据报每次经过一个路由器转发一次,TTL值就-1,TTL减到0就说明包到不了,就会丢弃
8位协议:表示传输层使用哪种协议
分用的时候,IP协议解析数据报的时候,把拿到载荷交给上层处理,此处8位协议编号就能起到区分效果

32位源IP地址/32位目的IP地址:IP协议最关键的部分
IP地址本质上是通过32位整数来表示的
由于32位整数不方便阅读,通常把IP写作点分十进制来表示
用三个点分出四个部分

IPv4 地址本质上是一个 32 位的二进制数,为了人类阅读方便,把它分成了 4 个 8 位一组(即 1 字节 = 8 比特),每组用十进制表示,中间用点隔开。
-
每个 8 位二进制数的最小值是
00000000(十进制 0)。 -
每个 8 位二进制数的最大值是
11111111(十进制 255)
IP地址不够用了怎么办
1.动态分配IP
上网再分配,有所缓解,不能根本上解决问题
2.NAT机制 网络地址转换
把所有的IP分成两个大类
- 公网IP/外网IP
- 私网IP/内网IP 10.* 172.16~172.31.* 192.168.*
NAT机制就可以用一个外网IP对于一系列的内网设备(允许重复)
一个设备有一个独立的的局域网IP,多个设备共用一个外网IP(不允洗重复)
NAT背景下,网络通信如何进行?
- 同一个局域网下,设备A访问设备B
由于IP本身是不允许重复的,自然不受任何影响,NAT起不到作用
- 公网设备A访问公网设备B
由于公网IP本身也是不重复的,也不受影响,NAT不起作用
3.不同局域网,设备A访问设备B是不允许的
NAT禁止这样的访问形式
4.局域网设备A访问公网设备B
网络地址映射
网络地址映射
网络通信中涉及五元组,通过端口号可以将返回的响应发送到对应电脑
路由器触发NAT地址替换的时候,自身维护一个"哈希表"记录,记录替换的映射关系,即使有一样的端口,在记录的时候会修改,可以找回去就好
5.公网设备A尝试访问局域网设备B
不允许
如果在自己的电脑上搭一个能让外网访问的服务器,需要特殊手段,比如通过内网穿透,通过VPN(虚拟私人网络)

3.IPv6
IPv4 是使用4个字节作为IP地址
IPv6 是使用16个字节作为IP地址
IPv6和IPv4是不兼容的,升级IPv6就得更换设备(路由器),但是不能提高任何的上网体验
IP地址的其他规则
网络划分
学校、企业、组织需要组网(成百上千个设备,网络结构十分复杂)
就需要专业人员负责搭建网络(如何配置路由器/交换机,搭建网络环境)
网段划分
把一个IP地址分成两个部分,前半部分称为"网络号",后半部分称为"主机号"
同一个局域网中,网络号必须相同,主机号必须不同
两个相邻的局域网中,网络号必须不同,主机号无限制
违反后将不能正常上网
192.168.100.98 哪个部分是网络号,哪个部分是主机号,是可以配置的

家用网络一般是255.255.255.0,也可以简写成 192.168.100.98/24
上古时期的划分方法

特殊的IP地址
- 将IP地址的主机地址全部设为0,就成为了网络号,代表这个局域网
局域网不能把直接把主机号设为0
- 将IP地址的主机地址全部设为1,就成为了广播地址,用于给同一个链路中相互连接的所有主机发送数据包
往广播地址上发送数据包就会被转发给局域网中的所有设备,只局限于当前的局域网(手机投屏)
- 127.*的IP地址用于本机环回测试,通常是127.0.0.1
- 习惯用法:192.168.100.1 在IP协议中只是普通的IP,在实际组网中,通常使用这个IP作为网关IP(网络的入口/出口 家庭网络中一般是路由器,大型企业/网络结构,网关通常是单独的理设备)
路由选择
通过IP协议进行数据转发的过程
网络环境是非常复杂的环境,任何一个路由器都无法存储所有的网络信息,但是每个路由器是可以知道附近的网络情况
当数据包到达某个路由器的时候,就会匹配这个路由器的"路由表"(路由表就记录了这个路由器周围的设备的IP,以及也会记录每个设备要通过哪个路口转发过去)
1.如果目的IP刚好匹配到路由表中的记录,直接按照当前对应的口转发过去就行
2.如果没有匹配到,路由表会有一个特殊的表项"下一跳",指向设备上一级路由器的位置







