本文是网络编程的理论基础,也是网络部分的重点和难点,在笔试,面试中,这部分内容也多有考察.
- 应用层
- 传输层
- 网络层
- 数据链路层
- 物理层
我们自己编写的代码,就是在应用层,这也是在实际开发中接触最多的层.
应用层里有许多现成的协议 , 而在实际工作中 , 我们经常需要"自定义应用层协议" , 也就是自己制定一些规则 , 约定好客户端和服务器按照什么样的格式来传输数据 .
领导下发一个任务 , 从任务下发到任务完成后提交 , 大致需要经历这些步骤 :
我们在上一节中所写的"回显服务器" , 就隐含了应用层协议的约定 , 每个请求都以 \n 结尾 , 每个响应 , 也都以 \n 结尾 . 这是一个非常简单的约定 . 那么约定应用层协议 , 应该从哪些方面入手呢 ?
以在淘宝店铺购买衣服为例 .
1.考虑清楚 , 交互过程要传递的信息有哪些 .
2.考虑清楚 , 这些信息的组织格式 !
注意 :
传输层协议 :
Q ; 如果传输的数据超过了64KB , 怎么处理呢 ?
A:
Q : 校验和怎么工作的 ?
A :
Q : 校验和一致 , 能说明数据100%没问题吗 ?
A :
理论上不行 !
如果一个数据是10 , 那么10=9+1 , 10=8+2 ,都有可能 . 也可能两个错误正好互补 , 导致校验和恰巧正确 . 但由于数据发生传输错误的可能性较低 , 而错误互补导致校验和恰巧正确 , 其概率就更低了 .
在工程上 , 这种可能性就忽略不计了 .
注意 : 只要是数据传输可能出现问题 , 都可能用到校验和的思想 , 而不限于UDP . 例如 , 大型游戏在下载过程中通常附带校验和 , 玩家下载好游戏后 , 可以借助第三方工具重新计算校验和 , 并进行比对 , 确认是否有丢失数据的现象 .
无连接 , 不可靠 , 面向数据报 , 全双工 .
Q : 如何提高UDP传输的可靠性 ?
A : 参考TCP的做法 .
源/目的端口号 : 表示数据从哪个进程来 , 到哪个进程去 ;
32位序号/32位确认号 : 确认应答机制 ;
4位TCP报头(首部)长度 : 指定该TCP报文段到底有多长 ;
6位标志位:
16位窗口大小 : 表示接收缓冲区的剩余空间大小 ;
16位校验和 : 发送端填充 , CRC校验 , 接收端校验不通过 , 认为数据有问题 .此处的校验和既包括TCP首部 , 又包含TCP数据部分 ;
16位紧急指针 : 标识哪部分数据是紧急数据 .
对于可靠性的一些理解
- 可靠传输,指的是发出去的数据 , 对方收没收到 , 我能够知道 , 而不是说发出去的数据 , 对方可以100%收到 .
- 可靠性 != 安全性 .
- 可靠性 : 发出去的数据 , 能明确知道对方是否收到了;
- 安全性 : 指的数据被截获后 , 不容易被理解或篡改 , 主要是一些加密机制 .
确认应答机制 : 是可靠传输中的核心机制 . 其关键就是接收方收到消息之后,给发送方返回一个应答报文(ack,acknowledge) , 表示自己已经收到了 .
B收到A返回的消息 , 此时A(发送方)就可以明确知道B已经收到消息了 .
但是 , 在网络上存在一种问题 , 即"后发先至" .
如果应答报文按照正常的顺序返回 , 那么A自然可以清楚地知道B表达的意思 .
但是如果出现"后发先至"问题 , 就可能会出现表达上的歧义 . 如下图所示 :
这就坏事了 . 那么 , 如何解决后发先至问题呢 ? 可以对消息进行编号 , 明确每一条回复针对的是哪条消息 .
TCP内部是如何实现编号的呢 ?
Q : TCP不是按字节传输吗 , 为什么传输的是数据报呢 ?
A :
传输了报文 != “面向数据报” , 这是两个不同的概念 .
面向字节流还是面向数据报 , 主要影响的是代码的写法 (即影响应用层) !!!
在传输层 , 数据都是以一个一个报文的方式来传输的 .
在确认应答情况下 , 如果没收到ACK , 不是直接放弃 , 而是需要重新再发一遍 , 这就是超时重传 !
网络的环境是非常复杂的 , 有时会出现网络拥堵 , 就可能导致丢包 .
例如打王者农药 , 突然卡了 , 可能是 :
丢包是无差别的 , 任何一个数据报 , 无论是普通报文还是ACK , 都可能会丢包 .
对于发送方来说 , 无法确定是业务数据丢了 , 还是ACK丢了 , 所以发送方只是在达到一定时间后 , 就触发重传 !
超时重传机制 , 发一个数据包 , 丢包 ; 再发一次 , 是否还有可能丢包呢 ? 答案是肯定的 .
假设丢包的可能是1% , 那么连续两次都丢包的可能性就是1% * 1% ; 随着发送数据包的次数增加 , 理论上 , 全部丢包的概率应该是越来越低的 .
丢包操作 , 还有一个超时时间 . 超时时间具体是多少,可以在操作系统内核进行配置 .
则t2 > t1, 这里的等待时间间隔 , 随着时间的推移 , 要越来越大 , 连续两次都没发过去 , 意味着当前单次发送的丢包概率已经相当大了!!
连续重传之后又丢包的次数越多,此时意味着单次发送的丢包概率就更大 . 很可能是网络上遇到了非常严重的故障,短期内恢复不了. 此时发送的再频繁,也没啥卵用~~
所以 , 超时重传也不会无限制的重传下去 . 尝试几次之后 , 仍然无法传输过去 , 此时就会放弃尝试,然后就只能断开连接尝试重连 . 如果重连也还是连不上,就彻底放弃了 .
确认应答和超时重传 , 是保证TCP可靠传输的最核心机制 !
连接管理是面试中最高频的问题 , 是网络知识中最高频的考题 !
TCP是有连接的协议,我们把客户端与服务器建立连接和断开连接的过程 , 称为"三次握手" 和 "四次挥手" !
创建socket对象 , 在实例化时就在建立连接 !
三次握手 , 是一种保证可靠性的机制 , 相当于"投石问路" , 在正式通信之前, 先确定好通信链路是否通畅 ! 如果通信链路不通畅 , 后序大概率要丢包 . 就像地铁 , 早上第一趟车一般都是空车先跑一趟 , 即"投石问路" , 确保交通道路通畅 !
三次握手 , 还可以用于通信双方协商一些重要的参数 , 比如发送的数据序号从哪开始 , 最大报文长度MSS是多少等等 .
三次握手 , 是在最少的次数内验证通信双方的发送能力和接收能力是否正常 !
三次握手的功能总结:
- 保证通信链路畅通 ;
- 协商一些重要参数 ;
TCP断开连接 , 可能在A给B发送FIN时 , B还有数据未读取完 , 所以B一般不会立即断开连接 , 要把未处理完的数据都处理完 . B发送FIN , 属于代码层次的实现 .
连接管理机制图解 :
图上信息涉及到 :
三次握手 , 四次挥手 , 中间数据传输流程 ;
三次握手 , 四次挥手过程中 , TCP状态转换 ;
是提高传输效率的机制 , 本质是把等待ACK的时间重叠起来 , 减少等待时间 , 相当于提高了效率 .
在不等待的前提下 , 最多一次可以发N条数据 , N就是窗口大小 .
Q : 发送的一批数据会不会乱序 ?
A : 有可能 ! 但TCP数据上是带有序号的 , 会在接收缓冲区里进行整队 !
Q : 窗口大小N越大越好吗 ?
A : 传输效率由发送效率和接收效率共同决定 ! N再大 , 框框发送 , 但是接收不过来 , 那也白搭 !
滑动窗口示意图 :
滑动窗口之下 , 可靠性是否受到影响呢 ?
- 确认应答 , 没啥影响 ;
- 超时重传 , 在滑动窗口下 , 出现丢包 , 怎么处理 ?
滑动窗口可以一定程度上提升效率 , 补救因成全可靠性带来的低效率问题 , 但是相比无可靠传输(UDP) , 其效率还是会逊色一些 .
流量控制,就是把接收缓冲区剩余空间的大小,作为下一次发送时的窗口大小.是在针对发送速率进行制约;本质上是对滑动窗口的制约.
我们谈到 , 整体的传输速率 , 取决于发送速率 + 接收速率 .
如果发送速率 > 接受速率 , 此时一味提高发送速率 , 是不能提高整体效率的 , 反而会因为接收方丢包 , 触发更多的重传 , 导致速率降低 , 赔了夫人又折兵 !
我们所希望的 , 是发送速率和接收速率在步调一致的前提下 , 尽可能加快 !
发送速率 :发送数据时滑动窗口的大小可以用于衡量发送速率 .
接收方如何把接收缓冲区剩余空间的大小告知给发送方呢 ? 可以在ACK报文中带上这个信息 .
我们可以将接收缓冲区类比为一个水池 .
流量控制,是站在接收方的角度,控制发送速率;而整体的传输,不仅有发送方和接收方,还有中间一系列用来转发的设备!拥塞控制就是通过实验的方式,找出合适的窗口大小,而中间设备的转发速率会影响到实验结果.
拥塞控制的实验方法是 :
- 刚开始按照小的窗口来发送;
- 如果不丢包,说明网络中间环境比较畅通 , 此时逐渐放大发送窗口的大小;
- 放大到一定程度 , 速率已经很快了 , 网络上容易出现拥堵 , 进一步丢包 ! 此时就减小窗口大小.
反复在2和3之间循环,最终达到一个动态平衡.此时发送速率可以达到能承载的极限,同时也可尽量减少丢包,还可以适应网络环境的动态变化.
拥塞窗口的变化如下图所示 :
Q : 主机甲和乙已建立了TCP连接,甲始终以MSS=1KB大小的段发送数据,并一直有数据发送;乙每收到一个数据段都会发出一个接收窗口为10KB的确认段。若甲在t时刻发生超时时拥塞窗口为8KB,则从t时刻起,不再发生超时的情况下,经过10个RTT后,甲的发送窗口是 ?
A :
1、把慢开始的门限值设为当前窗口的一半,即ssthresh=1/2 *8KB=4KB,
2、把拥塞窗口cwnd设置为1个最大报文段MSS大小,
3、再次从慢启动阶段开始。发生拥塞后开始慢启动 cwnd=1KB,之后呈指数增长。
经过1个RTT cwnd=2^1=2KB
经过2个RTT cwnd=2^2=4KB, 此时到达门限值ssthresh,之后 进入拥塞避免 阶段
经过3个RTT cwnd=4+1=5KB , 由于题目说之后一直都没有发生超时,cwnd会一直线性增长
经过10个RTT cwnd=4+8=12KB
发送窗口大小=min(接收窗口,拥塞窗口)=10KB。
总结 :
延时应答是一种提高效率的机制,在网络传输中,窗口越大,网络吞吐量就越大,传输效率就越高.延时应答就是尽可能地使窗口增大.
Q : 所有的包都可以延迟应答么?
A :
在延时应答下 , ACK不一定要和发送的数据报一一对应 , 比如2001就涵盖了1001 , 4001就涵盖了3001…
而根据上面所介绍的延时应答机制 , ACK延时一会儿 , 就可能和返回相应数据时间上重合 , 那么这俩就可以一起发给客户端 .
在捎带应答机制作用下 , 四次挥手也可能变成三次 !
面向字节流,指的是读写载荷数据的时候,是按照"字节流"的方式来读取的,TCP数据报本身仍然是一个一个"数据报"这样的方式来传输的.
面向字节流最核心的问题 : 粘包问题 !
粘包问题的解决方案 :
这都是在自定义应用层协议 , 通过上述方式 , 就可以明确从哪里到哪里是一个完整的应用层数据报 !
1.主机关机 (按照固定程序关机)
按照程序关机 , 会先杀死所有的用户进程 , 释放进程PCB , 释放文件描述符表上对应的文件资源(相当于调用close) . 这时会触发FIN开启四次挥手的过程 , 如果四次挥手完成 , 正常关机即可 ; 如果四次挥手没有完成 , 就已经关机 , 此时需要对端重传FIN若干次 , 如果没有响应 , 就放弃了 .
2.程序崩溃 , 同上 , 无论程序是正常关闭 , 还是异常崩溃 , 都会释放PCB及文件描述符表 ,都会进行四次挥手 .
3.主机掉电(突然拔电源 , 都别玩)
如果是笔记本电脑 , 因为有内置电源 , 影响不大 ; 如果是台式机等等 , 直接就没了 , 也来不及挥手 . 掉电又可分为两种情况 :
注意 : 应用程序里有时也会实现"心跳" .
4.网线断开 : 和主机掉电相同 .
- 如果关注传输数据的可靠性 , 优先考虑TCP ;
- 如果传输的单个数据比较大(超过64KB) , 优先考虑TCP ;
- 传输的数据对于可靠性要求不高 , 而对性能要求很高 , 使用UDP , 如同一个机房内部的主机之间的通信 ;
- 如果需要进行广播 , 优先考虑UDP , 一个发送方 , N个接收方 . UDP直接往广播IP上发消息 , 局域网里的设备就都能收到 , TCP只能通过建立多个连接在应用层实现 .
有些场景下 , 既需要可靠性 , 又关注效率 , 此时可以考虑其他的一些传输层协议 ,如KCP…
网络层协议的工作:1.地址管理;2.路由选择(规划路径). 网络层中,最核心的协议就是IP协议!
4位版本号 : 当前IP协议的版本 . 4 -> IPv4 6 -> IPv6
4位首部长度 : IP报头的长度 ! 是带有选项字段的 , 可以有 , 也可以没有 ; 可以有一个 , 也可以有多个 ! IP头部的长度是多少个32bit , 也就是length*4的字节数 , 4bit表示的最大数字是15 , 因此IP头部最大长度是60字节 , 最短是20字节 .
16位总长度(total length):IP数据报整体占多少个字节。(报头+载荷) . UDP是被64KB限制死了 , UDP要想传输更大的应用层数据 , 需要在代码中手动拆包组包 . 当前的IP协议 , 自己内置了拆包组包功能 , 如果搭载了太长的TCP数据报 , 此时IP就会分包 , 每个包来携带TCP的一部分数据 !!
16位标识(id):唯一的标识主机发送的报文。如果IP报文在数据链路层被分片了,那么每一个片里面的这个id都是相同的。
3位标志字段:第一位保留(保留的意思是现在不用,但是还没想好说不定以后要用到)。第二位置为1表示禁止分片,这时候如果报文长度超过MTU,IP模块就会丢弃报文。第三位表示"更多分片",如果分片了的话,最后一个分片置为1,其他是0。类似于一个结束标记。
13位分片偏移(framegament offset):是分片相对于原始IP报文开始处的偏移。其实就是在表示当前分片在原报文中处在哪个位置。实际偏移的字节数是这个值 * 8 得到的。因此,除了最后一个报文之外,其他报文的长度必须是8的整数倍(否则报文就不连续了)。
8位生存时间(Time To Live,TTL):数据报到达目的地的最大报文跳数。一般是64。每次经过一个路由,TTL -= 1,一直减到0还没到达,那么就丢弃了。这个字段主要是用来防止出现路由循环。
IP地址本质上是一个32位整数 . 因为32位整数看起来不方便 , 因此就发明了一种表示方式 , 即点分十进制 . 使用三个点把32位的整数分成4个部分 , 每个部分占1个字节 , 每个部分取值范围是0~255 .
现实生活中 , 地址一般都是唯一的 , 那能否为每个主机分配一个IP地址呢 ? 32位整数可表示的范围是42亿九千万 , 这个数字是不够用的 . 那么如何解决IP地址不够用的问题呢 ?
1.动态分配IP地址 . 一个设备上网 , 就分配 ; 不上网 , 就不分配 .
2.NAT IP地址转换 .
注意 : 同一局域网中 , IP地址不能相同 ; 不同局域网中 , IP地址可以相同 .
3.IPv6 , 解决IP地址不够用的终极方案 .
IP地址是一个四字节的整数 , 为了更好地进行组网 , 又对这个IP做出了一些更详细的划分 ~~ 把一个IP分成两段 , 前一半叫做网络号 , 后一半叫做主机号 .
现在主流的划分方式 , 是引入子网掩码 .
类似于地图寻路 . 作为一个路痴 , 我出门一般会使用"高德地图" , 而类似于"高德地图"这样的工具 , 已经把整体的位置信息都收集好了 , 即明确知道起点在哪 , 终点在哪 , 中间都有哪些东西 , 即有很大的存储空间来保存地址信息 . 而在路由器上进行路径规划时 , 则没有这么多空间用于保存全局的信息 , 每个路由器只能知道位置信息的一部分 , 例如路由器只知道和他相邻的一些设备 , 怎么走 .
路由器这里的数据转发 , 就类似于没有"高德地图"之前的寻址方式 , 就是"开局两条腿 , 问路全靠嘴" . 比如我要去"西苑山庄" , 此时先问路人甲 , 他告诉我可以先到"怡和阁小区" ; 到达"怡和阁小区"后 , 我再问路人乙 , 他告诉我下一步可以到"天籁宫" ; 到达"天籁宫"后 , 我问问门口的大黄 , 他告诉我向西继续前进 , 就能到达目的地->“西苑山庄"了 ! 每一次问路 , 就相当于经历了一次"路由转发” . 每个人脑子里记住的一些位置信息 , 称为"路由表" . 问路的时候 , 询问要去的"西苑山庄"IP数据报中的"目的IP" , 路由器(路人)就会根据目的IP在路由表中匹配 , 如果匹配到了 , 就会按照指定的方向继续向下转发 ; 如果没有匹配到 , 会有一个默认的方向(下一跳地址 , 路由表中的默认选项) .
路由转发 , 基本过程类似于"问路" , 一跳一跳进行转发 !
数据链路层的作用 : 两个设备(同一种数据链路节点)之间进行传递数据 .
以太网是一种技术标准 , 既包含了数据链路层的内容 , 也包含一些物理层的内容 , 如规定了网络的拓扑结构 , 访问控制方式 , 传输速率等 .
以太网帧格式 :
注意 : mac地址是身份证号, ip地址是邮编你一生下来, 身份证号就已经定了,你换个地方, 每个地方的邮编都不一样 , 这就是区别 .
路由器进行相邻节点转发这个过程中 , 需要能够建立好一套转发的规则 , (转发表) , 使用ARP和RARP主要是用来在转发之前 , 把转发表给构造好 .
域名系统(Domain Name System,DNS)是Internet上解决网上机器命名的一种系统。就像拜访朋友要先知道别人家怎么走一样,Internet上当一台主机要访问另外一台主机时,必须首先获知其地址,TCP/IP中的IP地址是由四段以“.”分开的数字组成(此处以IPv4的地址为例,IPv6的地址同理),记起来总是不如名字那么方便,所以,就采用了域名系统来管理名字和IP的对应关系。域名可以通过DNS系统自动转换成对应的IP地址 .
最早的DNS系统 , 是一个文件 , 称为hosts文件 .
现在的网站成千上万,不可能把所有的映射关系都写到文件中.因此,更科学的办法,是使用专门的DNS服务器来保存这个文件.哪个电脑需要DNS解析,就访问这个DNS服务器即可.
全世界需要上网的设备这么多,都请求DNS服务器,DNS服务器能抗住这么大的访问量吗? 每个服务器 , 在给客户端提供服务的时候 , 都需要消耗一定的硬件资源(CPU , 内存 , 网络带宽…) 并且每个服务器能提供的硬件资源是有上限的 .
我们的电脑要想正确上网 , 就需要配置好使用的DNS服务器 , 一般来说DNS服务器的地址都是自动获取到的 . 当然也可以手动配置 .
经典面试题 : 从浏览器输入URL开始 , 到最终看到页面 , 中间都发生了哪些事情 ?
本文内容到此结束 !