中山企业网站优化,郑州一建南阳分公司,南昌企业网站设计,关键词推广方法一)应用层协议简介:根据需求明确要传输的信息#xff0c;明确要传输的数据格式#xff1b; 应用层协议:这个协议#xff0c;实际上是和程序员打交道最多的协议了 1)其它四层都是操作系统#xff0c;驱动#xff0c;硬件实现好了的#xff0c;咱们是不需要管 2)应用层:当我… 一)应用层协议简介:根据需求明确要传输的信息明确要传输的数据格式 应用层协议:这个协议实际上是和程序员打交道最多的协议了 1)其它四层都是操作系统驱动硬件实现好了的咱们是不需要管 2)应用层:当我们继续进行写应用程序的时候尤其是应用程序负责网络通信的时候往往是需要自定义应用层协议的我们要根据需求明确要进行传递的信息我们还要明确传递的数据格式 3)应用层这里面最重要的事情就是说自己实现一个应用层协议比较简单也是在工作中经常做的事情 FTPSSHTELNETDNS(域名解析的协议) 1)使用现成的应用层协议来进行开发 2)程序员自己约定一个协议来进行开发只要客户端和服务器自己进行开发的例如我在网页上面给服务器发送了一个请求请求中包含着一个链接然后服务器给浏览器返回了一个响应这个响应就是一个网页在这个过程中使用的协议就是HTTP协议这时就可以 使用自定义协议来约定好请求是什么格式响应是什么格式客户端和服务器就可以按照这样的约定来进行开发了 3)总而言之自定义协议其实是一个很简单的事情只需要约定请求和响应的详细格式即可越详细越好要把各种细节都能够交代到能够更好的表示当前的信息咱们也可以自己来约定好格式也可以基于xmlJson来约定数据格式甚至还可以通过一些其他的二进制的方式来进行约定数据 1)例如我们要提供一个新的需求要求在外卖软件的首页显示一个优惠活动用户参与活动就可以参加抢红包的操作; 2)客户端:修改界面可以显示优惠活动的详情; 3)服务器:修改后台逻辑针对啥样的用户可以参加优惠具体怎么做可以领到红包红包金额是多少; 4)当的客户端启动的时候就要向服务器进行查询当前用户是否可以进行参加活动服务器就要返回是或者否 5)这个时候在开发功能之前就要约定好客户端发的查询请求的功能是怎么样的(带有用户的身份信息)在约定好服务器返回的响应是什么样的(1表示可以表示能参与0表示不能参与true表示能参与false表示不能参与) 1)假设现在自己实现一个功能叫做获取用户的订单历史(这个订单历史在数据库里面我们要从服务器操作数据库来拿这样的功能就会涉及到前后端的一个交互过程)咱们的前端和后端就是通过网络来进行交互的 2)在这个交互的过程中我们就需要约定好前端发送什么样子的数据后端要返回什么样子的数据本质上来说就是在进行规划请求和响应之间的信息以及传数据的格式 咱们进行查历史订单查找谁的订单以及查找哪一个时间段的订单以及数据太多你要不要截取一部分 1)我们前端发送的请求类似于: { 用户的ID 查询的起始时间 查询的结束时间 显示的条数 } 2)我们后端返回的响应: 2.1)查询是否成功如果失败原因都有哪些 2.2)返回一个订单数组: 数组里面的每一个元素: { 商品名 商品单价 商品数量 店铺名称 总的金额 } 像上面的过程就相当于是设计应用层协议咱们设计应用层协议本质上就是做两个工作 1)明确要传输的信息 2)明确数据的组织格式请求的格式是啥样子的响应是啥样子的 1)当前我们只是给出了一种可能的格式此处我们这里面的数据的格式也是可以进行随心所欲的约定的 2)相比之下我们只需要让我们的客户端和服务器都按照一定的格式来进行交互就可以了 3)上面的这一种设计十分不好咱们的输出传输效率比较低这也就说明了是运行效率低 4)咱们的这个代码可读性也不好本身开发效率也比较低正因为如此大佬们发明了一些比较好的协议的模板让我们向上套 XMLJSONprotobuffer前两种可读性比较好但是运行效率不高咱们的第三种可读性不好但是运行效率比较高 1)咱们的XML就是一种老牌的数据格式了虽然现在也在用但是越来越少了咱们的这些标签本身占据的空间已经超过了数据本身所占用的空间也就说引入了更多的辅助信息我们程序的效率就会受到很大的影响对于一个服务器程序来说最贵硬件资源就是网络带宽对于XML来说虽然提高了可读性但是又引入了太多的辅助信息比如说标签名字这些辅助信息所进行占用的空间甚至已经超过了数据本身的一个空间因为在XML里面要经常表示这些辅助信息在传输相同的网络数据的时候这就会导致传输相同数目的请求的时候占用的网络带宽是更高的如果说带宽固定那么相同时间内能够传输的请求数目就是更少的所以说我们程序的效率就会收到很大的影响 2)咱们现在看这个XML标签标签名就是key标签值就是value就构成了一个键值对通过这些标签就更好的体现了我们数据的可读性尤其是哪一个部分代表是什么意思非常一目了然咱们的一个服务器程序最贵的硬件资源就是网络带宽因此XML现在已经很少作为应用层协议的设计模板了现在使用XML主要是为了做配置文件 3)JSON的传输效率虽然要比XML要好一些但是还是要多进行传递一些冗余信息就是Key的名字但是Json格式的数据也是有非常大的缺点尤其是在进行表示数组的时候这里面的Key是在不断重复的这也会占用大量的带宽资源但是咱们的JSON是一种最重要的设计模式 4)因此咱们的protobuffer应运而生这是一种二进制格式的数据在这种格式的数据里面不会再包含上面的key的名字了而是通过顺序或者是一些特殊的符号来进行区分每一个字段的含义同时在通过一个IDL文件来进行描述这个数据格式(来进行描述每一个部分是啥意思)IDL只是起到一个辅助性开发的效果并不会真正的进行传输传输的只是二进制的纯粹的数据似乎传输效率高了但是开发效率低可能是把对应的文字换成了对应的二进制数据咱们通过二进制的数据对这里面的内容重新地进行了编排甚至有可能做出一些数据压缩只进行传递一些必要的信息传输效率会更高但是这些数据肉眼难以观察 5)开发效率低是包含了开发和调试调试很不方便如果说咱们的线上环境出现了问题如果说咱们用JSON出问题的请求和响应一目了然(一看就知道哪里出现了问题)如果使用protobuffer二进制的数据根本没法用肉眼看我们就需要自己开发一个专门的程序来进行解析这里面的数据来进行分析这里面的问题这就会比较麻烦 咱们广论开发:咱们的protobuffer里面有IDL这个就是说咱们的protobuffer约定的一种文件格式类似于C语言结构体的写法 class message Response{bool ok1;string reason2;repeated Data data3;
} 咱们来进行设计应用层协议是一件非常普遍的事情也是一种并不复杂的事情 设计应用层协议我们要做的工作就是说: 1)要根据需求明确传输的信息 2)要进行明确数据传输的格式要参考现有模板比如说JSONXMLprotobuffer 端口号的用途:标识一个进程就可以区分当前数据要交给哪个进程来处理 1)例如在开发服务器的时候首先会让服务器提供一个业务端口通过这个端口就可以提供一些广告搜索的服务(上游服务器就可以通过这个端口来获取到广告数据) 2)其次服务器还会提供一个调试端口在服务器运行过程中其实会涉及到很多很多的数据有时候为了定位一些问题就需要查看一些内存中的数据通过这些调试端口给服务器发送一些调试请求于是服务器解耦可以返回一些对应的结果; 3)直接拿调试器打一些断点可以吗? 如果拿调试器来进行断住程序此时整个进程就会处于一种阻塞的状态的这就意味着这个服务器是无法响应正常的业务请求的 4)咱们的很多网络服务是进行使用非常常用非常广泛的服务为了更好地进行管理我们就给这些服务分配一些专门的端口号 80----http服务器 443----https服务器 23 ------ssh 23------ftp服务器自己的部署http服务器就可以把他绑到80也可以把它绑到其他转口 当一个数据包到达了网卡以后然后自己主机上面的电脑的所有进程都是通过同一个网卡来传输数据的可以让每一个进程分别绑定不同的端口号此时收到的网络的数据包中也会包含一个目的端口的字段此时就会让这些目的端口找到对应的端口号的进程从而把数据交给对应的进程这个过程就是由操作系统内核来完成的 5)咱们的端口号的范围就是0-65535这里我们就需要说一些常见的知名端口号我们把0-1024这些端口号给进行划分出了一些具体的应用 6)因为咱们的很多网络服务是属于非常常见非常应用广泛的一个服务我们为了进行更好的管理我们就需要给这些服务分配一个专门的端口号一方面你见到了这个端口就知道这是什么服务另一方面我们把常见的服务的端口分配好然后我们再进行部署一些服务的时候这些服务之间的端口号就不会容易冲突 7)我们并不是强制要求而是建议因为咱们的80端口是http服务器咱们的443端口是http服务器咱们的22端口是ssh服务器咱们的23端口是ftp服务器 端口号:端口号是一个整数用来区分进程但是PID(用于进程的身份标识)也是一个整数(PCB中的一个特性)用来区分进程为什么在网络编程中不直接使用PID而是直接用到端口号这样的概念呢 端口号是固定不变的端口号我们可以手动指定但是PID每次进程启动之后都会发生改变这是系统自动分配的我们无法控制 1)例如客户端要连接一个服务器客户端就要先知道服务器的IP与PID一旦服务器进行重启PID就会发生改变 中国移动:10086--类似于端口号这是固定不变的 转换人工:工号xxx为您服务 他就类似于PID每次接通电话可能都不一样 同样的两个进程无法绑定到同一个端口号 2)系统提供的原生的socket API其中有一个方法就叫做bind功能就是把端口号和一个socket关联起来一个进程是可以绑定多个端口的 3)端口号是传输层协议的概念, TCP和UDP协议的报头中都会包含源端口和目的端口, 并且都是使用2个字节, 16bit来表示端口号, 范围也就是 0 - 65535; 但是我们日常写的程序使用的端口号一般都是从1024开始的, 因为0 - 1023这个范围的端口号也称为 “知名端口号/具名端口号”, 这些端口号系统已经分配给了一些知名并广泛使用的应用程序 4)这里我们并不是完全不能使用0 - 1023这个范围的端口号, 只是建议使用, 虽然这些端口被分配给了特定程序, 但是这些程序是否在主机运行着, 主机上是否安装了这些程序都是不一定的, 要使用0 - 1023这些端口, 需要注意2点 : 要确定这个端口没有和程序绑在一起.要拥有管理员权限. 应用层和传输层的联系:除了最上面的应用层下面的传输层网络层和数据链路层物理层这四层都已经在操作系统内核驱动程序和硬件设备已经实现好了不需要我们去实现传输层是紧接着应用层的一层, 虽然传输层是操作系统内核实现好了, 但是我们在写应用层代码的时候, 是要调用系统的socket API去完成网络编程, 所以需要我们了解这里传输层的一些关键协议UDP和TCP. 二)UDP协议:适用于数据量比较小的场景了数据载荷比较小 2.1)UDP抱头: 1)传输层是基于操作系的内核进行实现的咱们的程序员是不需要和传输层来进行打交道的但是传输层意义非常重大因为进行网络编程是需要用到Socket也就是说一旦调用了Socket代码就进入到了传输层的范畴 2)咱们一个完整的UDP数据包UDP报头UDP数据载荷UDP数据载荷就是一个完整的UDP数据包 3)所谓的把一个应用层数据报封装成UDP数据包其实本质上也就是说加上了8个字节特定含义的数据 4)咱们在代码中所写的端口号就会被打包到这样的UDP数据包里面这是我们在报头中体现的咱们以UDP客户端为例源端口也就是操作系统自动默认给客户端分配的端口目的端口也就是服务器的端口已经被指定 5)为什么端口号的范围是0-65535在UDP数据报头中最多也就给安排了两个字节的数据 6)咱们是否可以把UDP这里面的端口改成4个字节之类的呢 1)不可以一方面这个东西是操作系统内核实现的不好意思你都拿不到windows系统源码就改不了内核 2)第二方面就算你把自己的主机的UDP协议给改了那还有别人的主机呢要想改就把所有的世界上的主机都进行修改 1)咱们的入口服务器会根据请求分别从广告服务器和大搜索服务器来进行获取数据并进行拼接最终会得到一个完整的网页返回给浏览器 2)现实就是说咱们的广告服务器和入口服务器和咱们的自然搜索结果服务器和入口服务器要进行网络的传输和交互就需要选定一个好的应用层协议最开始的时候用的UDP协议 3)因为最开始的时候咱们的所进行使用的就是UDP协议但是随着时间的推移随着业务的发展咱们的广告服务器所进行吐出的业务数据越来越多更复杂 2.2)UDP校验和: UDP校验和:是验证网络传输过程中这个UDP数据是否是正确的因为咱们的网络上面的传递数据的本质是光信号和电信号 1)网络上传输的数据有可能会发生故障网络上的数据本质都是一些0/1的bit流这些bit流是通过光信号和电信号来表示的(高频率表示1低频率表示0) 2)如果在传输过程中出现了比特翻转(0-11-0)的情况尤其是卫星上面的数据要考虑比特翻转和太空上宇宙射线就比如说磁场就会使我们的原有的一些传递的信息发生了改变肯定会有一些特殊的情况会影响到我们的数据传输质量再举一个例子我们本来是想要发一组连续的高频信号因为我们遇到了一个强磁场我们就可能会导致其中的某些高频信号变成低频信号(从0变成1)在网络传输中我们一定会遇到一种情况会影响到我们的网络传输质量的本来表示0结果变成1了数据就变错了 3)校验和的本质上是检验当时数据的传输是否出问题需要尽可能识别出数据是不是错的校验和就可以帮发现数据是否错误但是校验的效果不够理想, 万一你的数据同时变动了两个bit位(前一个字节少1, 后一个字节多1), 就会出现内容变了, CRC没变这样的情况. 比如说我想要去超市买菜我妈妈让我买四样菜土豆西红柿白菜酸菜; 校验和:四样菜我们就可以根据四样菜来进行判断买菜是否正确 1)我买完之后发现手里面有5样菜或者3样菜就发现我买错了; 但是此时我发现手里面有4样菜就一定买对了吗 就是有可能是菜有猪肉羊肉西兰花韭菜 其实也不一定这是仅仅靠检验数据来进行校验的所以校验和正确不一定保证数据正确 2)校验和不一定可以百分之百的进行校验如果校验和正确不一定可以保证数据100%一定对但是如果校验和不正确这时就可以直接判断数据一定是错的 上面的一副对联就相当于是校验和就是基于内容来计算出来的校验和比如说最后一条变成了小李飞刀对联就对不上了比如说srcmd5还有sha1 2.3)UDP是如何进行数据校验的: 第一种方式:crc机制也叫做循环冗余检验例如现在有一行二进制数据依次按照字节为单位将这些字节进行累加如果说咱们的校验和数据出错那么直接丢弃 short sum;
for(byte b:数组
{sumsumb;
}
这是如果出现溢出溢出的数据就不要了 1)传输数据的时候就会把数据和src校验和一起传输给目标接收方同时收到了src校验和数据接收方就需要验证一下当前的数据是否是正确的(在网络传输中可能会出现比特反转的情况接收方再按照同样的算法再根据数据部分进行校验和把这个新计算的结果和收到的src校验和进行对比看看结果是不是一致的; 2)本质上来说就是在发送之前针对要发送的数据算一遍src接收到之后再次算一遍src把这个新算出的结果与发送过来的src的结果进行对比看看他们俩相同如果出现了比特翻转接收到的内容和发送的内容就不一样了此时算出的两份src就大概率是不会相同了其实还是有一定的概率两个不同的数据算出的src是一致的这个概率整体上来说是比较小的 3)md5算法这种算法的应用场景非常多用来做校验和只是其中的一个场景本质上是一个非对称的哈希算法 3.1)首先回顾一下哈希表它的核心是一个数组可以做到O1复杂度的增删改查借助了数组下标的随机访问能力通过key进行哈希算法转化成一个数组下标 3.2)哈希算法有很多种针对整数的哈希算法都是比较简单的 md5算法本质上是针对数据进行一系列的数学变换md5算法的特性:定长分散不可逆 1)定长:无论输入的字符串长度有多长得到的md5的值都是固定的字符串的长度(32位)4个字节64位 八个字节(128位) 2)分散:只要输入的字符串改变了一点点得到的md5的值都会差别巨大如果两个字符串的md5的值一致基本上是不可能的 3)不可逆:给定源字符串很容易算出md5的值但是对于md5的值很难算出字符串 可以作为哈希算法也可以作为校验和key是MD5value是原始字符串 4)例如在银行里面设置密码后台的工作人员可不可以看到呢 密码原文小键盘输入程序读取到这个密码之后就可以通过MD5进行加密然后数据库里面存的也是密文由于MD5不可逆的特性程序员无法感知 2.4)UDP总结: 1)无连接:知道对端的IP地址端口号就可以直接进行传输不需要建立连接 2)不可靠:没有任何的安全机制咱们的发送端发送数据包之后如果说因为网络故障导致无法收到信息UDP协议层也不会给应用层传递任何错误信息 3)面向数据包:咱们的应用层交给UDP多么长的报文UDP会原样进行发送数据既不会拆分数据也不会合并数据如果说发送端一次发送100个字节那么接收端也必须一次接受100个字节而不是说循环接收10次一次接受10个字节 4)缓冲区:咱们的UDP只有接收缓冲区没有发送缓冲区咱们的UDP没有真正意义上面的发送缓冲区我们进行发送的数据会直接交给操作系统的内核由内核直接把数据交给网络层协议来完成后续的传输动作但是咱们的UDP具有接收缓冲区但是这个接收缓冲区并不能保证咱们的收到的UDP数据包的顺序和发送UDP数据包的顺序是一致的如果缓冲区满了再次到达的UDP数据就会被丢弃 5)大小受限咱们的UDP首部长度中有一个是16位的最大长度也就是2个字节也就是说一个UDP数据包的最大传输的数据范围是64K 三)TCP协议: 1)16位源端口和16位目的端口:表示这个进程从哪里来到哪里去 2)32位序号和32位确认序号:进行编号保证可靠性 3)4位首部长度:这里面是表示四个比特位本质上来说就是表示TCP报头的长度TCP的报头是在不断变长的不像UDP一样就是固定8个字节此处的表示该TCP头部有多少个32个bit位都多少个四个字节4位表示4个比特位也就是说TCP头部的最大长度就是说4*1560个比特位 4)16位紧急指针标识哪些数据是紧急数据 5)16位窗口大小标识滑动窗口的大小 6)16位校验和表示发送端填充CRC校验如果说接收端校验不通过那么就认为数据有问题此处的校验和不光包含TCP首部还包含着TCP数据部分 7)URG:表示紧急指针是否有效 ACK:表示确认序号是否有效 PSH:表示提醒接收端应用程序立刻从TCP接收缓冲区里面把数据读走 RST:表示对方要求重新建立连接我们把表示为RST标识的称之为复位报文段 SYN:表示请求建立连接我们把SYN标识的段叫做请求同步报文段 FIN:表示本段要进行关闭了我们把表示为FIN结束标识的段称之为结束报文段 8)保留6位:保留位的意思就是说现在还不进行使用但是保不齐以后会进行使用也就是说咱们是为了未来的升级留点空间 9)选项:主要是说这个选项可以有也可以没有可以有一个可以有多个 1)TCP头部中的最后一个选项字段是可变长的可选信息这一部分最多包含40个字节因为TCP头部长度最多就是60个字节还包括前面讨论的20个字节 2)最重要的是窗口扩大因子 可靠性:不是说发送出去的数据100%就能够接收到我发出去的数据到底对方收没收到我心里有数我可以知道我发送的数据成功还是失败(接通电话的时候对方有反应就知道可靠不可靠) 1)换一种角度来说客户端发送出去的数据客户端知道对方是接收到发送的数据还是知道对方没有接收到我的数据 2)发送出去之后就不管了也不知道你有没有收到这就叫做不可靠传输 3)接收方收到消息之后给发送方返回一个应答报文ACK表示自己已经收到了 1)确认应答机制(保证可靠传输的核心)(正常情况下数据传输的可靠性) 咱们虽然在TCP代码上面看不出来但是这个确实是TCP中最可靠的机制引入TCP的关键原因就是为了可靠传输因为TCP是有连接的所以在客户端发送数据给接受方了接收方就应该返回一个应答报文当发送方收到了这个应答报文后就认为对方已经收到了我打电话的时候我进行在电话里面说话对方说嗯嗯我才表示已经收到了 1)但是网络上的传输顺序是不确定的因此就不可以单纯的通过收到数据的顺序来判断和确定逻辑你所进行发送的两个数据包是不一定走同一条路的先发后到 2)例如:我给我的女神发送消息 第一次发送请求你在吗女神回复说在这就是一个可靠传输 第二次发送了两个请求: 请求一:我想请你吃个麻辣烫请求二:做我女朋友吧 3)这是我发送了两条消息一个是好呀好呀另一个是滚由于网络延迟我不知道他的回答是是针对我的那条请求来回答的出现了后发先置的情况我并不知道它的这两个响应报文分别对应着哪一次请求可能这个吃麻辣烫是滚对做我女朋友是好呀好呀 1)这时女朋友和我可以来个序号(编号): 我:请求1:女神你陪我吃麻辣烫好吗请求2:女神你做我女朋友好吗(序号) 女神作出回应:针对请求一是好呀好呀针对请求二是滚确认序号我们如果说使用编号就不会再出现数据顺序混乱的情况了 2)确认序号表示当前这个应答报文是针对哪一个消息进行的确认应答TCP是依靠字节来进行编号通过序号来进行表示当前发送的是哪一条消息通过确认序号来进行表示针对哪一条消息来进行回应 3)在网络通信中两个主机之间通信链路可能存在多条数据包1和数据包2走的都是不同的路线并且设备的转发速率也是非常慢的受网络环境的影响这种后发先到的情况是不可避免的收到消息的变数有可能就会受到影响这样应答错乱以后解析数据的含义就出现歧义了 1)第一次请求主机A给主机B发了个1000字节的数据序号就是1-1000每一个字节都是有一个序号的这个操作相当于是发了个TCP数据报这个数据报的的序号是1长度是1000 2)咱们的主机B会给主机A返回一个应答报文(ACK里面)里面会传输一个确认序号叫做1001确认应答数据报里面的确认数据是1001意思就是1001之前的数据B已经被顺利收到了另外也可以理解成接下来B在向A索要1001开始的数据这是发送方就可以经过应答报文来确定对方是否收到了接下来主机A也就是知道了B已经收到了我发送的1到1000的数据包接下来主机A要发送1001-2000之间的数据了 3)接下来A收到B的确认应答报文之后A会向B发送一个1001-2000的数据包B会给A返回一个确认序号为2001的应答报文表示200之前的数据已经收到了同时也在催促A该发送2000以后的数据报文了此时A就知道了B已经收到了2000之前的数据咱们要接下来发送2001-3000的数据 4)TCP的核心是可靠性可靠性的核心是确认应答 5)发送1-1000的数据的意思是TCP报头中的序号是1报文长度是1000通过这个信息来表示明确的范围就是表示时时刻刻确认序号之前的数据咱们已经收到了 6)咱们的这个1-1000是一个TCP数据包这一个TCP数据包通过层层的封装变成了一个以太网数据帧进行传输如果说有传输了多个数据包分成了多个以太网数据帧多个数据帧之间才会出现后发先致 7)在生产者消费者模型里面也会存在着确认应答机制消息队列里面会保存着一些数据这里面的数据也要是定期删除的因为消息队列里面的存储空间也不是无限大的那么我们要删除那些数据呢? 这个时候就要用到确认应答机制了当消费者模型将元素进行消费的时候会给消息队列发送一个ACK这是消息队列就知道了哪些数据是被消费过的 8)咱们的TCP针对每一个字节的数据进行编号那么就是序列号咱们的每一个ACK都是带有一定的确认序列号意思是告诉发送者我已经收到了哪些数据下一次你要从哪里发 9)超时重传机制相当于是对确认应答机制进行了补充上面说的确认应答是网络在一切正常的情况下通过应答来进行确认可靠性就是咱们的服务器会给客户端发送一个ACK通知发送方表示我收到了但是如果出现了丢包的情况超时重传机制就要起到效果了 10)TCP可靠传输的能力最主要是通过确认应答机制保证的, 通过应答报文不仅可以让发送方清楚的知道是否传输成功, 而且通过序号和确认序号对多组数据的应答对应关系进行了详细的区分 2)超时重传机制(偶然丢包的时候数据传输的可靠性) 为什么要进行重传重传丢的数据是什么 1)第一种情况就是说主机A传输数据syn的过程中就出现了丢包另一台主机没有接收到数据自然也就没有办法给你返回ACK 2)第二种情况就是说主机A成功的把数据发送到了主机B主机B也确实收到消息了但是主机B返回的ACK丢包了 对于咱们的发送方来说根本是无法区分哪一种原因导致我收不到ACK于是主机A就直接往坏了想对方压根就没有收到我们就重新再进行发送一次业务数据 3)这里面的重发也不是立即就重发的而是要等一会给点反应时间数据在网络传输是需要时间的如果发送发10min没有等到ACK超出了等待时间就进行重传业务数据正常情况下连续发丢了两次数据包的概率是比较低的因此就希望第二次重传发送是可以成功的如果网络不是有太大的问题一般重传都是可以成功本来说丢包就是一个小概率时间但是连续发送两次都进行丢包那么概率就会变得更低了 超时重传TCP的去重: 1)现在来假设试想一下如果是接收数据方的返回的ACK丢了此时触发了超时重传就会导致接收方受到了重复的业务信息如果说这里的重复发送的数据不是请吃麻辣烫而是借钱那就很尴尬了 2)TCP内部就会有一个去重操作接收方收到的数据会先存放到操作系统内核的接收缓冲区里面接收缓冲区咱们可以认为是一个内存空间也可以视为一个阻塞队列 3)如果咱们收到了新的数据TCP就会根据这个序号来进行检查这个数据是否在接收缓冲区中已经存在了如果不存在就直接放进去如果存在我们就直接丢弃 4)这个时候咱们的操作系统内核就可以保证咱们的应用程序调用SocketAPI拿到的这个数据一定是不重复的ACK和业务代码没啥关系虽然说咱们的超时重传操作可能会使接收方收到重复的TCP报文但是TCP在我们的操作系统内核里面已经完成了去重操作这样就可以保证应用程序所拿到的数据一定是不重复的应用程序是感知不到超时重传的过程的 重新发送的数据是会进行重新封装还是将之前封装好的报文缓存下来之后再进行发送 1)进行重新发送的数据这是TCP负责进行发送的调用一个socket中的write操作本质上是把这个数据写到TCP的发送缓冲区里面由我们的操作系统内核从发送缓冲区中取出数据通过网卡进行传输封装的过程 2)当触发了超时重传的时候操作系统内核就会把刚才发送缓冲区里面的内容重新进行封装在进行传输如果顺利的收到了ACK就可以把这个数据从发送缓冲区里面给删除掉咱们的发送缓冲区也可以视为是一个阻塞队列 超时重传的策略:动态重传时间最大传输次数 1)linux中是以500ms为单位来进行控制每一次判定超时重传的时间都是500ms的整数倍如果说进行重新发送一次得不到应答就会等待2*500ms进行重传如果说还收不到应答就会等待4*500ms进行重传以此类推以指数形式进行倍增 2)进行重传的次数累积到一定的次数那么TCP认为网络或者对端主机出现异常就会强制进行关闭连接 只要重传还是一直失败大概率是认为这个传输时是通不了咱们进行超时重传的数据是不一定会成功的 1)假设一次丢包的概率是10%那么两次都丢包的概率是1%三次都丢包的概率是0.1%如果两次三次都丢包了就认为网络上面出现了严重的故障(挖掘机铲网线)没网费了自动断开TCP连接就尝试重新建立连接 2)重传如果失败可能还会再进行尝试也不是会无休止的重传连续几次重传都不行那么TCP就认为这个网络可能是约到了严重的情况怎们重传恐怕都不行了就只能放弃了就会自动地进行断开TCP的连接再去尝试建立连接 3)咱们的重传的时间间隔不是确定的不是一直不变的而是在进行动态变化的一般重传的时间间隔会变得越来越大重新尝试发送数据的频率会越来越低 4)一次传输失败的概率本身就比较小连续两次都失败概率只会变得越来越小小上加小这个时候咱们的TCP本身就不会在指望咱们重传可以成功了认为这个时候重传频率再高大概率也是没啥用的白费力气 5)假设丢包概率是10%相当高了那么连续两次丢包的概率就是1%连续两次丢包就意味着当前丢包的概率已经高于10%了接下来我们再次进行发送TCP知道能够顺利发送过去的概率和可能性也是非常小的 6)超时的时间的确定也是非常困难的: 在最理想的情况下需要找到一个最小的时间保证确认应答一定可以在这个时间段内返回但是这个时间的长短随着网络环境的不同是会有差异的如果的超时时间设置的太长那么会影响整体的重传效率如果说我们的超时时间设置的太短那么就会有可能说是频繁发送重复的包所以说咱们的TCP保证无论在什么情况下都可以进行高性能的通信会动态地进行计算这个超时时间 1)基于上述的两个机制咱们的TCP可靠性就看基本得到了有效的保障一种是针对数据传输正确的情况下是怎么进行处理的确认应答 2)一种是基于丢报的情况下怎么进行处理的超时重传 3)超时重传以500ms为单位进行依次叠加如果是发送的syn丢了那就再发送一次SYN即可如果返回的ACK丢了那么此时隔一段时间重复发送SYN就会在内核的接收缓冲区里面进行去重 3)连接管理:也是保证TCP可靠性的一个机制本质上是四次交互 一)三次握手: 1)A一定是服务器是被动的那一方但是B是客户端是主动的那一方开始建立连接的过程是客户端主动建立连接的但是在断开连接的时候客户端和服务器都是可能先进行发送FIN的 2)本质上就是说咱们的客户端和服务器通过三次交互完成了相互连接的过程: 3)TCP这里的连接指的是由一个五元组来标识, 一个连接建立完成就表示通信双方知晓对方的IP和端口信息, 就是通信双方各自都维护着连接这样的一个数据结构, 双方把对方的地址信息都保存下来就是完成了连接, 而断开连接就是把各自存储的连接删除掉 1)为什么是三次握手不是四次握手 1)当A的syn报文到达B后B的内核会在第一时间发出响应 就会立即应答B会发出syn尝试建立连接给A同时会立即向A发出ack确认刚才已经收到A发送出去的请求这两件事是同时触发的就没有必要进行四步握手协议直接一次到位 2)咱们在网络上面进行传输的数据都是需要经过一系列的封装和分用才可以完成传输封装两次肯定不如封装一次更高效因为到了网络层就需要加上IP数据报头到数据链路层就需要加上以太网数据桢头桢尾还要在网络上进行传输所以就需要放在一起提高传输效率 3)把syn和ack合在一起只需要把TCP报头中六个标志位中的的syn和ack标志位都设置成1就可以了 2)为什么要进行三步握手协议为什么要建立连接和可靠性有啥关系 1)投石问路:通过三步握手协议来进行确定A和B之间的传输是否是通畅的也就是说确保通信的链路畅通检查这个网络的情况是否满足可靠传输的基本条件如果你强行在网络环境特别差的情况下进行TCP传输那么也是会涉及到大量的丢包问题无法保证可靠传输那么也就没有必要进行TCP的数据传输了防止通信过程中浪费大量的时间 2)尤其是确认A和B的发送数据能力和接受数据能力是否是正常的保证可靠性的前提条件所以说三次握手也是在检测通信双方发送能力和接受能力是否都正常 3)协商参数也就是说让通信双方协商一些重要的信息通过三次握手让A和B之间通通气选择一些传输中合适的参数例如TCP的序号从几开始咱们的客户端和服务器是有一些共同的信息的因此在三次握手的同时也会相互之间交互一些必要的内容双方按照商量好的一个方式来进行后续的工作 1、对方报文发送的开始序号;
2、对方发送数据的缓冲区大小;
3、能被接收的最大报文段长度MSS;
4、被支持的TCP选项; 例如在一个打电话情境中A和B不刚要知道自己的听筒和话筒是否正常还要彼此知道对方的听筒和话筒是否正常就是在进行验证通信双方的喇叭和听筒是否都好使 1)上面的这个环节接收方只是知道一半只知道对方的麦克风和自己的听筒是好使的 2)到了最后一个环节就知道了咱们的四个设备双方都知道是好使的咱们也就具备了可靠传输的基本条件 如果网络中出现了问题三次握手的过程都无法保证成功那么也没有必要进行后续的传输三次握手主要是判断一个主机是否有发送数据和接收数据的能力如果连这些基本的要求都做不到那这两台主机就没有什么必要再去进行沟通和通信了 3)TCP服务器中一些重要的状态 1)listen:表示服务器启动成功端口绑定成功随时有人可以有客户端找来进行建立连接手机开机信号良好随时可以有人可以给我打电话创建好了ServerSocket的实例的时候就进入了这个状态等待客户端来进行进行连接 2)establed:表名客户端和服务器已经建立连接成功了随时都可以进行通信了 代码中accept返回得到了一个socket对象这是一个稳定的状态说明连接已经建立好了可以正式进行通信了也就是说有人给我打电话我接听了接下来就可以说话了 3)closed:表示客户端或者是服务器处于关闭状态 4)syn_sent:表示客户端连接请求已经发送此时客户端进入阻塞等待服务器进入到确认应答状态一般来说此状态存在的时间很短 5)syn_rcvd:表示服务器已经收到了客户端的连接请求发送ACK和SYN进入阻塞 4)四部握手可以吗两次握手可以吗 1)可以但是没有什么必要也可以得到投石问路的效果这样比较麻烦效率比较低对多余的一次数据包进行层层封装和分用传输的开销就会变大中间的ACK和SYN是可以合在一起 2)A--B(syn)B---(synack这样做可以吗? 3)B也无法知道自己的发送数据的能力是否正常也无法知道A的接受数据的能力是否正常 4)两次传输意味着缺少最后一次此时客户端知道两方的发送接收能力正常的情报是完整的但是服务器这边是残缺的服务器不知道自己的发送能力是否OK也不知道客户端的接受能力是否OK 5)也就是说此时此刻服务器对于当下是否满足可靠传输心里是没有底气的这第三次交互就是为了给服务器吃一个定心丸 二)四次挥手:释放之前的系统资源 三次握手是为了进行可靠性传输之前的一个验证四次挥手是为了释放资源双方各自向对方发送了FIN并且给对方一个ACK确认报文 1)咱们的FIN叫做结束报文段当我们标志位的FIN是1的时候就意味着向对方发送了一个结束报文段 2)三次握手过程一定是客户端主动进行发起的主动发起请求的一方叫做客户端 3)但是咱们的四次挥手过程中可能是客户端主动发起也有可能服务器主动发起 4)咱们的三次握手中间两次是可以进行合并的但是咱们的四次挥手中间两次可能合并不了也有可能可以进行合并大概率是合并不了结合上面的三次握手过程和四次挥手的过程来进行理解一下 1)为什么要进行四次挥手: 1)四次挥手是为了释放我们对应的资源我们的三次握手过程其实本质上来说就已经让客户端和服务器建立好了连接但其实在建立好连接之后我们在操作系统内核里面我们就要使用一定的数据结构来保存有关于建立连接的信息保存的信息最最重要的是其实就是在保存咱们前面所说的五元组占用系统资源 2)咱们的客户端和服务器都是需要进行保存5元组的源IP源端口目的IP目的端口协议类型占用系统资源你要是不进行保存你都不知道自己自己到底和谁进行建立的连接既然我们要保存信息就需要占用系统资源比如说内存 3)假设有朝一日连接断开了之前咱们保存的连接信息其实本质上就没有什么意义了我们对应的空间也是可以进行释放掉了也就是说把之前的记录进行销毁就是为了释放咱们的系统资源连接保存信息的内存就可以释放了销毁了 5)客户端和服务器都可以主动断开连接三次握手主要是为了验证咱们进行可靠性传输的一个验证而我们的四次挥手就是为了说释放资源 6)咱们的三次握手的过程中B发送的syn和ack是同一时机因此就可以进行合并此时B给A发送的syn和ack都是由操作系统的内核来进行负责运行的是几乎同时进行发生的 7)但是在四次挥手过程中被动断开连接的那一方发送syn和ack的时机往往是不同的B给A发送的syn是操作系统的内核来进行实现的在进行收到主机A发送过来的请求断开连接主机B就会立即做出响应操作系统的内核就会将ACK返回给主机A 8)但是B给A发送的FIN是我们进行使用用户的代码来进行实现的B的代码中调用了socket.close()方法才会触发FIN只有当我们的用户代码执行到close方法才会进行触发这完全取决于用户的代码是怎么写的如果代码执行处理的逻辑比较短那么就可能很快执行到close() 9)也有可能就是说代码可能会进行处理大量的逻辑用户的代码可能出现了错误造成了死循环之类的这样我们的socket.close()可能就会永远无法执行了可能FIN永远无法触发从内核触发ACK和用户代码层面触发FIN这是两个不同的时机 10)这两个操作时间差比较大就不能进行合并了如果是时间差较短这是有可能合并的延时应答和捎带应答 1)A向B发送SYN(请求断开连接) 2)B只要收到A发出的FIN就会立即触发ACK(确认已经收到想要断开连接的请求)发送ACK,这是操作系统的内核可以快速做出响应但是啥时候发送FIN是用户自己手动来决定只要代码中出现了socket.close()这样的操作的时候才会触发FIN 3)所以说一个是内核触发的一个是用户代码触发的无法保证他们是在同时进行的他们本质上没有关联关系 就是之前咱们再写TCP客户端服务器代码的时候咱们有一个循环代码 1)TCP服务器针对每一个TCP客户端都创建了一个clientSocket单独的写一个procession来进行循环读取TCP客户端发送过来的请求 2)当客户端发送FIN的时候咱们的服务器还在处于一个while循环里面还想着客户端能够发送数据呢里面有一个循环退出条件while(!scan.hasNext())当我们满足循环条件也就是说客户端断开链接了进入到这个方法咱们的服务器就感知到了客户端来给咱们发送syn了所以说当咱们执行while(!scan.hasNext())方法的时候就会立即返回一个ACK 3)但是此时咱们服务器立即重传ack这是操作系统内核进行完成的工作可不会立即传输FIN因为想要传输FIN就需要执行climentSocket.close()但是想要执行完这个方法还需要处理完我们的if(!scan.hasNext())循环语句块里面的逻辑调用break语句才可以退出万一在这一个循环里面出现了死循环就无法发送FIN了 private void procession(Socket clientSocket) throws IOException {System.out.printf(我们这一次客户端请求的IP地址是%s 端口号是%d,clientSocket.getInetAddress().toString(),clientSocket.getPort());try(InputStream inputStreamclientSocket.getInputStream()) {try (OutputStream outputStream clientSocket.getOutputStream()) {Scanner scanner new Scanner(inputStream);while (true) {if (!scanner.hasNext()) {System.out.printf(客户端断开连接%s %d, clientSocket.getInetAddress(), clientSocket.getPort());break;}String request scanner.next();System.out.println(request);String response process(request);PrintWriter printWriter new PrintWriter(outputStream);printWriter.println(response);printWriter.flush();System.out.printf([客户端的端口号是%d 客户端的IP地址是%s],请求数据是%s,响应数据是%s, clientSocket.getPort(), clientSocket.getInetAddress(), request, response);}} catch (IOException e) {e.printStackTrace();} finally {clientSocket.close();//listenSocket.close();在这里面是不能进行关闭的}} 1)但是实际上FIN的触发本质上是内核里面释放了对应的PCB的文件描述符比如你虽然没有调用socket.close()但是Socket对象被GC回收了也是有可能会释放对应的文件描述符的 2)或者进程结束了PCB都要被销毁PCB的文件描述符表没了进一步的文件描述符也就没了这同样会触发FIN(停止客户端代码) 2)四次挥手过程中一些重要的状态: 1)ClLOSE_WAIT:谁被动接受FIN谁就进入到这个状态B收到了FIN进入的状态等待用户调用sockt.close来发送FIN四次挥手已经挥了一半了在三次握手和四次挥手的过程中同样是有可能会出现丢包的问题的 2)四次挥手过程已经过了一半了是咱们的主机B在收到A请求的FIN做出ACK响应之后没有发送FIN给A的这个状态就是在等待代码中调用socket.close()方法来进行后续的挥手过程,在咱们正常的情况下一个服务器不应该有大量的CLOSE_WAIT状态如果这个状态存在那么就说明代码出现了BUGclose方法没有被执行到 3)如果在调试代码的过程中出现了大量的CLOSE_WAIT这是说明socket.close()没有被及时地调用到 2)TIME_WAIT:谁主动发起了FIN谁就主动进入到TIME_WAIT状态它所起到的效果就是给最后一次ACK提供重传机会为了处理最后一次ACK丢包 如果是最后一个ACK丢了B就收不到A传输的ACK了因为咱们的B不知道是自己的发送的数据FIN丢了还是应答报文ACK丢了所以就会重新的进行传输业务数据重传FIN 1)四次挥手必须是四次吗可以是三次吗 有延时应答和捎带应答会将ACK和FIN是有可能合并在一起的 2)四次挥手一定会执行吗 不一定四次挥手是一个正常TCP断开的流程但实际上有的情况TCP也会异常断开例如网线都断了 SYN标志表示请求建立一个连接称携带SYN标志的TCP报文段为同步报文段 建立一个新连接当SYN1ACK0时表示这是一个请求建立连接的报文段当 SYN1ACK1时表示对方同意建立连接 SYN1说明这是一个请求建立连接或同意建立连接的报文 FIN标志表示通知对方本端要关闭连接了。我们称携带FIN标志的TCP报文段为结束报文段 断开一个连接表示通知告知对方本段要关闭连接了标记数据是否发送完毕当FIN1表示告诉对方“我的数据已经发送完毕你可以释放连接了”代FIN标志的TCP报文段称为结束报文段 四)滑动窗口:提升效率 虽然上面的几种机制都是为了保证可靠性但是TCP也要尽可能地保证效率本质上来说保证效率和保证可靠性是比较冲突的要等待接收ack这是需要时间的但是TCP在保证可靠性的前提下再来几种机制努力的提升传输效率提升性能 咱们批量发送一次发一波数据一次等一波的ACK这是就把等待多组ACK的等待时间重叠起来了进一步缩小等待时间提高程序执行效率 1)咱们滑动窗口的效果就是说在尽可能地保证可靠性的情况下尽可能的提高传输效率 2)况且咱们进行发送滑动窗口的本质就是说进行批量的发送数据咱们尽可能说是把等待ACK的时间总体进行缩短咱们可以把等待一份ACK的时间变成等待多份ACK的时间速度提升了等待ACK的时间甚至少等一些ACK 3)可以看到由于确认应答机制的存在这就导致了当前每执行一次发送操作都要等待上一个ACK的到达只有ACK到了发送方才可以进行发送下一次传输的数据大量的时间都花在等ACK上面了整体等待花的时间多了那么在单位时间内传输的数据量就变少了 4)ACK:确认应答机制里面的接收者告诉发送者自己受到的那些数据里面有序列号发送方要花费很多时间来进行等待这个过程就消耗了大量的时间,所以我们就要想办法批量去发送批量去等待 5)例如A向B发送了1-1000的数据B返回一个确认序号A就会进行等待直到A收到这个确认序号这个确认序号的意思就是说B告诉A我收到了1-1000B在向A索要1001-2000的数据如果没有收到ACK就会进行重传 窗口大小:一次批量发的数据的长度称为窗口大小如果没有批量发送数据的长度限制完全不等ACK就一通发其实就没有可靠性而言窗口越大整体的效率就会越高但是可靠性会非常低但是可以窗口越小整体的效率就会越低但是可靠性也会越高 1)当前窗口范围就是1001-4000也就意味着发送方现在同时发送了1001-20002001-30003001-4000发送这四组数据的过程中不会等待ACK这四组都发完了发送方统一等待4组数据的ACK把一份等待一组ACK的时间变成了一份等待多组ACK的时间把等待多组ACK的时间给压缩成一份TCP是要保证可靠传输的而保证可靠传输的灵魂就是确认应答如果说没有这个ACK可靠传输就形同虚设等是必须的只能让他少等一会如果一次发送的批量数据是N统一等待一波此时这里的N就被称为窗口大小 2)滑动的意思是并不需要把N组数据的ACK都等到了才继续往后发送而是收到一组ACK就继续往下发送一组 3)这时依然在等待四份数据的ACK并不是在等待四份数据的ACK都到达了才发新的数据而是随时收到ACK就随着向后发送 此时向服务器发送的数据是1-4000的数据进行批量发送发送之后接收完数据当前的客户端是在进行等待1001200130014001四组ACK咱们的客户端不需要等到4001来了之后才继续向后发送数据等到一个之后就向后发送一组新的 4)此时只要1001到了我们就可以立即向后多发一组(4001-5000)此时等待ACK的范围就是2001300140015001如果是2001这个ACK到了我们就认为1001-2000之间的数据我们已经收到了我们也可以继续向后多发1组数据(5001-6000) 5)此时等待ACK的范围就是3001400150016001每一次等到一个ACK就向后滑动窗口向后滑动咱们向后多进行发送一组数据咱们要等待ACK的范围会发生改变 1)滑动窗口这时传输ACK会存在后发先致的情况 1)ACK 1001 200130014001都在网络上运输呢不一定非得是1001先到呀可能是30014001先到呀这样也没有关系 2)确认序号表示从该序号之前前面的数据已经接收到了如果收到了4001这个ACK说明0-10001001-20002001-30003001-4000的数据已经收到了此时4001前面的这几组ACK收或不收已经不关键了然后滑动窗口向后滑动三格然后直接发送4001-50005001-60006001-7000的数据7001-8000的数据 3)当前这个滑动窗口越大说明就可以认为传输速度越快窗口大了同一时间内需要等待的ACK就越多总体等待ACK的时间就少了 发送方连续进行发送了0-10001001-20002001-30003001-40004001-5000 4)咱们的接收方接收这5类数据之后发送发就会进行批量等待5组数据的ACK但是其实发送方们只需要进行等待5001这个确认序号就可以了 5)发送方收到5001的时候也就意味着1-5000的数据已经收到了咱们的1001200130014001被进行丢包是毫不影响的只要我们收到了5001就已经涵盖了3001和4001所进行表示的信息我们的窗口就一下子向后面挪了5个格子 2)滑动窗口过程中出现的丢包问题: 2.1)返回的ACK丢了: 现在发送0-10001001-20002001-3000数据包已经抵达ACK却丢了 1)这种情况不打紧只要不是全丢了就好发送方如果1001这个ACK丢了但是3001这个ACK到了这此时也就知道了确认序号3001之前的数据应该都到了肯定0-1000的数据都已经收到了此时3000前面的数据已经接收到了 这个时候再是否收到1001这个ACK数据包其实已经没有必要了 2)实际上有时TCP为了偷懒滑动窗口下并不是每条数据都有ACK有时会个几条才会发一个ACK 2.2)直接发的数据就丢了 仍然需要重传(ACK不处理还可以) 1)假设主机A向主机B发送0-1000的数据包丢了B返回的确认序号就永远是1001这个数据即使A给B已经向后发送了若干个数据包此时仍然是在索要1001 2)索要若干次之后A就明白了自己丢包了就会触发超时重传机制重新发送0-1000的数据包 因为咱们在这里面的重传只是需要把丢了的那一部分在进行重传了即可其他已经到了的数据就不需要进行重传了这样咱们的整体的重传效率就会变得比较高了这样的重传称之为快速重传这个快速重传就是类似于看电视剧以前大家看电视剧是真的是用电视看每一天都是固定时间播放的如果某一天有事了其中某一集没有看成只能继续往后看过两天发现另外一个电视台也在放这个电视剧就可以通过另外一个电视台把缺了的那一集给补完此时就可以了 这时1001-2000发的数据包丢了因为1001丢包了主机B返回的ACK就会一直索要1001确认信号就一直是是10011001-2000丢了B就会给A返回一个ACK由于包丢了就会一直返回我收到0-1000的数据我想要1001之后的数据 1)A向B发送了1-1000的数据B返回一个ACK表示收到了1-1000的数据还向A索要1001之后的数据 2)A发送1001-2000的数据B没反应不会返回ACK 3)A在发送2001-3000的数据B在A不丢包(1001-2000的数据)的情况下会返回一个已经接收到了2001-3000的数据确认序号是3001的ACK 4)但是此时A丢包了B返回的ACK就变成了我已经收到了0-1000的数据我还是索要1001-2000的数据但是实际上2001-3000的数据确实是已经存放到接收方的接收缓冲区里面了 5)这时A还会发送2001-3000的数据B确实收到数据了但返回的ACK确认序号还是10016)这时A还会发送3001-4000的数据但此时B返回的ACK仍然是向A索要1001-2000的数据;2001-4000的数据已经让B存放在接收缓冲区里面了但是此时的确认序号仍然是1001但是0-1000的缓冲区和2001-3000之间还是有空位置的此时因为1001-2000的数据没到本属于他的缓冲区是空着的 6)这时A就知道自己已经丢包了就会给B再次发送1001-2000的数据此时B收到了0-4000的数据就会知道4001之前的数据已经收到了这时返回的ACK中的确认序号就变成了4001就是我已经收到了0-4000的数据就会再次向A索要4001之后的数据了 我们在这里面的重传只需要把丢了的那一块数据给重传了就可以了其他已经到了的数据就不需要进行重传了 五)流量控制:限定滑动窗口发送速率的大小保证TCP的可靠性16位窗口大小里面就包含了接收缓冲区的剩余大小 进行流量控制的关键就是说可以进行衡量接收方的处理速度咱们在此处就直接使用接收方剩余的接收缓冲区的剩余空间大小来进行衡量当前接收方的应用程序的处理能力和速度 流量控制这件事情就是根据接收方的处理能力(接收缓冲区的空余于空间大小)来动态决定发送方的发送速率(控制滑动窗口的大小) 1)接收方的处理能力是通过接收方缓冲区的剩余容量来衡量的, 接收方缓冲区的容量剩余多少, 下次发送方的窗口大小就是多少 2)当发送方的数据到达接收方的时候, 接收方都会返回一个ACK这个ACK除了确认能够确认应答, 还能告知接收方缓冲区的剩余容量, 然后发送方就会根据接收方缓冲区的剩余容量来控制发送速度(窗口大小) 3)当接收方得知接收方缓冲区空间满了的时候, 就暂时不会发送数据了, 而是会定期去给接收方发送一个探测窗口报文, 这个报文不携带具体的业务数据, 只是为了触发ACK查询接收方缓冲区的剩余容量. 1)机制的本质目的是在控制滑动窗口的大小是想要保证可靠性的窗口的大小决定了传输的效率窗口越大效率就会越高单位时间批量等待ACK的时间就会越来越多 2)窗口越大资源开销就要更多窗口越小效率就得不到保证发送方发送的贼快接收方根本处理不过来了接收方就会把新收到的包给丢了发送方是不是还得重传 就是怕咱们的接收缓冲区把数据挤满了应用程序处理不过来操作系统内核就把数据包给丢了数据包一丢咱们还需要进行重传没有必要 3)此时的流量控制就是基于接收方的一个处理数据的能力来限制窗口大小的 TCP这个数据的传输过程类似于生产者消费者模型 1)主机A在给B发送数据到达了B的接收缓冲区此时主机A就是生产者B的应用程序通过SocketAPI来读取数据被SocketAPI读取的数据就从缓冲区删掉了此时应用程序就是消费者接收缓冲区就相当于消费场所就相当于是一个阻塞队列缓冲区里面的数据可以随时被B里面的应用程序通过SocketAPI来进行读取) 2)此时说的窗口大小就是发送方批量发送的数据有多少例如主机A发的数据很多窗口很大此时接收缓冲区的数据也会增长很快如果主机B的应用程序读的数据很慢随着时间的推移接收缓冲区逐渐满了如果此时不加任何限制主机A还是按照原来的速度发不加任何限制这时新来的数据没有地方保存了就被内核丢了 1)如果说剩余空间比较大那么就认为B的处理数据的能力就比较强就可以让A发送的快点 2)如果说剩余的空间比较小就认为B的处理能力是比较强的叫可以让A发送的慢一些 1)例如接收缓冲区的大小现在是4000当1-1000的数据到达的时候缓冲区这里用了1000还剩3000返回的ACK就会把这个信息发回去发送方再次发送数据的时候就会按照3000这个滑动窗口来发送数据 2)如果窗口大小变成0了(接收缓冲区已经满了)这是发送方就啥也不干了么 不是的发送方此时是不会再发送数据了但是为了可以查询当前接收方的窗口大小每隔一段时间还会发一个窗口探测包通过这个包(不会传输具体的业务数据)主要目的是触发接收方的ACK当发送方知道现在就知道当前窗口的大小了然后再次发送数据 3)为什么TCP传输数据的大小不会受到限制呢? 正常来说16为窗口大小最大的数字表示65535那么TCP的窗口大小最大就是65535字节吗64KTCP是传输层协议在TCP首部40字节选项(在选项中)中包含了一个窗口扩大因子 M实际上的窗口大小是窗口字段的值左移M位 1)流量控制是根据接收方的剩余缓冲区大小来进行确定滑动窗口的大小但是拥塞控制是把中间的链路看成了一个整体看看中间数据链路的拥堵情况根据传输数据快慢以及效率来进行控制窗口大小 2)虽然TCP有了滑动窗口这个大杀器可以相当可靠的发送大量的数据但是如果在刚开始的阶段就发送大量的数据可能会引发问题网络上有很多的计算机可能当时的网络状态很拥堵若不清楚网络状态贸然发送大量的数据很可能会导致网络系统瘫痪 3)本质上是在另一个角度上来控制发送方的窗口大小他是站在一个宏观角度来看待问题他是把中间的链路都看成了一个整体只看结果是一个不断尝试的过程因为我们的A和B的中间节点有多少个咱们也不知道就很难对这些设备一一进行衡量 流量控制针对的是接收方一个元素针对的拥塞控制针对的是整体路径进行限制它们会动态变化的,真实的窗口大小取流量控制的窗口和拥塞控制的窗口他们的较小值 正式的窗口取流量控制窗口和拥塞控制窗口的较小值是希望滑动窗口的大小是出现在阈值和网络拥塞窗口(丢包窗口)之间这样就做到了尽量少丢包况且传输效率还特别高 六)拥塞控制: 数据传输的效率不仅仅取决于接收方滑动窗口的大小和接收方接收方缓冲区处理数据的能力还和传输路径以及中间链路连接有关 1)先用一个较小的窗口来传输数据看看是否丢包如果不丢包说明网络通畅如果丢包说明网络拥堵当我网络通畅的时候就加大传输速率如果当前网络不丢包的情况下就立即降低发送速率通过这样的方式就可以直接试出来当前最合适的窗口大小 2)当A开始的时候会以一个较小的窗口来发送数据如果说数据很流畅的就到达了就逐渐增大窗口大小如果加大到一定程度之后出现了丢包就意味着通信链路出现拥堵了这个时候我们在减小窗口 3)通过反复的增大和减小过程逐渐就摸到了一个合适的范围拥塞窗口就在这个范围内不断地发生变化达到动态平衡 1)最期望的理想滑动窗口效果就是说在阈值和丢包窗口之间在这个窗口之间就可以保证既不进行丢包传输速率也是比较快的 2)咱们拥堵的值是啥就是完全取决于这一次传输是否丢包如果进行丢包了那么就说明视为拥堵也不一定反正会出现多次重传 3)咱们就直接可以通过拥堵的值来进行确定更新下一次阈值的这一个过程不是阈值影响到了拥堵的值而是我们拥堵的值影响到了阈值下一次拥堵的值是如何进行改变的拥堵的值就是完全取决于咱们网络上面的一个传输数据的效率这个值还是和网络整体环境是有关系的所以拥堵的值也是在实时变化的因为我们一开始的网络传输的时候拥堵值就是不确定的所以下一次的阈值也是不确定的 4)刚开始启动的时候窗口比较小此时通过指数增长就可以在很少的轮次(发送的次数)上把窗口大小给顶上去刚开始的窗口比较小慢慢达到增长到即将丢包的最大窗口实际情况既希望速度快又希望不丢包我们只能增加滑动窗口接近丢包的极限又没真丢 初始情况下我们给定的窗口太小了可能合适的值是一个更大的值通过上述过程我们就可能接近一个更合适的值指数增长到一定程度就会变成线性增长滑动窗口已经快打到极限了此时如果指数级增长可能来个2倍就直接超过了最大范围线性增长也是增长增长到一定程度就会出现丢包的问题 5)一旦发生了丢包的情况此时发送方就立即会让窗口变回归到刚才的初始情况下的窗口大小继续重复刚才的指数增长线性增长一大块就减没了 1)这就类比于两个人谈恋爱在一开始的时候两人的感情呈现指数级增长之后呈现线性稳步上升两个在一起时间长了一吵完架感情迅速降到级点对彼此很失望 2)一段时间之后两个人都彼此发现自己有不对的情况感情又迅速升温最终还是会线性增长但是这一次增涨到极点的过程也就是阈值还是比较短的至少比第一次短 1)第二次滑动窗口从0开始增长的阈值就是第一次滑动窗口达到的最大值的一半 2)这个阈值就决定了咱们窗口大小啥时候从指数增长为线性这个阈值也不是永远固定不变的每一次出现丢包阈值就变更新为当前出现丢包的窗口大小的一半就想要通过阈值来进行描述一下线性增长的过程 3)通过拥塞窗口来进行控制滑动窗口的大小 4)如果达到阈值就会从原来的指数增长变成线性增长增长速度变慢因为阈值已经快接近丢包的范围了传输轮次是传输的次数当传输伦次达到13次的时候 就会出现网络拥堵然后窗口的大小又会变成最小值阈值会发生变化 7)延时应答:琢磨窗口大小相当于是流量控制窗口的一个延申(16位窗口变大) 1)咱们的流量控制是踩了一下刹车想让发送方发送的不要太快延时应答就是在这个基础上面能够让窗口变得更大一些想让咱们的发送方多发送一些数据 让窗口大小保证在可靠的基础上效率再高一些窗口争取再大一些流量控制就是说窗口大小是接收缓冲区的剩余空间大小会随着时间的推移接收方的应用程序会不断地进行消耗缓冲区里面接收的数据窗口越大网络吞吐量就越大传输效率就会越高我们的目标是在保证网络环境不拥堵的情况下尽量提高传输效率 2)在A发数据的时候本应会返回一个ACK但是这时缓冲区的大小可能会发生变化可能会减少很多这时B就对数据的需求量更大了所以我们可以让ACK等一会再发出去在B向A发ACK的这个过程中B可能会消费的更多缓冲区的剩余就更多这时下次A再发的数据就可能会让缓冲区填不满所以说咱们就等一会再来发送ACK延时应答让最后传输的ACK中的16为窗口大小就会变得更大 咱们再进行举一个入水和出水的例子来进行理解一下: 1)我们在进行注水的同时也在出水每一次在进行注入一波水的时候就会进行询问水池中的剩余空间有多少 2)此时我们采取的策略就是不立即进行回答而是稍微晚一会再进行回答我们稍微迟一点进行回答就是为了在迟一点的这个延时时间里面就会排出更多的水 3)如果立即进行回答那么我们可能会回答一个剩余20吨水但是我们稍微等一会回答也就是说延时一会回答就可以说剩余5吨我们在借助延时的这个时间里面又出了15吨水所以说我们在下一次进水的时候就会进更多的水 4)所以说我们的这个操作就实在有限的情况下又尽可能地提升了一点运行传输速度既想保证可靠性也想保证效率发送给多的数据 咱们如何来进行延时应答呢 1)每隔N个包就来进行应答一次 2)超过最大延时时间就进行应答一次 八)捎带应答: 捎带应答:捎带应答本身是一个概率性的机制当前ACK的延长时间正好要比接下来发业务数据的时间要长一些取决于代码具体的实现内核中只要收到数据就会立即返回ACK这是应用程序进行的响应捎带应答本身就是延时应答的延申 咱们用浏览器上网打开网页用的就是一问一答的模型一个请求就是一个响应 1)内核返回的ACK和应用程序用户代码返回的响应是有一定的时间间隔的因为这两个东西是不同时机进行触发的两种数据包本来进行返回的时间间隔是不同的所以说在本质上就不应该进行合并 2)但是有了延时应答的存在导致我们的ACK是不会立即返回的而是要稍微等一会返回让我们的接收方的应用程序多消耗一下接收缓冲区里面的数据才可以进行返回的16位窗口大小变得更大些 3)ACK正好在这里等一会的过程中服务器要返回业务上面的请求了正好可以把这个ACK和response合成一个包因为在网络通信过程中对于数据涉及到大量的封装和分用收到之后还要进行解析时间成本也高效率也低 3)之前我们说过四次挥手是有可能变成三次挥手的但是具体的概率是不知道的 捎带应答是建立在延时应答的基础上的 他是有可能把中间的ACK和FIN合并到一起的例如在服务器收到请求并返回响应这个过程中消耗50ms但是延时应答ACK最多等待20ms这个时候就无法触发捎带应答了 4)所以说它本质上是一个概率上的机制但是此时假设延时应答ack最多是等60ms第50ms的时候此时处理完数据代码执行逻辑完成响应这个时候ACK就可以和FIN一起过去了也就同时触发了延时应答和捎带应答 这是延时应答的一种情况假设左边是A右边是B 1)正常来说A再给B发送业务数据的时候B的操作系统内核会立即返回一个ACK但是由于延时应答的问题咱们的ACK要等一会发等一会是为了让B消耗了更多的业务数据让B的接收缓冲区变得更大让返回的ACK里面的16位窗口大小变得更大些 2)业务数据用户操作代码执行逻辑实现的但是在等待过程中业务数据处理完成了那么这个ACK就会和响应报文一起发送给对方这就是演示应答的基础上发生了捎带应答 九)TCP面向字节流: 面向字节流可能会出现粘包问题粘包问题主要指的是粘应用层的数据报咱们的TCP数据报中的载荷里面放的就是应用层数据 咱们不仅仅是TCP存在粘包问题而是说所有的面向字节流的机制都是存在着粘包问题的比如说读文件 1)TCP粘包主要是粘的是应用层的数据包在咱们的TCP缓冲区里面若干个应用层数据包粘在一起了分不出谁是谁了 2)本质上就是说指数据在进行传输的时候在一条消息中读到了另一条消息的部分数据这种现象就叫做粘包咱们的TCP是面向字节流的传输协议流是没有明确的开始和结尾边界的所以会出现粘包问题 1)接收方收到这些数据之后进行分用先把TCP数据包进行解析解析过后就会把这些数据部分放到接收缓冲区里面 2)应用程序通过read方法从接收缓冲区中读数据的时候就不知道从哪里到哪里是一个完整的应用层数据包了应用层取的就是一个个完整的字节应用程序此时只能看到接收缓冲区中的一个一个的字节无法区分当前应用层有多少个应用层数据包以及从哪里到哪里是一个完整的应用层数据报他不是TCP特有的问题而是面向字节流共有的问题 3)TCP的报头没有长度的当前我们的接收缓冲区可以视为是分用后的数据(当前已经把TCP报头给去掉了缓冲区里面是没有TCP报头的)或者SocketAPI拿不到从应用程序角度来看就是一个一个的字节 4)那么如何解决这个粘包问题呢应该通过再设定一个合理应用层协议来进行解决 1)不知道两个应用层数据之间的界限 2)解决方法的关键是要在应用层协议这里加入包之间的界限 3)粘包问题虽然是在TCP这里面被提及但是他从本质上来说还是一个应用层的问题咱们的粘包问题本身对于TCP协议没有任何影响但是这个粘包问题导致了说基于TCP的应用层协议会出现一些麻烦应用程序就不知道一个应用层数据到另一个应用层数据之间的界限了会对我们的应用层产生影响 解决粘包问题:明确包和包之间的边界 数据包的长度固定使用四个字节来进行表示假设每一个TCP数据包中的载荷部分是完整的应用层数据应用程序先进行读取前4个字节得到了5接下来在进行读取5个字节就读到了hello这个hello就是一个完整的应用层数据包接下来在进行读取4个字节得到5接下来在进行读取5个字节得到world此时的world也是一个完整的应用层数据包 3)预定发送方和接受放以固定大小的数据长度为单位的数据包进行发送长度不够的使用空格来进行填充有了固定大小以后就知道每一条消息的具体的边界了就没有粘包的问题了 上面解决粘包问题的方案再HTTP数据包里面都有体现 UDP不存在粘包问题: 采用了链式结构来记录每一个到达的UDPUDP则是面向数据包传输的是有保护消息边界的,接收方一次只接受一条独立的信息所以不存在粘包问题 因为咱们的HTTP协议在底层是一个应用层的协议那么我们就有可能面对粘包问题 所以说我们要设计合理的应用层协议来进行明确包和包之间的边界 1)使用分隔符 2)使用长度 1)比如说当前有若干个GET请求的数据包到达了TCP的接收缓冲区里面了此时在HTTP协议里面当我们的应用程序读取请求的时候就以空行作为分隔符遇到一个空行说明一个数据包读取完毕仅此而已通过空行来作为结束标记 2)假设此时发送方发送了一堆POST请求也就是一大堆TCP的数据包到达了接收缓冲区因为咱们的POST请求是有body格式的数据的此时当我们的应用程序读到了空行之后就需要再次按照Content-Length的长度再来进行读取若干个长度的数据 十)TCP中的一些异常问题: 1)进程终止: 在任务资源管理器里面咱们的程序通过TCP建立的连接QQ的客户端和服务器建立好连接了在进程毫无防备的情况下咱们突然偷袭一个进程突然关闭它还有可能是一种正常情况不管进程中是怎么终止的抛出异常终止 1.1)咱们的TCP连接是通过Socket文件来进行建立连接的Socket本质上来说就是进程打开的一个文件文件其实就是存在于进程中的PCB的文件描述符表每打开一个文件就在文件描述符表里面增加一个表项包括Socket我们每一次关闭一个文件就在PCB的文件描述符表里面减少一个表项 1.2)如果直接杀死进程进程没了PCB也就没了进程里面的文件描述符表也就没了此处的文件就相当于是自动关闭了因此这个过程其实和手动调用socket.close()的效果是一样的都会触发四次挥手这样我们来进行偷袭进程和正常情况下进行四次挥手是没有任何区别的 1.3)本质上都会对应释放PCB也会释放对应的文件描述符一样会触发四次挥手进程终止并不意味着连接就终止进程终止本质上是调用了socket.close()而已和正常流程一样 2)机器重启: 这里面的机器重启或者关机是按照操作系统的正常流程来进行关机正常流程的关机本质上是让操作系统杀死所有的进程然后再进行关机和第一个步骤一样,机器重启的时候其实也是在先杀进程其实本质上就是关闭Socket仍然是先触发四次挥手 3)机器掉电/网线断开: 这是属于一种突发情况机器来不及进行任何动作的不知道要断开连接直接拔电源偷袭成功就比如说咱们的台式机直接拔电源咱们的操作系统不会有任何反应时间更不会有任何处理措施当电源或网络直接断开时是没有任何时间留给操作系统去反应的, 所以根本来不及去完成四次挥手 1)掉电的是接收方此时另外发送方这边还会继续传输数据发送数据显然发送方不会再收到ACK于是发送方就会进行超时重传操作逻辑重传几次之后接收方也不会进行反应发送方就会认为这个连接已经出现故障了尝试就会进行重置连接(三次握手)重新连接复位报文段,这个时候A就会主动放弃链接然后发送方就主动释放与服务器曾经的连接 信息但是此时连接还是连不上这时发送方就会将连接对应的资源回收掉 2)掉电的是发送方:此时另外一方正在尝试接收数据但此时不会收到任何数据此时的接收方如何知道发送方是挂了呢还是说发送放休息一会暂时没有发呢接收方好像只能干等 3)这是接收方采取的策略时不时的向发送方就会发送一个PING包(心跳包)(很小的报文)不会带有实际的数据此时他也期待对方可以给它返回一个PING(pong包)(探测报文)只是想让他返回一个ACK如果反复发送几次后对方也不应答不再返回ACK接收方就会认为发送方已经挂了通过探测报文检查发送方是不是有回应通过心跳包来进行检测发送方就回收五元组 1)UDP还有一个天然的优势就是支持广播, IP地址中有一种特殊的地址叫 “广播IP”, 通过UDP往广播IP上发送数据报, 此时该局域网内所有的设备都能收到数据 2)比如同一个机房内部的服务器之间通行就可以使用UDP, 因为这种场景下的网络结构相对简单, 网络带宽也是比较充裕的, 转发设备也是比较好的设备, 整体丢包的可能性就比较小了, 这里就可以要求以更高的效率进行传输 1)咱们主机机房的内部机房内部的主机可以进行相互通信机房内部的网络环境比较简单带宽也是比较高的丢包的概率就比较小并且像咱们机房内部的主机的通信也是对效率要求比较高 2)在应用层的基于UDP复刻TCP的机制 3.TCP和UDP有什么区别 1)TCP有连接UDP没有连接 2)TCP可靠UDP不可靠 3)TCP面向字节流UDP面向数据包 3.1)UDP是面向报文的发送方的UDP对应用层交下来的报文不合并不拆分只是在其上面加上首部后就交给了下面的网络层也就是说无论应用层交给UDP多长的报文它统统发送一次发送一个而对接收方接到后直接去除首部交给上面的应用层就完成任务了因此它需要应用层控制报文的大小 3.1)TCP是面向字节流的不论应用层发送的报文长度如何到了传输层后TCP总是把收到的报文看作一串字节流并且把每一个字节都进行编号TCP会根据当前网络的拥塞程度和对方接收缓存的大小决定现在应当发送多长的报文段TCP关心的是必须保证每一个字节都正确无误的传输给对方并不关心传输了多少个报文段和每个报文段有多少个字节这就表明了TCP是面向字节流的 4)UDP 不止支持一对一的传输方式同样支持一对多多对多多对一的方式也就是说 UDP 提供了单播多播广播的功能一份数据可以同时发送给多台主机UDP本身是支持广播的但是TCP本身并不会支持所以只能通过多个连接通过轮询的方式来给每台主机来发送数据 5)TCP对于数据传输没有长度限制UDP对于数据传输有限制只能是64K 6)特别注重速度就是用UDP因为等待ACK要花费更多的时间拥塞控制与流量控制都是在限制TCP的传输效率,UDP不用等ACK所以效率会更高 像王者荣耀,吃鸡使用的是什么协议 都不一定既要保证TCP的一些特性又要保证效率其实都不一定有的协议可能会更好的兼顾到可靠性和效率付出的代价可能是浪费更多的机器资源传输层协议并不仅仅限于TCP/UDP协议知乎上的一位大神自己开发了KCP协议效率变得更高了这样就更适用于即时性更高的游戏例如说超时重传不以500ms为单位而是以更小的单位进行 注意应用层描述的主体是应用程序之间但是传输层描述的是两台主机之间一个主机可以包含多个应用程序 数据链路层是负责设备之间的传送和识别的数据在传输的时候经过多个设备进行数据传输而数据链路层就是负责相邻数据传输和识别设备中的 数据链路层是负责相邻设备之间的数据帧的传送和识别的因为数据在传输的时候需要多个设备进行数据传输数据链路层就是负责相邻设备之间的数据传输和识别 物理层是将数据转化成信号再将信号转化成数据的 TCP保证可靠性: 1)校验和是保存在TCP首部的一个数据TCP的发送端和接收端会采用相同的算法根据发送方的数据计算出一个16位的校验和并且校验和会和数据一起发送给接收端TCP/IP/UDP的校验和都是相同的 2)TCP采用的是动态时间的超时重传机制如果第一次发送的消息丢了那么发送端会在1000ms内再发送一条消息那么它会在更长时间内传输一条消息重复多次之后发现消息还没有正常的发送那么TCP会认为对方主机存在异常会强制断开连接...... 3)TCP之所以要进行三次握手是因为TCP双方都是全双工的所谓全双工是指TCP任何一端既是发送方又是接收方 为什么说TCP面向字节流UDP面向数据包 1)UDP: 发送方的UDP对应用程序交下来的报文在添加首部后就向下交付IP层,UDP对由应用层交下来的报文既不合并也不拆分而是保留这些报文的边界,也就是说无论应用层交给UDP的报文有多长UDP都会照样发送即一次发送一个报文 2)TCP 不论应用层发送的报文长度如何到了传输层后TCP总是把收到的报文看作一串字节流并且把每一个字节都进行编号,TCP会根据当前网络的拥塞程度和对方接收缓存的大小决定现在应当发送多长的报文段TCP关心的是必须保证每一个字节都正确无误的传输给对方并不关心传输了多少个报文段和每个报文段有多少个字节。这就表明了TCP是面向字节流的 TCP(传输控制协议,Transport Control Protocol) UDP(用户数据报协议,User Data Protocol)