本章重点
- 了解应用层协议
- 了解传输层
UDP/TCP 网络传输协议! - 重点了解
TCP 可靠传输背后的机制 - 重点掌握三次握手和四次挥手
- 重点掌握网络层
IP 协议
网络编程原理进阶
我们的进阶内容比网络编程初始比较深入!我们通过这一章节的学习可以掌握网络编程原理,对网络编程有了初步认识! 我们这里的学习也从五层网络协议逐层进行学习! 我们先来复习一下这五层协议的功能
- 应用层
应用层协议主要由程序员编写,指定发送的信息和发送的信息的格式 重点学习htttp 协议 - 传输层
传输层协议报头保存了源端口和目的端口,用来明确两台主机交换的服务器 重点学习TCP /UDP 协议 - 网络层
网络层协议报头保存了源IP和目的IP,明确了网络传输的主机位置 - 数据链路层
数据链路层是由一些路由器/交换机,保证了相邻设备的传输,并且数据链路层,帧头保存了每次相邻设备的位置信息会不断的更新数据! - 物理层
物理层就是硬件设备
我们重点掌握 应用层和传输层协议! 像其他3层协议身为搬砖程序员不需要了解,这是操作系统内核,除非是系统开发工程师,驱动开发工程师!
应用层
我们知道应用层协议得我们程序员自己编写! 突然就一整个头大了,这咋编写呀,不要慌问题不大! 我们只需要明确需求,根据需求确定需要传输的信息,还有传输信息的格式! 我们根据传输的信息和传输的格式就可以完成了一个应用层协议的组织! 就是我们发送的请求,另一台主机可以根据这个应用层协议,获取到请求信息,然后发送响应! 就好比前端有一个需求请求,请求发送的后端,后端根据应用层协议就可以获取到请求内容给出具体响应,完成强后端的交互过程!
传输的信息
例如:我们有一个图书馆借阅查询的请求!
我们就要明确传输的信息!
(前端发送的请求)
1.用户id
2.查询的起始时间
3.查询的结束时间
这就是我们要传输的信息,明确了请求信息,我们后端就要根据请求给出响应信息!
(后端返回的响应)
1.查询成功(成功标志)
2.查询失败(失败原因)
3.结果数组(查询成功)
这里的结果数组就包含了响应信息
书籍名称
书籍借阅天数
是否已还
....
数据的组织格式 我们上面明确了传输的信息,当时还不够,我们需要约束数据的组织格式! 我们通过那种格式将数据进行传输,就好比你发送一个文件给好友,你要明确是用QQ发还是用微信发!而这里传输层协议也一样,我们需要约定数据的格式!
例如:我们以下面的格式进行传输!
请求格式形如:
bug郭/t20220506/t20220611
响应格式:
OK
三国演义/t29/t已还
水浒传/t3/t未还
上面的格式只是随便编写的一个格式,我们可以自行决定用那种格式进行传输,可以自行编写,只要保证请求和响应格式类型一样就可以!
这样自己编写格式,显然有点人编写的协议格式很拉垮,有点大佬写的格式很好!
而不同的数据组织格式 可读性和传输效率不同! 这时就有大佬发明了协议模板,我们只需要根据协议模板往上套即可! 协议模板
//xml格式
<标签名>内容</标签名>
可以看到xml 数据组织格式和html 相同由标签和内容构成,可读性好!
--xml格式请求
<request>
<id>bug郭</id>
<startTime>20220511</startTime>
<endTime>20220611</endTime>
</request>
--xml格式响应
<response>
<result>
<book>
<name>三国演义</name>
....
</book>
</result>
</response>
这里的xml 虽然可读性很好,但是标签太多,用这种方式组织数据,传输时,传输效率不高,有很多无效数据,很吃带宽!所以xml 一般用于编写配置文件,已经渐渐淡出应用层协议数据组织格式的舞台!
json格式
{
键:值,
键:值,
键:值,
...
}
json 格式数据组织格式!
由{} 保存一个json 数据 键值对之间用, 分割 键和值之间用: 分割 最后一个键值对的, 可有可无,json 语法比较宽松,但是语法是不用的! 然后值可以是多种数据类型(数字,字符串,数字,json 对象…) 这里的键默认是字符串,不需要用引号,但如果键中包含了特殊符号如 空格,- 就需要用分号保存! 这里的值字符串由引号保存,单引号和双引号都可(一般没有区分字符和字符串类型通用)
{
name:'bug郭',
startTime:'20220506',
endTime:'20220611'
}
{
ok:true,
reson:"",
data:[
{
bookname:"三国演义",
time:12,
state:"已还"
},
{
bookname:"水浒传",
time:3,
state:"未还"
}
]
}
可以看到json 相较于xml 可读性也很好而且比较简洁,这是如今主流的一种应用层协议数据组织的格式! 可读性好,效率高!
protobuf 格式是纯二进制数据格式! 通过数据的顺序或者一些特殊符号来区分每个字段! 十分简洁,传输效率高! 我们会疑惑,二进制文件,那我们咱看得懂,怎样组织数据呀! protobuf通过一个文件IDL 请求方和响应方通过这个文件就可以知道每个数据是什么意思,用来描述数据格式,是一个辅助开发的工具!类似于C语言结构体!
这里就不做举例了!
显然这中格式的传输效率高,但是可读性差,不利于开发! 开发效率会下降!没有json 主流!
通过上述对传输信息的学习和数据组织格式的学习我们可以知道应用层协议的设计也并不是很困难!
- 只要我们明确传输的信息(根据需求)
- 明确数据组织格式(参考现有模板xml,json,protobuf…)
除此之外,业界还有一些现成的,已经设计好的协议 我们并不是任何时候,都要自己从0开始设计一个应用层协议,我们也可以基于现有的协议稍加修改,进行二次开发! 最知名应用层协议就是http 协议,我们后面会介绍!
DNS
DNS:域名系统(Domain Name System ) 啥是域名呢? 我们知道每台主机设备都有自己的IP地址!通过这个IP地址我们就可以访问该主机! 联系我们实际上网过程,难道我们访问一个网站的服务器是通过IP地址访问的嘛? 显然不是我们那能记得住那些IP地址呀,如果访问一个服务器都要记下一个IP地址那谁还上网对吧! 域名系统就很好的解决了这个问题!我们的IP地址记不住就算了! DNS通过一串英文单词标记一个IP地址,这一串英文单词就是这个网站的域名,通过这个域名就可以找到这个网站服务器的IP从而访问到服务器!
DNS就是管理域名的 刚开始时这些域名和IP地址的映射关系,保存在我们主机的系统文件中,通过这个文件,主机就可以根据域名找到IP地址! 系统中的这个hosts 文件就保存着域名和IP地址的映射!
可以看到文件中这里IP127.0.0.1 通过域名local.id.seewo.com 和 activate.navicat.com 就可以访问.不过现在这种方式已经不用了! DNS系统管理服务器 现在有一个专门管理域名的机构,通过这个机构,就可以申请登记域名和IP地址的映射关系,然后这个机构将这个映射关系保存在他的服务器上,在你的主机需要访问一个域名时,通过访问这个域名服务器就可以拿到这个域名对应的IP地址从而访问到服务器!
我们又想到一个问题,全世界这么多设备需要上网,如果都访问这个域名服务器那这个服务器不得挂! 确实如此,所以这个机构的域名服务器为根域名服务器,不同的区域还有响应的镜像域名管理系统服务器,不同的地方通过访问这个镜像服务器就可以获取到域名对应的IP. 我们的域名解析服务器有很多,我们的主机可以自己设置选择通过那个域名解析服务器解析域名! 我们通过这个设置就可以对域名解析器服务器进行选择! 默认是自动获取DNS也就是从运营商哪里获取! 也可以手动更改其他的域名解析服务器!
当我们浏览器查询一次域名解析服务器后,就会将这个结果保存在浏览器一段时间,下次访问就不用通过域名解析访问,可以直接访问了!
传输层
虽然除了应用层其他四次都是由操作系统内核实现,但是传输层协议的学习有助于当我们代码出现bug 更加精准的找bug 定位问题,修改问题! 我们知道传输层协议很多,但是我们主要来学习TCP 和UDP 这两个协议!
端口号 范围: 2个字节大小; 0-65535之间的整数 知名端口号:把0-1024这些端口号划分为一些具体的作用 很多网络服务器是非常常用的 未来方便管理,及将这些服务分配了一些专门的端口号 这里并不是强制要求,而是建议! 例如: 80 http服务器 443 https服务器 22 ssh 23 ftp … 我们自己部署http端口也可以绑定其他的端口,就像java 中的tomcat 就没有将http绑定在80端口,而是绑定在了8080端口!
UDP
学习一个协议很多时候就是研究他的报文格式
这里的报文格式排版并不之前,只是因为美观所以这样,实际上的报文格式如下所示:
就是我们之前分装的数据格式!
- 源端口
源端口就是保存该主机程序的端口号 - 目的端口
目的端口就是接收方主机程序服务器的端口号 - 报文长度
这里的报文长度范围是2个字节大小,也是就0-65535,单位是字节! 也就是最大的报文长度64k 一次传输的UDP传输层协议报文只能保存64k 数据! - 校验和
校验和就是检验接收方接收到的数据是否正确! 如何校验呢? 数据内容参与校验! 例如: 你要去买饮料,寝室的人都叫你带,而且买的饮料都不一样,这时,你如何知道有没有买正确呢?我们可以根据人数就可以确定数量是否正确,然后更加每瓶饮料的首个字,保存下来,然后对照买的饮料看看是否一样! 通过这样的方法,就可以校验是否正确! 而这里的内容校验也是类似,可以保证绝大多数时间我们接收的数据都是正确的!
TCP面试重点
我们看到TCP 协议报文格式比较复杂!
- 源端口:保存请求方服务程序端口号
- 目的端口:保存响应方服务器端口号
- 32位序号:因为TCP是面向字节流传输,TCP对传输的数据按字节顺序进行编号.
例如:我要传输200个字节的数据,然后数据的确认序号为100,说明我是从字节序号为100的数据开始传输传输200长度字节! - 32位确认序号:就是发送方期待响应方发送已经接收到的字节序号的下一个字节序号(告诉发送方下一次要发送的数据字节开始顺序)
例如:我们发送方从100字节序号发送200个字节数据,接收方接收后,就应该返回301的确认序号给发送方! - 4位数据偏移(首部长度):也就是tcp的报文头大小这里的单位是4字节,也就是15*4字节= 60字节,协议报头可以有60字节大小
6位保留位:用于后期扩展,保留今后使用,但目前应都位0 - 6位标志位:
1.紧急URG:当URG位为1时,紧急指针字段有效,表示这个协议报中存在紧急数据需要优先处理 2.确认ACK:当ACK标志位为1时,确认号字段有效,TCP规定在连接建立之后ACK标志位要置为1 3.推送PSH:当两个应用进程进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应,这时候就将PSH=1 4.复位RST:当RST=1,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接; 5.同步SYN:在连接建立时用来同步序号.当SYN=1,ACK=0时表示发送连接请求报文,当响应方同意建立连接后发送SYN=1,ACK=1给请求方! 6.终止FIN:用来释放连接,当FIN=1时,表示数据以全部发送请求释放连接!
- 16位窗口:通知接收方,我发送的文本,需要留有多大的空间接收
- 16位校验和:检验接收的首部和数据是否出错!
- 16位紧急指针:指出本协议报文紧急数据的字节数
- 选项:可变长度不像udp报头固定了8字节,tcp留有扩展的余地!
- 填充:用于存放完整应用层的数据载荷
我们通过之前对TCP的学习已经对TCP协议特点有所了解!
上面的这些特点在我们的套接字编程中TCP中都有所体现,除了可靠传输!
那么TCP是如何保证数据传输的可靠性呢?
TCP 原理
TCP 协议需要保证传输数据的安全可靠性,还有数据传输的传输效 率! 所以TCP的设计原则:在保证可靠传输的基础下尽可能提高传输效率! 下面是TCP设计的一些原理策略!
确认应答
我们刚刚学习32位序号和确认序号时,已经知道TCP 由于面向字节流的特点,对传输的数据按照字节顺序进行编号!然后返回确认序号!
如果不对数据进行编号会怎样呢? 当小明发了两条消息给李华时!
这时小明收到"好呀好呀"不知道是啥意思了,因为他发送了两条消息,这是上号还是学习呀,就很迷!
如果我们有确认序号就帮我们解决了这个问题! 小明接收到李华发送的消息,有了确认序号2,表示李华已经接收到了第一条数据,并且给了回应!希望小明发送第二条数据!这就是序号和确认序号的作用!TCP对每个数据都进行了编号! 每次接收方接收到了数据,就会发生一个ACK确认已经之前的数据已经全部接收到,可以继续发送数据了! 确认应答策略保证了数据传输的可靠性!
超时重传
超时重传相当于对确认应答进行了补充! 我们知道我们网络传输的环境十分复杂,有可能会存在数据丢失丢包的情况,我们此时如何保证数据传输的可靠性呢?
网络正常: 当网络正常没有掉包时,刘备通过张飞发送的"好啊好啊"ACK可以知道张飞已经接收到消息,我们的消息发送成功!
但我们没有接收到ACK时! 当我们没有收到接收方传来的ACK时,我们也不知道是我们的数据丢包了,还是对方的ACK丢包了,这时就无法知道接收方,是否已经接收到了数据! 此时发送方等待了会,没有接收到ACK时会将消息再次发送,这就是超时重传!
可能有人就会说了,那如果是ACK丢了的话,如果我们再次发送一条消息,那消息不就重复了呀? 我们的ACK是在操作系统内核发送的,也就是是说,如果接收方接收到了数据,首先到了接收方操作系统socket的接收缓存队列(阻塞队列)中,然后会记录下接收数据的数据编号,然后将ACK 发送个发送方! 如果我们数据丢包了,接收方的接收数缓冲区就没有该数据,会将该数据编号保存,发送一个ACK确认序号! ACK 丢了,发送方再次发送数据到接收方,先到了操作系统内核,然后呢保存到接收缓冲区,发现已经有该数据了,就直接丢弃,就不会再给接收方这个数据了! 这就保证了应用程序调用socketapi 拿到的数据不重复!
当网络只是发生抖动,那么超时重传就能保证传输继续! 但是当网络遭受重创,直接卡了网线! 那么无论如何重传也无济于事,那这是发送方就不会一直重传下去了! 并且每次重传的时间间隔会越来越大! 最后停止重传! 这里的每次重传的时间间隔并没有准确的值,也可以自行设置!
连接管理(面试重点)
我们知道TCP 是有连接的,所以传输数据之前必须双方进行连接! 那么如何进行连接呢?
3次握手
3次握手就是客户端和服务器建立连接的交互过程 这里的握手只是一个比喻,一次握手就好比一次交互过程,这里进行了3次握手也就是客户端和服务器建立连接需要进行3次交互才能实现!
3次握手就类似于打电话刚接通的过程: 经过这3次交互过程我们就可以知道双方的通话环境是否ok! 第二次握手,可以知道客户端的发送能力和服务器的接收能力没有问题!当进行最后一次交互握手过程时,就可以确认通信双方的可以进行通信!
上图就是客户端和服务器进行3次握手的过程!
TCP的状态: LISTEN 状态,表示服务器可以进行连接,等待连接状态,就好比手机开机,网络良好,等待电话! ESTABLISHED :表示通信双方已连接成功,就好比已经接通了电话,可以进行信息交流了!
3次握手有啥用?和可靠传输有什么关系?
- 3次握手相当于投石问路,检查一下通信双方的网络状态,是否满足可靠传输的基础,如果网络状态不好,就没有进行数据传输的必要了!经历了3次握手就可以验证双方的发送能力和接收能力,就好比打电话双方打通后需要验证双方的麦克风和喇叭!
- 让双方能够协商一些重要信息
为啥是3次握手,不能是4次?或者2次嘛?
上图就是4次握手,可以4次但是没有必要,我们可以将第二次ACK 和第3次SYN 放在一个数据包中传输,没必要分开进行分封装分用,就好比你去一家店铺买2件衣服,商家肯定打包成一个包裹!
这里分开传输效率低!不如和在一起!
2次握手显然不行,因为进行次握手后,客户端知道了自己的发送能力和接收能力良好,但是服务器不清楚自己的发送能力和接收能力是否良好,进行3次握手双方才能确认了双方通信状态,进行接下来的数据传输! 3次握手就好比2个人谈恋爱是一歌双向奔赴的过程,所以其中的交互缺一不可,不然就不能确立男女朋友关系!
4次挥手
4次挥手客户端和服务器断开连接的过程!我们在进行3次连接后,在操作系统内核中保存了一些连接的信息,就是我们之前学过的五元组!断开连接的过程就是将保存的信息删除! 也就是相当于情侣之间分手的过程! 当俩个人确认关系后,那么双方会在心里留一块位置给对方,起码知道对象叫啥名,不然谈啥恋爱对吧,然后4次挥手就把心里的位置给腾开,把之前双方的信息给销毁!
CLOSE_WAIT :四次挥手进行了2次后的状态,这个状态就是等待代码中调用socket api中的close 方法进行接下来的挥手,如果一个服务器中出现了大量的该状态,大概率出现了bug! TIME_WAIT :谁发起的FIN 谁就进入了该状态,表面上看A 发送了ACK 就没撒事了,但是有可能出现最后一个ACK 丢包问题,所以TIME_WAIT 状态会等待B 的超时重传!要保证,B重传时,A还在连接! 一般A的等待时间是2*MSL ,MSL是网络间,任意两点传输的最长时间!
我们此时又在想:3次挥手中间的ACK 和SYN 合在了一起,提高了传输效率,那么这里为啥不能是3次挥手呢?
3次握手中间两次可以合并,都是由操作系统内核完成! 而4次挥手有时候不能合并! 不能合并的原因B发送ACK 和FIN 的时机不同,ACK 是由操作系统内核发送,而FIN 是代码中的socket.close 方法才会触发,有时两者的时间间隔太大无法合并一起!
滑动窗口
上述的机制都是针对TCP 的可靠的传输设计的! 而在可靠传输的基础上还要保证传输效率! 滑动窗口机制就是提高TCP 网络协议的传输效率! 我们可以看到,如果TCP 每次传输一次数据就要等待一个ACK 确认序号后再进行传输数据,显然这样每次等待ACK 这就使得TCP 的传输效率很慢,而滑动窗口就解决了这个问题!
我们可以一次发送多组数据,每组数据都会有一个ACK 我们同一发送完一组数据后,再一次等待ACK 保证传输的可靠性同时又保证了传输效率!
- 这里的一组数据的数据条数就是窗口大小,在窗口大小的范围内,我们无效等待
ACK 可以继续发送数据! - 滑动,就是我们接收到
ACK 后,我们的窗口位置就可以往后滑动,又可以进行数据发送!
这张图就很好的诠释了滑动窗口! 这里一组数据有4条数据,就是窗口大小为4! 当我们已经接收到了ACK 2001时,我们就知道1001-200数据已经发送成功,窗口向下滑动!
一起发这么多ACK ,如果出现丢包问题,该如何解决呢?
-
ACK包丢失,数据到达! 这种情况,我们并不需要处理,就如上图,虽然ACK1001丢失了,但是后面的2001接收到了,我们知道,接收到了ACK2001说明前2000个字节数据传输成功,也就是ACK2001涵盖了ACK1001的功能!后面的4001丢失也是如此,我们已经接收到了5001,就保证了1-4000数据已经发送成功! -
数据丢失 数据丢失,就需要进行处理,可以看到当1001-2000 数据丢失后,虽然这一组数据的发送并没有结束,但是每次的ACK1001 确认序号都是一样的,服务器向客户端索要1001-2000 数据,在进行这一组数据的发送后,客户端会将数据重发! 我们知道主机B会将数据保存到接收缓冲区,在没有接收到1001-2000 的数据,接收缓冲区会留有一块空间! 直到接收到1001-2000 的数据将其补上! 这种机制叫做高数重发机制(也叫快重传)
流量控制
流量控制是在滑动窗口机制上对其补充!保证了传输的可靠性! 我们了解到在主机A和主机B进行通信时,主机A的数据先保存到主机B操作系统中的接收缓冲区当中! 我们又说过,接收缓冲区是一个阻塞队列,那么当主机A的传输数据过快时,那么数据就会在主机B中的接收缓冲区阻塞,如果主机A继续发送数据就会导致丢包问题!
因此TCP根据接收方的处理能力,来决定发送速度,这个机制就叫做流量控制! TCP如何得知接收方的处理能力呢?
- 接收方通过将接收缓冲区剩余空间大小放在
TCP 报文中的16位窗口大小中,然后通过ACK报文告知发送方! - 窗口大小越大说明,接收方的处理能力越强!
- 当接收缓冲区将要满时,发送方会减小发送速率,避免产生丢包!
- 如果接收缓冲区满了,那接收方就会回将0保存在16位窗口中!发送方收到ACK后就会停止数据传输,会过会时间定期发送一个窗口探测报文,接收方会将窗口大小返回给发送方!
我们知道16位窗口大小只能保存最大数据为65535字节,那么一个TCP数据报超过65535字节呢? 我们TCP报文中还有一个40字节的选项,这里面包含了一个窗口扩大因子M,实际的窗口大小可以是16位窗口大小+M左移因子!
拥塞控制
这也是滑动窗口的延伸,限制滑动窗口的发送速率保证可靠性! 拥塞控制是衡量发送方到接收方,这整个线路之间的拥堵状况(处理能力)这里和流量控制一样限制了发送方的传输速率!
我们知道两个设备进行通信,中间链路情况十分复杂,中间有很多设备,而每个设备的信息处理能力又不一样!如果中间一个设备处理的很慢,那其他设备处理的再快也无济于事! 这就导致,整个链路的传输速率也下降,这也促使发送方降低发速率!
发送方如何得知中间链路的阻塞状态呢? 这里无法直接得出,我们需要通过实验得出一个结论,找到这个最适合的发送能力,既要保证传输速率快,又要避免丢包!
- 发送方一开始取的拥塞窗口大小非常小,取纵轴的1个单位(1个单位的大小是1个字节,10个字节,具体要看操作系统代码如何实现)
- 一开始发送方通过指数增长的方式,增加传输速率,到达了一个合适的值后,就降低增长速率!
- 到达阈值后就是指数增长就会变成线性增长,使速率无限接近最大速率,直到产生丢包
- 丢包后,说明网络拥塞,自接将速率减少到最开始的初始值,避免丢包过多,又反复进行上述过程,当第二次指数增长时,会将阈值减小到当前出现丢包窗口的一半(一开始到达
24 就产生了丢包,那第二次就会将阈值减少到12 ),然后进行线性增长,直到找到一个最合适的值!
这个过程可能有些许复杂,可以联系俩个人谈恋爱! 一开始在一起时,热恋期好感度指数增长,到了后面,新鲜感没了,没有增长的这么快了,最后吵架闹分手,就好感度一重新开始,所谓小别胜新欢,然后又热恋,显然这次热恋期没第一次长,然后种种最后爱情的镜头还是财米油盐!
这里的拥塞窗口和刚刚的流量控制都是对发送方发送速率的约束,那么最后发送方发送的速率是取决于那个呢? 发送速率 = Min(拥塞窗口,流量控制);
延时应答
演示应答是流量控制的的延伸 流量控制是告诉发送方不要发送的太快! 延时应答是在这个基础上,尽可能然窗口更大一些!
刚刚的流量控制,当接收方的接收缓冲区满后,发送方就会停止发送数据,而是定期发送窗口探测报文,为了得知当前接收方接收缓冲区的大小,然后接收方就会发送返回一个窗口大小的报文!
而这里的延时应答就是将这个窗口大小的报文延时传送给发送方! 这样延时处理后,给接收方些许时间处理数据,使得窗口大小变大,然后下次发送方就可以提高发送速率!
捎带应答
这里又是延时应答的延伸! 因为延时应答的存在导致接收方接收到数据后ACK 可能不是及时传输,那么当ACK 延时后,当再次发送时,发现和另一条发送数据的发放时间一样,那么就可以将ACK 和另一条数据一起封装分用,进行打包发送,提高发送效率!
就是将ACK 和数据一起打包传输返回给主机A!
客户端和服务器之间的通信方式有多种!
- 一问一答:客户端一个请求返回一个响应(网页)
- 一问多答:下载文件
- 多问一答:上传文件
- 多问多答 :直播
这里的捎带应答策略针对的是一问一答方式
这里捎带应答ACK 和数据报丢了咋整,和正常的丢包程序一样处理!
面向字节流
TCP是面向字节流读取数据的,也就导致了一个粘报问题! 所有的面向字节流都会存在粘包问题!
粘包问题: 当主机A发送3条数据给主机B时,3条数据就来到了主机B的接收缓冲区,然后应用程序通过socket中的read方法读取数据,面向字节流读取,这时应用程序也无法区分数据包,就出现了粘包问题!
如何解决呢? 显然我们的应用层数据拿到的数据时进行了分用的,也就是说并没有TCP协议中的字节确认序号,所以这里要通过对应用层协议的改进才能解决粘包问题! 在设计应用层协议时就要对数据通过标记位或者分隔符进行分割! 在应用层协议设计好就处理了粘包问题! 如果我们是基于某些库和框架来完成的网络通信,一般来说粘包问题已经被库和框架解决了,如果你是自己实现的库和框架使用TCP协议那就需要考虑粘包问题!
TCP的异常处理
- 进程终止
当我们强制杀死一个进程时,这时的TCP连接是咋样呢? TCP 连接是通过Socket 来建立的,Socket 本质是进程打开的一个文件,而文件就存在于进程的PCB 中的文件描述符表,每次打开一个文件(包括Socket )就会在文件描述符表中添加一项! 每次关闭一个文件就在文件描述符表中删除该项! 如果直接杀死进程,PCB 也就没了,文件描述符表自然没了,相当于手动调用了socket.close 效果一样! - 机器关机
关机机器会先通过操作系统将主机中的所有进程强制结结束也就和上述的进程终止一样,然后再关机 - 机器掉电/网线断开
机器掉就是电台式机拔掉了电源 操作系统毫无防备,没有对该进程处理后事! 主机B接收方挂了 主机A并不知道该主机已经断电! 一直在等待ACK 然后进行超时重传,重传失败几次后就会重新建立连接,最后发现无济于事就断开了连接! 主机A发送方挂了,主机B并不知道是什么原因是A休息了,还是A挂了! 主机B就会时不时发送一个小的报文(不带数据的,只为触发ACK)心跳报探测报文,当A不再放回ACK就认为A出现了问题,就断开连接!
TCP和UDP对比
- TCP适用场景
对可靠性有一定要求(日常大部分开发都是基于TCP) - UDP适用场景
对可靠性要求不高,对于效率要求高(机房中主机间通信,分布式系统)
经典面试题 基于UDP如何实现可靠传输(其实在考TCP) 本质就是在应用层协议中基于UDP实现TCP的机制! 确认应答… UDP是操作系统中已经实现的不能更改,只能更改应用层协议!
网络层(IP协议)
我们知道网络层协议保存着两台设备的IP地址! 所以网络层最重要的协议就是IP 协议! 并且博主学习的就是TCP_IP 协议栈! 所以这里的IP 协议的学习也是重中之重!
我们先来了解一下IP报头格式 IP 协议报头格式:
这里是4个bite 位,当前IP协议的版本号有IPv4 和IPv6 2个版本,所以版本号只有两个取值,0110 和0100 分别表示不同的IP协议版本!
这里的4位首部长度和TCP协议报类似,就是IP 协议报头的长度大小!这里的单位是4字节,也就是说当取值1111 时表示最大的首部长度是15*4,60字节,这里的IP 协议报头中的选项也使得这里的协议报头长度是可变长度的!
这里的8位服务类型,目前只有4位有效! 这四种状态分别吧表示如下含义:
并且这里的TOS同一时刻只能选择一种状态! 比如:你想要传输过程的最大吞吐量,那就无法保证最高的可靠性,只能4选一! 这里的TOS 就相当于切换形态,为了给IP 协议报规划一条合适的线路,这里的合适就是你想要选择的一种传输状态!
这里的16位总长度表示IP 协议报大小最大为64k 字节! 而我们知道TCP 中的协议报大小可能不止64k ! 如果一个超过64k 大小的TCP 协议报交给传输层IP 协议该如何进行分装处理呢?
如果一个报文大小超过了64k ,我们可以将IP 协议报文进行拆包处理! 将一个TCP 协议报拆成多个然后进行传输! 我们知道IP 协议报中保存的数据载荷就是一个TCP 协议报! 我们就可以把这个TCP 协议报拆成多个! 我们就将一个TCP 数据报拆成了多个,交个多个IP 协议报进行传输! 问题又来了,我们传输过去后,如何确认那几个IP 协议报传输的是一个TCP 数据报呢?
我们通过下面3个标识位的学习就可以将一个TCP 协议数据报进行重新组装!
传输相同的TCP 协议报,IP 协议报头的16位标识位相同,通过这个16位标识位,我们就可以知道那几个IP 协议报是一个TCP协议数据报! 就好比快递一个包裹装不下,那就拆成了2个包裹,当时这两个包裹具有相同的快递单号!
第一位保留(现在IP协议报头还没想好怎么用) 第二位为1,表示禁止分片,就是禁止拆分,如果报文长度超过了MTU (数据链路层中一个以太网数据帧所能承受的最大数据范围),就丢弃该报文! 第三位表示更多分片,就是刚刚当一个协议报拆成多个协议报时,当该IP 协议报中的该位为1表示该协议是这个TCP 协议数据报拆包后的最后一个IP 协议报,否者该位为0!
是分片相对于原始IP报文开始处的偏移。其实就是在表示当前分片在原报文中处在哪个位置。实际偏移的字节数是这个值 * 8 得到的。因此,除了最后一个报文之外,其他报文的长度必须是8的整数倍(否则报文就不连续了)。 简单讲就是这个片偏移可以知道该IP协议报在原来未拆包前报文的位置信息,然后再重新组包时才能将数据组织回去!
(Time To Live):这里的TTL 表示一个IP 协议报设备之间最大的转发次数,一般设为128或者64,我们知道两个设备之间的通信要经过中间很多台设备的传输过程,一次传输过程就是一次转发经过一个路由器或者交换机TTL就减一!如果TTL的值减小到0,我们就视为该数据永远也传输不到目的IP,就将其丢弃!就好比我们发送的目的IP压根不存在,那无论如何转发传输到到达不了,防止出现路由循环!
8位协议的取值就表示这个IP 协议数据传输的数据载荷传输层是应用的那种协议!不同传输层的协议类型有不同的取值.
16位首部校验这里采用的是CRC 算法进行校验,来验证IP头部是否破损!
就是发送方的IP地址
接收方的IP地址
我们知道IP 地址是由32位比特位构成(这里针对的是IPv4 版本)! 我们通常采用点分十进制的格式进行展示! 将32位比特位分成4组每组表示一个字节,然后每组之间用: 分割开! 例如:192.168.107.89 这就是一个IP地址,但是在计算机内部还是采用2进制格式存储!
这里的选项字段长度不定最多40个字节!
我们介绍完了IP报头格式,我们再来了解一下IP 协议报的主要功能! 我们的IP 协议在整个网络传输过程中主要完成两件事情!
地址管理
我们知道IP地址的表示分成4组,每组一个字节! 我们又把这4组数据进行了划分! 前3个字节一般表示网络号,最后一个字节表示主机号! 192.168.107.89
不同的网络号表示不同的局域网!
主机号表示该局域网中的设备编号!
我们通过网络号可以找到该局域网,通过主机号可以找到该局域网下的主机设备!
这里的的地址划分是唯一不变的嘛?如果该局域网下的主机设备超过1个字节大小咋整?
这里的地址划分并不唯一,如果该局域网下的主机过多,一个字节一个无法编号!那就需要有新的划分方式,我们这时就要引入子网掩码这个概念! 子网掩码 子网掩码也是32位!通过子网掩码可以将IP地址进行不同的划分方式! 例如:我的IP地址是 192.168.107.89 我咱知道我前几位表示网络号,后几位表示主机号呢? 通过这个子网掩码255.255.255.0 就可以知道前3个字节表示网络号,最后一个字节表示主机号! 网络号:通过通过1标识(255) 主机号:通过0标识 这里的0和1不能交替,只可能1在前,0在后! 这就是子网掩码地址划分方式
特殊IP地址 根据对地址的划分,我们可以更好的管理IP地址 我们也规定了一些IP 地址具有特殊意义!
- 如果
IP 主机号全为0,该IP就表示网络号(一个局域网中的设备主机号不能全为1) - 如果
IP 的主机号全为1,该IP就是广播地址,通过这个IP,我们可以对整个局域网中的主机进行消息传输,在该局域网中的设备都可以接收到数据,类似于机房的广播! - 如果
IP 地址以127开头,该IP表示环回IP,就是表示自己的主机,典型的环回IP127.0.0.1 - 如果
IP 地址以10开头,192.168开头或者172.16-172.31开头表示该IP地址是一个局域网IP地址,这个IP地址只能由该局域网中的设备访问,其他局域网的设备访问不到这个IP地址! - 要求外网
IP 是唯一的,每个外网IP都会对应到唯一的一台设备,内网IP在一个局域网中是对应到唯一设备的,当时不同局域网中的内网IP可以相同!
我们再思考另一个问题,居然每一台设备都有自己的IP 地址,为啥要区分内网IP和外网IP,大家都整一个不同的IP不就好了? 我们知道IP 地址范围是32个比特位也就是一个int型数据,而最大的表示范围是4294967295 也是是42亿九千万大小! 而如今的世界上的设备远远超过了42亿,如果我们给每一台设备分配一个IP ,势必会有设备的IP重复不唯一!
如何解决这个IP分配问题呢?
让每台设备联网的时候分配一个IP地址,设备休息的时候,就不分配IP,然而这个问题并没有从根本上解决这个问题,设备数量一直在增加!
让多台设备共用一个IP(外网IP) 把网络分成了内网(局域网)和外网(广域网) 要求外网IP只能唯一一台设备 同时同一个局域网的设备共用一个外网IP 所以一个外网IP可能表示上千或者上万台设备! 不同的局域网设备可以具有相同的内网IP 这时IP地址分配压力就缓解了! NAT机制下的重要结论: 有外网IP的设备,可以在互联网中的任何地方都可以访问到! 具有内网IP的设备只能由在这个局域网中的设备访问,其他局域网中的设备访问不到! 例如: 在一个学校就是一个局域网,然后每台设备都有一个内网IP,而这个I只能由在这个学校的局域网中的设备访问! 在不同的局域网下那我们如何将主机A的信息发送给主机B呢? 就比如我们发微信,我们先通过这个内网IP,访问一个共用的外网IP,这里可能是运营商的外网IP,通过运营商的外网IP,我们就可以访问到微信的服务器外网IP,然后将消息发送给主机B! 我们知道同一个局域网下共用一个外网IP,那如果该局域网下的多台设备下的客户端程序系统分配绑定相同的端口号咋整? 我们把具有外网IP的路由器设备叫做NAT路由! 通过NAT路由器可以将两个设备的内网IP和端口号记录下来,然后通过某种映射关系,映射新的端口号,然后消息再传回时,根据开始的映射关系将信息返回不同的设备!
虽然NAT机制目前解决了地址分配的情况,但是这并不是长远之计!毕竟我们IP设计的初衷就是一台设备分配一个IP地址,所以IPv6从根本上解决了这个问题! IPv6版本不同于之前的IPv4版本IP地址只有32位比特位! IPv6版本的IP地址升级具有128位的IP地址!也就是可以保存16位字节大小的数据!直接在IPv4版本42亿9千万的范指数加了个4,就是4个42亿9千万相乘,可想而知范围之大,号称可以将地球上的一粒沙子都编号IP地址! 但是IPv6和IPv4设备并不兼容,需要更改新的设备才能支持IPv6,国家在大力推行IPv6升级!
路由选择
我们知道网络层在网络传输中就相当于快递公司承担着规划路线的作用!我们要规划好合适的线路将该IP地址主机发送的信息传输到另一个IP地址的主机! 如何规划好线路呢?
我们的协议报头中已经保存了发送方和接收方的主机IP,但是我们要选择一条合适的线路,将信息发送过去! 而我们知道网络通信需要经过很多的路由设备,而网络环境复杂,如何选择一条线路到达目的IP地址十分重要! 路由器设备如果知道这个IP地址就可以直接发送过去! 就相当于你要去一个地方,如果你认识路就可以直接过去,如果你不认识路的话只知道大致的方向,那你只能到达一个地方通过问路的方式,继续接下来的前行! 这里的路由器也会通过这种询问的方式,先将数据发送给大致方向的路由器,然后由接下来的路由机继续发送,直到有路由器认识这个IP然后到达这个IP有很多条路线,这时IP协议就会选择合适的线路,这就是路由选择! 路由器如何认识IP呢? 路由器内部维护了一个数据结构路由表 路由表保存了一些网段信息(网络号)目的IP就是通过这些网段信息进行匹配以及每个网段信息对应的网络接口(也就是路由器里面具体的端口),路由器通过相邻的路由设设备保存的路由表信息扩展自己的路由表信息!
数据链路层
数据链路层比较偏底层,我们就大致了解一下! 通过以太网数据帧协议了解数据链路层 以太网数据帧格式:
这里的地址和IP地址不同,称为mac地址(物理地址),每一个设备都有唯一的物理地址(每个网卡都是唯一),这里指的是网卡的物理地址!并且这里的地址范围是6字节所以不存在地址分配问题,保证了每一台设备的物理地址唯一! 这就是博主设备的物理地址, 80-30-49-71-A0-4F 可以看到mac地址有6位每位是一个字节! 这里需要注意区分,这里的mac地址是在数据链路层下使用,而IP地址是在网络层下使用,具有不同的功能和作用不要混淆了! 我们知道数据链路层中的目的地址和源地址随着数据的传输时不断改变的!只是记录当前时刻相邻的设备地址!而IP协议报中的始终不变,一直保存着源IP和目的IP 还有这里每个设备的mac地址是在设备出厂时就固定的!
这里的帧协议类型字段有3种取值对应着IP,ARP,RARP.
这里是帧位的CRC校验码,校验数据是否丢失!
MTU
MTU指的是一个以太网数据帧可以承受的最大数据范围! MTU的取值取决于2个因数
- 硬件设备本身,因为传输链路中不同的设备硬件是不同的!
- TCP/IP报头长度,因为我们知道TCP和IP的报头长度是变长的,如果TCP和IP的报头长度越长,那MTU也就需要相应减小!
MTU对IP的影响 由于MTU范围大小的限制,使得对网络层IP协议也有所制约,之前所说的IP协议数据报拆包大部分情况下是针对这里数据链路层数据传输的限制,通过MTU,IP协议报也能更好的对数据报进行拆包!
就好比上图,发送快递的过程,当达到一个包裹的最大重量,就需要分成多个包裹进行邮寄!这里的IP也一样,当得知数据链路层的MTU可以承受的最大数据范围,就会将IP协议报数据进行拆包! MSS
- TCP的一个数据报也不能无限大,还是受制于MTU。TCP的单个数据报的最大消息长度,称为MSS(Max Segment Size);
- TCP在建立连接的过程中,通信双方会进行MSS协商。
最理想的情况下,MSS的值正好是在IP不会被分片处理的最大长度(这个长度仍然是受制于数据链路层的MTU)。 - 双方在发送SYN的时候会在TCP头部写入自己能支持的MSS值。
- 然后双方得知对方的MSS值之后,选择较小的作为最终MSS。
- MSS的值就是在TCP首部的40字节变长选项中(kind=2);
通俗点讲就是这里的MSS ,TCP在IP不分包的情况下最大的数据载荷量! 我们连接时双方互通MSS 这个值,就可以知道数据链路层在IP协议数据报不分包的情况下可以承受最大的数据载荷量,从而告诉TCP 协议传输数据时,数据报按照这个范围打包数据,在接下了的传输过程就避免了拆包,组装的过程,从而提高了传输速率!
ARP
这里的ARP 并不是用来传输数据的,而是起到一个辅助传输的效果! ARP保存IP 地址和mac 物理地址的映射关系!当我们路由器在转发数据时,首先拿到的是一个IP地址,而我我们需要转发给另一个路由设备,而路由设备的地址是mac地址,这时ARP就可以在以太网数据帧进行封装时,通过ARP协议保存的映射关系,设置源地址和目的地址的值! ARP 协议保存的映射关系从何而来? 当设备启动时,就会向局域网中,广播ARP报文,每个设备接收到后,就会给出一个应答,应答中就包含了自己的IP地址和mac地址,发起广播方就可以通过收集到的映射关系,建立一个映射表!
TCP_IP协议栈还有许多内容等待我们去学习去了解! 加油!
|