网络原理,进一步了解网络是如何工作的~~
按照网络协议这几个层次来展开分为五点:
- 应用层(重点介绍)
- 传输层(重点介绍)
- 网络层(跳过)
- 数据链路层(跳过)
- 物理层(跳过)
1.应用层重点协议
1.1DNS
DNS,即Domain name System,域名系统。DNS是一套从域名映射到IP的系统
TCP/IP中使用IP地址来确定网络上的一台主机,但是IP地址不方便记忆,且不能表达地址组织信息,于是人们发明了域名,并通过域名系统来映射域名和IP地址。
域名是一个字符串,如 www.baidu.com,
域名系统为一个树形结构的系统,包含了多个根节点。其中:
- 根节点为根域名服务器,最早IPv4的根域名服务器全球只有13台.
- 子节点主要由各级DNS服务器,或者DNS缓冲构成
网络通信发送数据时,如果使用目的主机的域名,需要先通过域名解析查到对应的IP地址:
- 域名解析的过程,可以简单的理解为:发送端主机作为域名系统树结构的子节点,通过域名信息,从下到上查找对应IP地址的过程。如果到根节点(根域名服务器)还找不到,即找不到该主机。
- 域名解析使用DNS协议来传输数据。DNS协议时应用层协议,基于传输层UDP或TCP协议来实现。
1.2NAT
【技术背景】
在IPv4协议中,IP地址的大小只有4个字节,意味着总数量只有42亿,但是目前互联网的设备已经超过了这个数字。
NAT计数当前解决IP地址不够用的主要手段,是路由器的一个重要的功能:
- NAT能够将私有IP对外通信时转化为全局IP。也就是一种将私有IP和全局IP相互转化的技术方法
- 很多学校,家庭,公司内部采用每个终端设置私有IP,而在路由器或者必要的服务器上设置全局IP
- 全局IP要求唯一,但是私有IP不需要;在不同的局域网中可以出现相同的私有IP是不受影响的
1.3NAPT
那么问题来了,如果一个学校是在一个局域网中,使用一个全局IP,但是同学A和同学B同时访问不同的网站,对于服务器返回的数据中,目的IP是相同的。那么NAT路由器是如何判断哪个数据包是哪个主机的呢?
这时候NAPT来解决这个问题了,使用了IP+port来建立这个关联关系。
这种关联关系也是由NAT路由器自动维护的。例如在TCP的情况下,建立连接时,就会生成这个表项;在断开连接后,就会删除这个表项
【技术缺陷】
由于NAT依赖这个转换表,所以有诸多限制:
- 无法从NAT外部向内部建立连接;
- 转化表的生成和销毁都需要额外开销;
- 通信过程中一旦NAT设备异常,即使用存在过热,所有的TCP连接都会断开;
【课外扩展连接】
电子监听、全国断网,棱镜门背后,中国如何从末路狂奔到世界之巅_哔哩哔哩_bilibili
2.传输层协议(重点)
2.1UDP协议
2.1.1UDP协议端格式
- 16位UDP长度,表示整个数据报(UDP首部+UDP数据)的最长长度;
- 如果检验和出错,就会直接丢弃此数据。
- 两种表达方式表示的都是一个意思。
2.1.2报头部分解析
【源端口和目的端口】
源端口和目的端口,各占16位大小。表示数据从哪里来?到哪里去?
【UDP长度】
表示这个数据报的长度,大小为16个比特位,这也导致了UDP数据报的最大大小为64K。
这个大小在当初设计的时期是够用的,但是随着技术的发展,数据所占的空间越来越大,这也导致了UDP数据报所每次传输数据的大小,满足不了人们的需求。
【校验和】
网络传输是一个非常复杂的过程,很容易受到外界的干扰。比如通过网线传输,如果外部存在强磁场,就可能导致数据的损坏或丢失。
为了检查我们传输的数据是否出错,我们引入了校验和来判断。如果校验和对,数据大概率是正确的;如果校验和不正确,数据肯定是错误的。
为了让校验和能够识别率更高一点,计算的时候通常会以数据的内容作为参数来进行计算。如果数据改变了,校验和也会变化
比如我们收到一个数据报,收到的校验和为1,但是我们根据内容进行计算校验和是23,就表示这个数据是错误的。
2.1.3UDP的特点
UDP的传输有点类似于寄快递。
【无连接】
知道对端的IP和端口号就直接进行传输,不需要建立连接。
【不可靠】
没有任何安全机制,发送端发送数据报以后,如果因为网络等原因导致对方没有接收到,UDP协议层也不会给应用层发任何的错误消息。
【面向数据报】
应用层交给UDP多长的报文,UDP原样发送,不会进行拆分,也不会进行合并。
比如:我们使用UDP发送100字节大小的数据报,发送端一次发送100个字节,接收端也必须一次接收100个字节
【缓冲区】
UDP只有接收缓冲区,没有发送缓冲区:
UDP没有真正意义上的发送缓冲区。发送的数据会直接交给内核,由内核将数据传给网络层并完成后序的传输动作。
UDP就有接收缓冲区,但是这个接收缓冲区不能保证UDP报的顺序和发送的向后顺序一样,比如我们发送:(你好!)(很高兴认识你!)。由于网络环境是很复杂的,可能出现先发送后到达的情况,在一些场景中这是很严重的问题。比如发送(你愿意做我女朋友吗?)(你愿意和我一起看电影吗?),在接收的时候顺序发生了改变,同样回答每个问题的答案也会导致乱序;如果缓冲区满了,在到达的UDP数据就会被丢弃。
UDP的socket既能读又能写,这个概念被称为全双工。
【大小受限】
UDP协议首部中有一个16位的最大长度,最大空间为65536个字节。也就是说一个UDP数据报能传输数据的最大长度为64K(包含UDP首部)。
【基于UDP的应用层协议】
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备)
- DNS:域名解析协议
2.2UDP面试题
1.UDP本身是无连接的,不可靠的,面向数据报的协议,如果要基于传输层UDP协议,来实现一个可靠的传输,应当如何设计?
发送的使用引入发送的时间,来保证发送的先后的顺序不会出错。同时我们引入一个确认应答的机制,在接受方,接收到消息后,要进行应答。另外如果发送方迟迟得不到接收方的应答,应当进行超时重传的操作。
2.UDP大小是受限的,如果要基于传输层UDP协议,传输超过64K的数据,我们将如何设计?
我们引入将数据拆分成小于64K大小的数据,然后在发送的时候引入序号,在接收的时候,我们可以根据序号进行排序。
2.3TCP协议
TCP,即Transmission Control Protocol,传输控制协议。要对数据的传输进行一个详细的控制。
2.3.1TCP协议段格式
- 源端口/目的端口号:表示数据是从那个进程来,到哪个进程去;
- 32位序号/32位确认号:后面讲;
- 4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4个字节);所以TCP头部的最大长度为15*4=60
- 6位标志位:
- URG:紧急指针是否有效
- ACK:确认号是否有效
- PSH:提示接收端应用程序立即从TCP缓冲区把数据读走
- RST:对方要求重新建立练连接;我们把携带RST标志的称为复位报文段
- SYN:请求建立连接;我们把携带SYN标识的称为同步报文段
- FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
- 16位窗口大小:后面再说;
- 16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的校验和不光包含TCP首部,也包含TCP数据部分。
- 16位紧急指针:标识哪部分数据是紧急数据;
- 40字节头部选项:暂时忽略
2.3.2TCP原理
TCP对数据传输提供的管控机制,主要体现在两个方面:安全和效率。
这些机制和多线程的设计原则类似:在保证数据传输安全的前提下,尽可能地提高传输效率。
【确认应答机制】(安全机制)
TCP将每个字节地数据都进行了编号,即为序列号。
每一个ACK都带有对应地确认序列号,意思是告诉发送者,我已经收到了哪些数据;下次你从哪里开始发送。
如果我们需要发送地数据是1-10 000,主机B收到了1-1000,1001-2000,3001-4000,主机A下一步将如何发送呢?
主机A在发送一段时间后,发现确认应答地仍然是2001,将重新发送2001的部分;主机B收到2001的数据后,将确认应答,要求发送4001的部分(因为原来发送的数据是已经被接收到了,在接收缓冲区内,并没有丢失,在补全前面的数据后将继续接受后面的数据)
【超时重传机制】(安全机制)
- 主机A发送给B数据之后,可能因为网络的原因,导致数据无法到达主机B
- 如果主机A在一个特定时间间隔内没有收到主机B发来的确认应答,就会进行重新发送
但是,主机A未收到B发来的确认应答,也可能因为ACK丢失了
那么超时的时间如何确定呢?
- 最理想的情况下,找到一个最小的时间,保证“确认应答一定能在这个时间内返回”;
- 但是这个时间的长短,随着网络环境的不同,是有差异的。
- 如果超过时间设定的太长,会影响到整体的传输效率;
- 如果超时时间设定的太短,有可能会频繁的发送重复的包。
TCP为了保证无论在什么情况下都能比较高性能的通信,因此会动态的计算这个最大超时时间。
- 在Linux中(BSD Unix和Windows也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
- 如果重发一次之后仍然得不到回应,等待2*500ms后再进行重传。
- 如果仍然得不到答应,等待4*500ms进行重传。依次类推,以质数形式递增。
- 累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制断开连接。
【连接管理机制】(安全机制)
在正常的情况下,TCP要经过三次握手建立连接,四次挥手断开连接
- syn被称为同步报文段,请求与对方建立连接;ack是确认报文,表示同意建立连接。
- 第一步:客户端会首先发送syn要求服务端建立连接。
- 第二步:服务端收到客户端的信息后,立即回应ack,并也要求建立连接syn
- 第三步:客户端收到服务端的请求后,也发送ack进行回应。
【不恰当的栗子】小明跟小红表白。
小明说:我能做你的男朋友吗?
小红说:可以,我能做你的女朋友吗?
小明说:可以。
但是为什么小红同意了,小红还要问小明呢?我们在举个栗子。
【栗子二】小明和小红连麦打游戏。
小明问:喂喂喂,你能听见吗?
小红说:能听见,你能听见我说话吗?
小明说:能听见。
第一次小红的回答表示,小明的麦和自己的音响没有问题。第二次小明的回答表示小红的麦和自己的听筒没有问题。这样才能确认双方的连接是正常的。
为什么要三次握手?
相对于UDP,TCP是可靠的通信协议,是全双工通信,TCP的三次握手的真正的关键在于,序列号的确认,对于客户端来说,发送syn,并接受到ack,表示自己的发送和接受是正常的;客户端syn是想检测自己的发送和对方的接受是否正常。在建立连接以后,就能正常进行通信了。
- fin被称为结束报文
- 第一步:客户端发送fin
- 第二步:服务端收到fin,立即进行应答ack
- 第三步:服务端在执行到socket.close()时,发送syn
- 第四步:客户端立即进行应答ack
【不恰当的栗子】小明和小红要分手。
小明说:我不想做你的男朋友了。
小红说:行。
小红思考再三也说:我也不想做你女朋友了。
向明说:行。
为什么握手是三次,中间的syn和ack能100%合并?而挥手确实四次挥手不一定100%合并?
三次挥手,ack和syn都是同一个时机进行触发的,都是由内核完成。
而四次挥手,ack和fin是不同时机触发的。ack是在接受到fin的时候立即回复ack,是由内核完成的;而fin是应用代码控制的,在调用socket的close方法,才会发送fin。而服务端的socket.close()不知道什么时候才能执行到,如果中间还有其他代码,就不会合并发送。(看程序猿怎么实现)
【滑动窗口】(效率机制)
我们刚刚谈论了确认应答的策略,大于每一个发送的数据段,都要给一个ack确认应答。收到ack后,发送下一个数据段。这样就会造成一个问题:效率低下。在往返的途中的需要消耗时间的。
解决方案:我们一次发送多条数据,就可以大大提高性能(其实就是将多个段的等待时间堆叠在一起)
- 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图中窗口的大小是4000个字节(4个段)
- 发送前四个段的时候,不需要等待任何ack,直接发送;
- 收到第一个ack后,滑动窗口向后移动,继续发送第五个段的数据;依次类推;
- 操作系统的内核为了维护这个滑动窗口,需要开启发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答过的数据,才会从缓冲区删除掉。
- 窗口越大,则网络的吞吐量就越高;
如果出现了丢包,我们将如何进行重传呢?这里要分为两种情况。
情况一:数据包到达,但是ack丢失了。
这里我们丢失了第一个ack,但是数据已经被收到了,此时并不用重传。后续的ack可以表示它已经收到了之前的数据。比如第四个ack返回的32位确认序号是4001,表示它需要的是4001之后的数据,而前面的数据它已经收到了。
情况二:数据包直接丢失了。
- 当某一段报文段丢失之后,发送端会一直收到1这样的ACK,就像是在提醒发送端“我想要的是1以后的数据”
- 如果发送端主机连续三次收到了同一个“1”这样的应答,就会将对应的数据1-1000重新发送
- 这个时候接收端在收到1-1000的数据后,返回的ack就是4001,因为4001以前的数据接收端已经收到了,被放到了接收端操作系统内核的接受缓冲区中
这种机制被称为“高速重发控制”(也叫“快重传”)
【流量控制】(安全机制)
接收端处理数据的速度是有限的。如果发送端发的太快,而接收端的缓冲区被填满了,这个时候如果继续发送,就会造成丢包问题,继而引起丢包重传等一系列连锁反应。
因此TCP根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做流量控制
- 接收端将自己可以接收的缓冲区的大小放入TCP首部中的“窗口大小”字段,通过ack端通知发送端;
- 窗口大小字段越大,说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了,就会将窗口的大小设置称一个更小的值通知给发送端;
- 发送端接收到这个窗口之后,就会减慢自己的发送速度;
- 如果接收端缓冲区满了,就会将窗口设为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据端,使接收端把窗口的大小高速发送端。
接收端如何把窗口大小告诉发送端呢?回忆我们的TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息;
那么问题来了,16位数字最大表示65535,那么TCP窗口最大窗口大小就是65535字节吗?
实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小使窗口字段的值左移M位。
【拥塞控制】(安全机制)
虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据。但是如果在刚刚开始阶段就发送大量的数据,仍然可能引发问题。
因为网络上有很多的计算机,可能当前的网络状态就比较拥堵。在不清除当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。
TCP引入了慢启动机制,先发送少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;
- 此处引入了一个概念称为拥塞窗口
- 发送开始的时候,定义拥塞窗口大小为1;
- 每次收到一个ACK应答,拥塞窗口加1;
- 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做对比,取较小的值作为实际发送的窗口;
像上面这样的拥塞窗口增长速度,是指数级别的。“慢启动”只是指初始时慢,但是增长速度非常快。
- 为了不增长的那么快,因此不能使拥塞窗口单纯的加倍。
- 此处引入一个叫做慢启动的阈值
- 当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长
- 当TCP开始启动的时候,慢启动阈值等于窗口最大值
- 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置为1
少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;
当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立即下降
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大的压力的这种方案。
【延迟应答】(效率机制)
如果接收数据的主机立即返回ACK应答,这个时候可能窗口的大小比较小。
- 假设接收端缓冲区为1MB,一次接收到了500K的数据,如果立即应答,返回的窗口大小为500K;
- 但实际上可能处理端处理的数据的速度特别快,10ms内就能把500K的数据从缓冲区消费掉;
- 在这种情况下,接收端远没有到达自己的极限,即使窗口再放大一些,也能处理过来;
- 如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M
一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高。
那么所有的包都可以延迟应答吗?肯定不是;
- 数量限制:每隔N个包就要应答一次;
- 时间限制:超过最大延迟时间就应答一次
具体的数量和超时时间,依照操作系统有所差异;一般N取2,超时时间取200ms;
【捎带应答】(效率机制)
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是“一收一发”的。意味着客户给服务器说了“How are you",服务端也会给客户端回一个”Fine,thank you";
那么这个时候ack可以搭顺风车,和服务器回应的“Fine,thank you"一起回给客户端。
【其他特性:面向字节流】
【其他特性:缓冲区】
【其他特性:大小限制】
创建一个TCP的socket,同时在内核中创建一个发送缓冲区和一个接收缓冲区;
- 调用write时,数据会先写入发送缓冲区中;
- 如果发送的字节数太长了,会被拆分成多个TCP的数据包发送;
- 如果发送的字节太短了,就会先在缓冲区里面等待,等到缓冲区长度差不多了,或者其他合适的时机发送;(所以在我们写代码的时候,发送TCP数据包,我们需要收到刷新缓冲区)
- 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区;
- 然后应用程序可以调用read从接收缓冲区拿数据;
由于缓冲区的存在,TCP程序的读和写不需要一一匹配,例如:
- 写100个字节数据时,可以调用一次write写100个字节,也可以调用100次write一次写一个字节;
- 读100个字节的时候, 可以调用一次read读100个字节,也可以调用100次read一次读一个字节.
2.4粘包问题
- 首先要明确,粘包问题中的“包”,是指的应用层的数据包。
- 在TCP的协议中,没有如同UDP一样的“报文长度”这样的字符串,但是有一个序号这样的字段。(这个序号,并不是表示这个信息的整体大小,主要用于数据之间的排序)
- 站在传输层的角度,看到的只是一串连续的字节数据。
- 那么应用程序看到了这么一连串的字节数据,我们将如何区分开一条数据是从哪里开始到哪里结束的呢?
【栗子】我们在聊天软件上给对方发送了两条信息,一条是:“是”,一条是:“不是”,两条信息发送的间隔时间不长。在接收端的就收缓冲区就变成了:“是不是”。我们要如何区分一条信息从哪里到哪里呢?
那么如何避免粘包问题呢?归根结底就是一句话,明确两个包之间的边界。
- 对于定长的包,保证每次都按照固定大小读取即可。
- 对于不确定长度的包,可以在包头的位置,约定一个包总长度的字段从而知道了包的结束位置;
- 对于不确定长度的包,还可以在包和包之间使用明确的分割符(应用层协议,有程序猿自己确定,比如"<"为信息开始的位置,“>"为信息结束的位置
思考:对于UDP协议来说,是否也存在着“粘包问题”呢?
- 对于UDP,数据报的长度确定的,我们可以根据给出的长度来区分不同的信息
- 对于UDP,我们在发送的时候是一次全部发送,一次全部接收,并不会进行拆分,所以一次接收的就是完整的信息。
2.5TCP异常情况
进程终止:进程终止会释放文件描述符,仍然可以发送FIN。和正常关闭没有区别。
机器重启:和进程终止的情况相似。
机器掉电/网线断开:接收端认为连接仍在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset。即使没有写入操作,TCP自己也内置一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放。
另外,在应用层的某些协议,也有一些这样的检测机制。例如HTTP长连接中,也会定期检测对方的状态。例如QQ,在QQ断线之后,也会定期尝试重新连接
3.网络层重点协议
在复杂的环境中确定一个合适的路径。