当应用程序间需要进行网络通讯,在 TCP/IP 五层协议的应用层需要做的工作是,程序员 自定义应用层协议 。
当前要开发一个关于“点外卖软件”的一个项目,其中有一个功能为:获取用户的订单历史(后端服务器从数据库中取出数据返回给前端页面),类似像这样的功能,就需要涉及到前后端彼此之间的交互。
前后端交互是基于网络进行交互的,那么在这个交互的过程中就需要约定好:前端发啥样的数据,后端返回对应的数据:
形如上述的工作,就是在设计一个应用层协议。
xml
json
protobutter
)xml 通过这些标签,很好的体现了对应数据的可读性,很明显就可以读懂每个部分表达什么意思。但是也是由于这些标签,需要引入太多的辅助信息。
我们要知道,对于一个服务器程序,最贵的是硬件资源,就是网络带宽。对于 xml 来说,因为要表示这些辅助信息,就导致传输相同数目的请求的时候,占用的带宽是更高的。
json 中通过构建了一个个键值对的方式来进行数据传输。
DNS
:进行域名解析
FTP
:文件传输协议
telnet
:远程终端协议
HTTP
:超文本传输协议,网络可靠交换数据的重要基础
http使用面向连接的TCP作为运输层协议,保证了数据的可靠传输。
SMTP
:电子邮件传送协议
在传输层中最著名的两个协议分别是
UDP
和TCP
,传输层负责的是 “端到端” 之间的通信,在发送方发出的数据,接收方有没有准确无误的接收到,是传输层主要关心的工作。
在学习一个协议,最主要的就是研究这个协议是以什么样的报文格式进行数据传送的,以及它有什么样的功能特点
在传输层封装协议就是给一个完整的应用层数据封装上8个字节的UDP协议格式,这8个字节中的信息用来保证进行客户端和服务器通讯的过程中数据传输的正确和可靠的。
面向数据报
数据传输分为面向数据流和面向数据报,UDP协议是面向数据报的,应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并。
无连接
就比如打电话和寄信两种通话方式,UDP相当于寄信,知道目的程序的IP和端口号就直接进行传输, 不需要建立连接。
不可靠
没有任何安全机制,发送端发送数据报以后,如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息(发送方也不知道信有没有寄到)
全双工
既可以读,也可以写。也就是发送方和接收方身份可以互相转化。
TCP全称为 “传输控制协议(Transmission Control Protocol”),人如其名, 要对数据的传输进行一个详细的控制
TCP是一个非常重要的传输层协议,在实际开发中被广泛使用,我们主要需要了解关于TCP可靠传输的机制。
TCP对数据传输提供的管控机制,主要体现在两个方面:安全和效率。
这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能的提高传输效率。
确认应答机制是保证可靠传输的核心机制,关键就是,接收方收到消息之后,给发送方返回一个应答报文(ACK,acknowledge),表示自己已经收到了。
在网路上,数据接收的顺序不一定和发送的顺序完全一致,会存在后发现至的情况。
TCP 将每个字节的数据都进行了编号。即为序列号:
每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;下一次你从哪里开始发。
超时重传相当于对确认应答机制进行的补充,确认应答是网络一切正常的时候,通过 ACK 通知发送方确认收到,但是如果出现了丢包的情况,发送方就收不到这个 ACK 了。而对于发送方来说,是无法区分由于哪种原因导致没有收到 ACK,稳妥起见,发送方在等待一段时间之后就会进行超时重传,将数据再发一次。
但是有一个问题,如果接收方已经收到了这个数据,只是在返回 ACK 的时候这个应答消息丢包了,此时发送方再发送一次数据,这样是不合理也是不安全的,例如:转账操作。
上述问题 TCP 它在内部用一个去重操作来避免的,接收方收到的数据库会先放到操作系统内核的 “接收缓冲区” 中。
收到新的数据,TCP 就会根据序号来检查这个数据是不是在接收缓冲区中已经存在了,如果不存在,就放进去,如果存在,直接丢弃。当顺利收到 ACK 之后,就可以把这个数据从缓冲区拿出进行下一步的封装传送了~
超时重传是不会无休止的进行重传的,一次重传失败会再尝试,但如果连续两次传输都失败,说明能够顺利发送数据的可能性是比较小了,所以是不会无休止的重传的。连续几次重传都不行,就认为这个网络可能是遇到了严重的问题,发送方就只能放弃了。
基于上述确认应答和超时重传两个机制,TCP的可靠性,得到了有效的保障。
TCP 进行可靠传输最重要的保障机制,也是是网络部分最高频出现的讨论问题,也就是著名的:三次握手,四次挥手。
三次握手是客户端和服务器之间,通过三次交互,完成了建立连接的过程,"握手"是一个形象的比喻。
在TCP报文头中,有以下六个标志位:
在上述 TCP 报文头中已经做了详细介绍。
下图是三次握手的交互过程:
解释:
发送能力
和 接受能力
是否正常。描述TCP三次握手的过程
画上面的最简易的图,理解好那些五颜六色的文字,用自己的话描述出来。
为什么握手是三次?而不是两次?四次?
两次:服务器验证不了自己的发送能力和对方的接受能力,此刻,服务器对于当下能否满足可靠传输心里是没底的,所以需要第三次握手接收以下客户端的 ACK,才能保证通讯是可以进行的。
四次:可以,但没必要。中间一次服务器发送的 SYN 和 ACK 可以合为一次发送。
当浏览器里面输入了 URL 之后,程序是怎么执行的:
① 对 URL 进行校验,如果校验通过(符合http协议),先进性 DNS 解析,找到对应的目的 IP,目的端口号
② TCP 连接(3次握手)
③ 数据封装(发送)
④ 服务器端程序接收到消息,对消息进行解析,通过 Request 对象来获取参数,对参数的正确性和非空性进行校验,才会调用数据库执行业务处理和查询;查询之后得到结果,将结果封装起来发送给客户端
⑤ 客户端会得到服务器端的响应结果,浏览器会使用自己的引擎进行解析和渲染,并最终展示给用户
⑥ 断开 TCP 连接(4次挥手)
在建立好连接之后,接下来要进行数据通信,所以在客户端和服务器的操作系统内核中,都会使用一定的数据结构来保存连接相关的信息。(源IP,源端口,目的IP,目的端口)
那么在通信完成时,就需要断开连接,也就是释放掉关于对方的一些不必要的资源。所以就需要:四次挥手
。
在四次挥手过程中,先由请求断开连接一方首先发送 FIN
(结束报文段) 请求,接收方收到这个断开连接请求时会先有操作系统内核回复一个 ACK
,表示已确认收到请求信息;
接下来服务器也同样会发送一个 FIN
表示同意断开连接并且付出行动(触发代码来close),客户端收到后再次返回一个 ACK
这样就可以有效的断开双方的 TCP 连接了。
三次握手,一定是客户端主动发起的(主动发起的一方才叫客户端)
四次挥手,可能是客户端发起的,也可能是服务器发起的
三次握手,中间两次能合并
四次挥手,中间两次有时不能合并
有时可以合并,有时不可以。主要原因在于,很多时候中间两次的 FIN 和 ACK 发送的时机可能是不同的。
因为:双方通讯时所发送的 ACK 和 SYN 都是操作系统内核负责的;而所发送的 FIN 是用户代码负责的(代码中执行到了 socket.close()
,才会触发 FIN)
服务端状态转化:
客户端状态转化:
滑动窗口存在的意义就是在保证可靠性的前提下,尽量提高传输效率
如果不采取滑动窗口机制,根据以上的确认应答机制,数据传输是如下图进行的
由于确认应答机制的存在,导致每执行一次发送操作,都需要等待一个 ACK 的到达,那么会耗费大量的时间在传输 ACK 了。
那么就引出了:滑动窗口机制,本质就是在 “批量发送数据”,一次发一组数据,然后一起等这一组数据的 ACK。
具体窗口是怎么“滑动”的?
不知道大家有没有发现,上述过程有一个核心的问题:丢包!
那么,在 “滑动窗口” 的背景下,如果 丢包 ,改如何重传呢?
情况一:数据包已经抵达,ACK被丢了。
这种情况下,部分ACK丢了并不要紧,因为可以通过后续的ACK进行确认;
情况二:数据包就直接丢了。
必须处理
注意:
这里的重传只需要把丢了的那一块数据给重传即可,其他的已经到了的数据就不必再重传了。所以,整体的传输效率还是比较高的。
流量控制是滑动窗口的延伸,目的是为了保证可靠传输,因为在 滑动窗口 中,窗口越大,传输速率就越高。而接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。
因此 TCP 支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制(Flow Control)。流量控制的关键,就是得能够衡量接收方的处理速度。
通过 ACK 报文来告知
虽然 B 反馈的窗口大小是 0,但是 A 也不能完全不发数据;A 需要定期的发送一个
探测报文
,探测报文不传输实际的数据,只是为了触发 ACK,用于知道当前 缓冲区的大小是多少。
滑动窗口的延伸,同时也是限制滑动窗口发送的速率。
拥塞控制衡量的是,发送方到接收方这整个链路之间的拥堵情况(处理能力)
相当于流量控制的延伸
流量控制是控制了发送方的传输速率
而延时应答,是想在这个基础上,能够尽量的再让窗口大一些
在 A 发送收据到缓冲区时,B 也同时在从“缓冲区”中拿出数据,这个延时应答,就是为了能够让 B 在这个延时的时间内,多拿走一些数据,这样缓冲区的空间就更大了,A 也就通过返回的 ACK 感知到了一个认为比较大空间,就可以发送更到的数据了。
捎带应答时延迟应答的延伸
客户端和服务器之间的通信有以下几种类型:
因为延迟应答的存在,导致 ACK 不一定是立即返回的。
如果当前的延迟应答导致 ACK 的返回时机和应用程序代码中返回的响应时机重合了,就可以把这个 ACK
和 响应数据
合二为一。
TCP 发送接收数据是面向字节流的,而面向字节流的机制会存在一个问题:粘包问题
TCP 粘包粘的是 应用层数据报,在 TCP 接收缓冲区中,若干个“应用层数据包”混在一起了。
解决方案:关键就是在应用层协议加入包之间的边界。
在进程毫无防备的情况下,突然结束进程,这个时候该进程的 TCP 连接是怎么样的?
TCP 连接,是通过 socket 来建立的,socket 本质上是进程打开的一个文件,文件其实就存储在于:进程的 PCB 里面有个 “文件描述符表”
每次打开一个文件(包括 socket),都在文件描述符表里增加一项;
每次关闭一个文件,都在文件描述符表里进行删除一项。
如果直接杀死进程,PCB 也就没了,里面的文件描述符表也就没了,此处的文件相当于“自动关闭” 了。这个过程其实和手动调用 socket.close() 一样,都会触发四次挥手。
按照操作系统约定的正常流程关闭
正常流程的关机。会让操作系统杀死所有进程,然后再关机。
引入 确认应答 机制
实现序号,确认序号以及去重机制
实现超时重传
…
实际这个问题就是在考 TCP 实现可靠传输都引入了哪些机制~
- 什么时候使用TCP?
对可靠性有一定要求,(日常开发中的大多数情况,都基于TCP)- 如果传输的单个数据报比较长(超过 64k)
首选 TCP- 啥时候使用UDP?
对可靠性要求不高,对于效率的要求更高(机房内部的主机之间通信,分布式系统中,广播就是首选UDP)
在复杂的网络环境中确定一个合适的路径。
进行 1. 地址管理,2. 路由选择
以上所说的 IP 地址 是一个
点分十进制
构成的数据,把 IP 地址分成了两个部分,网络号 + 主机号。
网络号:描述当前的网段信息
主机号:区分了局域网内部的主机
一般要求:同一个局域网里,主机之间的网络号是相同的,主机号不能相同;而两个相邻的局域网(由同一个路由器连接的),网络号也不能相同。
全0
,该 IP 就表示 网络号
,(局域网中的一个正常设备,主机号是不能为0的)全1(255)
,该 IP 就表示 广播地址
,(往这个广播地址上发的消息,整个局域网都能收到)环回IP
,表示主机自己,(127.0.0.1
:环回 IP 中的典型代表)10
开头,192.168
开头,172.16 - 172.31
开头,表示该 IP 地址是一个局域网内部的 IP,(内网IP)外网IP
(直接在广域网上使用的IP)要求外网IP 一定是唯一的,每个 外网IP 都会对应到唯一的一个设备
内网IP 只是在当前局域网中是唯一的,不同的局域网里,可以有相同的 内网IP 的设备。
当前的 IPv 协议,使用的 IP 地址是 32 位整数,32 位整数表示的数据范围:42亿9千万,如果给每个设备都分配一个唯一的 IP,意味着世界上的设备就不能超过 42亿9千万?
路由选择也就是规划路径
,两个设备之间,要找出一条通道,能够完成传输的过程
数据链路层考虑的是相邻节点之间的数据传输。
以太
:本来是物理学上的概念,“以太网”
,不是一种具体的网络,而是一种技术标准;
既包含了数据链路层的内容,也包含了一些物理层的内容。例如:规定了网络拓扑结构,访问控制方式,传输速率等;
以太网数据帧格式比 TCP 和 IP 都简单很多
以太网数据帧的格式中长度 48 位的 MAC 地址(物理地址),它真正做到了
每个设备都是唯一的 MAC 地址
,是在网卡出厂的时候就写死了。
一个以太网数据帧能够承载的数据范围(这个范围取决于硬件设备)
由于数据链路层MTU的限制,对于较大的IP数据包要进行分包。
- 将较大的IP包分成多个小包,并给每个小包打上标签;
- 每个小包IP协议头的 16位标识(id) 都是相同的;
- 每个小包的IP协议头的3位标志字段中,第2位置为0,表示允许分片,第3位来表示结束标记(当前是否是最后一个小包,是的话置为1,否则置为0);
- 到达对端时再将这些小包,会按顺序重组,拼装到一起返回给传输层;
- 一旦这些小包中任意一个小包丢失,接收端的重组就会失败。但是IP层不会负责重新传输数据;
MSS :TCP 中 在 IP 补分包的前提下,最多搭载多少载荷
ARP不是一个单纯的数据链路层的协议,而是一个介于数据链路层和网络层之间的协议,
ARP 报文并不是来传输数据的,只是起到了一个辅助的效果