详解TCP(详细版)
文章目录
- 一、概述
- 局域网/广域网
- 局域网
- 广域网
- ip地址
- 端口号
- 协议分层
- TCP/IP五层模型
- 什么是协议呢?
- 封装和分用
- 应用层
- 传输层
- 网络层
- 数据链路层
- 物理层
- 五元组
- TCP协议的格式
- 二、特点
- 1、确认应答
- 2、超时重传
- 情况1、发送超时
- 情况2、应答丢失
- 3、连接管理
- 3.1、三次握手
- 3.2、四次挥手
- 总结一下:
- 4、滑动窗口
- 情况1、应答丢失
- 情况2、发送丢失
- 5、流量控制
- 6、拥塞控制
- 7、延迟应答
- 8、稍带应答
- 9、面向字节流
- 10、粘包问题
- 方法1、设置分隔符
- 方法2、开辟一个区域,存储当前数据的长度
- 11、异常情况
- 情况1、程序崩溃
- 情况2、正常关机
- 情况3、发送方主机掉电或断网
- 情况4、接收方主机掉电或断网
- 三、应用
- 四、总结
一、概述
普及一些网络基础知识
局域网/广域网
局域网
局域网(LAN),全称为Local Area Network,指在较小的地理区域内,通过传输介质连接的一组计算机及其外围设备的网络
局域网又可以称之为内网,在内网当中的计算机设备之间是可以相互进行访问的,但是不同的局域网之间是不可以进行相互访问的,就类似一个班级,在上课的时候班级与班级之间是隔离的,而班级里面的学生就类似于一台台的计算机,在上课的时候不同班级的学生之间是无法进行交流的,一般局域网可以是一个教室也可以是一个学校范围,
广域网
广域网(英语:Wide Area Network,缩写为 WAN),又称外网、公网。是连接不同地区局域网或城域网计算机通信的远程网
局域网通过路由器讲多个局域网连接起来,在物理组成了很大范围的网络就形成了广域网,广域网内部的局域网都称之为广域网的子网
局域网和广域网之间没有明确的范围规定,只是相对而言的,如果把一个学校看作广域网,那么一间一间的教室就可以看作是局域网;如果把一个学校看作局域网,那么一个市就可以看作是一个广域网,因此两者之间是没有明确规定的,仅仅是相对而言
ip地址
P地址(Internet Protocol Address)是指互联网协议地址,又译为网际协议地址
IP地址主要是用来标识网络主机以及网络设备的网络地址,像我们的电脑,路由器,还有交换机都是有ip地址
IP地址可以理解为快递上的地址,通过这个地址就可以找到我们,ip地址也是如此,通过我们的ip地址就可以访问到我们的主机,但是现在不支持了。
ip地址是由4个8位二进制数组成的,一共32位
例:10000000 10000001 10000011 10000111
但是我们一般不用二进制进行表示,很明显就是因为不好记忆,一般使用点分十进制的方式进行表示,就是将8位二进制转化为十进制,十进制之间用“ . ”进行分隔
例:127.0.0.1
现在国际上IP地址主要分为两种,一种是传统的IPv4,另外一种是新兴的IPv6,随着时间的推移从ipv4出现到现在已经有很长时间了,世界上的网络设备也是越来越多了ipv4不够用了,就发明出了ipv6,但是现在通过很多技术还是可以继续使用ipv4
在以前ip地址是怎么分配的呢?只要有一个网络设备上线就会被分配一个ip地址,在以前那个时代相隔十万八千里的主机设备,只要我知道你的ip地址是多少我就可以访问你的电脑和你进行聊天,但是随着网络设备越来越多,IPv4地址分配不过来了,后面就改变了策略不使用这种方式进行分配ip地址了,现在ipv4的地址都称之为公网地址,一般只有服务器能够拥有公网地址,前面我们说了只有局域网之内的主机设备之间可以相互进行访问,但是我们却可以访问CSDN的网站,CSDN的主机总不可能一直在我们身边吧?这是因为CSDN的服务器拥有公网地址,我们只需要得到对方的公网地址就可以访问到对方的服务器。
ipv4目前还是主流,ipv6目前我们国家已经在推广使用了,基本覆盖百分之80 90了吧,ipv6理论上可以为地球上的每一粒沙子都分配一个IP地址
端口号
每一个应用程序在启动的时候都会被操作系统分配一个端口号。在网络通讯里面ip地址可以标识网络上的主机,端口号可以标识网络上主机里面的进程,在发送快递的时候,需要收件人的电话号码和地址,只有得到电话号码快递员才可以联系到收件人进行配送快递
端口号是0 - 65535之间的数字,0 - 1024是热门端口号,比较有名的程序已经持续占用这些端口号,比如http的80端口还有mysql的3306端口,
因此我们能够使用的一般是1024 - 65535之间的端口号,一般一个端口号只能被一个进程绑定,否则就会出现端口号冲突的问题,
原则是一台主机上的端口号在网络上的不同程序之间是不能冲突的,可以理解为一个班级换座位,一个座位只能由一个人坐,不能同时被多个学生坐,这个座位就可以理解为端口号,程序可以理解为学生,一个端口号只能被一个程序绑定
一般服务器的端口号是固定的,客户端的端口号是给系统随机分配的,为什么这样子设置呢?主包的想法是:
首先第一点是服务器是供给用户使用的,用户不会花心思去记服务器的端口号是什么,当我们需要使用的时候我们就直接访问对方的服务器,如果服务器的端口号是随机分配的,那么用户需要使用的时候还需要查一下今天服务器的端口号是什么,不太现实,对于业务的执行有很大的阻碍
第二点就是客户端,客户端去访问服务器端,客户在访问的时候,服务器端有必要记住客户端的端口号吗?今天我的账号在这台电脑登陆,明天在另外一台电脑登陆,如果服务器端全部都要记录的话,一个是消耗大量资源,一个是客户端访问就访问了,没必要记住是在哪里访问的是怎么访问的,这些不是什么重要的数据。还有一台主机上会运行多个应用程序,世界上那么多的应用程序如果全部都去固定一个端口号,1024到65535之间的端口号早没了
因此服务器端固定端口号,客户端的端口号随机分配,一方面是可以节省端口号的消耗,一方面是可以方便我们进行访问服务器
协议分层
我们在使用微信的时候,假设现在主包需要发信息给女朋友,首先我们编辑好信息之后,点击发送,我们的信息是直接发送到女朋友的手机上吗?
nonono不是这样的,我们信息发送到女朋友的手机上的时候,时间间隔很短可能一秒都没有,但实际上中间经过了很多东西,首先经过操作系统,网卡,网线,运营商网络设备
大家是不是很奇怪,什么玩意啊?我手机上面什么时候有网线了
实际上我们的是确实没有网线,但是有网卡,我们的微信将我们要发送的信息打包封装好,将信息通过操作系统发送给网卡,再由网卡发送交换机,交换机发送给运营商,运营商在通过网线最后再发送给女朋友。(交换机,路由器,服务器之间都是由网线进行连接)
为什么要分为那么多的层级呢?直接发送过去不就好了嘛?这是为了让不同的层级只专注做自己的事情,不同的层级以自己的数据处理方式来处理数据,只需要为上层提供API即可,并为下层做数据处理。大家可以理解为流水线,这一部分的员工只会重复做这一部分的工作,做完了之后再给下一个员工做另外的工作,做完当前的工作了之后再拿上一个员工给的再继续做,这样子一方面可以提高工作效率,
上层访问下层的API,当前层不关注上一层下一层做什么应该怎么做,只关心自己的事情
另一方面是微信的程序员没办法去控制操作系统传输数据,也没有办法去控制网卡,网线传输数据,以及网卡,网线里面是怎么对数据进行封装的,因此程序员只需要关注怎么调用操作系统提供的API即可,不需要关注操作系统网卡等设备是怎么进行数据传输的以及怎么进行包装数据。
Java程序员只需要关注调用JVM提供的API(JVM对不同的操作系统提供的API都进行了封装,Java程序员只需要知道JVM提供的方法怎么使用即可,不需要关注不同的操作系统提供的方法怎么调用),C++程序员调用操作系统提供的API,同样的操作系统也不会去关注微信发送什么数据,他只会将应用程序发送过来的数据进行自己的处理(比如转化为字节流存储进入缓冲区),然后再发送给网卡,接下来网卡干什么,操作系统就不管了
由这些不同的设备,最终演化为OSI七层模型
TCP/IP五层模型
七层模型里面的应用层表示层会话层我们一般,合为一层进行讨论,因此我们只聊五层模型
什么是协议呢?
不同的层级有不同的协议,我们程序员只需要关注应用层的协议即可,什么是协议呢?
可以简单理解为是一种规则,一种约定,我们在编写程序的时候拿微信来说,我发送给女朋友的信息,在我的设备上微信是怎么对我的信息进行封装,在女朋友的设备上是怎么解析然后呈现信息给女朋友的,这就算一种约定,只有编写微信这个程序的程序员才知道这个约定里面是怎么做的,这个约定就可以理解为协议,就是怎么对数据进行封装的,再怎么对数据进行分用的,人为规定的。不同的层就需要根据自己的协议来处理数据
应用层:主要是和用户进行打交道的,接收和展示用户的数据,就像发快递的时候,只关注快递本身是什么,重多少
传输层:完成端到端的传输准备,也就是确定收发主机的IP地址和端口号,关注的是快递的目的地在哪里,以及快递的收件人的电话号码
网络层:规划端到端的网络路径,也就是数据在传输过程中会经过的网络设备,比如在快递配送的过程中应该去哪里加油,走那条路最近
数据链路层:完成相邻节点之间的传输,每个网络设备之间的传输,需要注意区分,数据链路层并没有真正的进行传输,到了物理层才开始真正的传输数据,数据链路层只是确定了设备到设备之间应该怎么走
物理层:把真实的BIT数据流转化为光电信息或者电信号在传输介质当中传输
封装和分用
封装就是发送方对数据做的包装处理
分用就是接收方对数据做的解析处理
应用层
应用层:现在我需要在微信里面发送“hello”,那么微信这个应用程序就需要根据程序员定义好的协议(如何进行数据的解析和封装,一般是在设计程序最初就定义好协议),进行数据的封装。(这个是主包模拟的,并非真实的微信协议)
假设是以这种方式进行数据的封装,那么在应用层该数据就会被封装成为
最后再调用操作系统的API进行发送操作,将数据作为载荷发送给下一层
传输层
传输层:传输层最常见的两种协议就是TCP和UDP,我们拿TCP今天的主角来进行讲解,根据得到的载荷,在传输层将载荷根据TCP协议进行封装
tcp头里面主要存储的是源端口号和目的端口号,至于传输层是怎么得到的端口号,一切交给操作系统,封装完成之后再将数据作为载荷发送给下一层
网络层
网络层:规划出端到端之间的网络路径,根据网络层的协议对数据再次进行封装
ip头里面存储的主要是源IP地址和目标ip地址,根据当前的数据就已经可以确定是发送给那台主机上的那个进程了。但是需要注意的是这个目标ip地址可不是我们的好友的IP地址,而是微信服务器的地址,在我们发信息的在一个过程当中,我们是发送信息给微信服务器,再由服务器转发给我们的好友
那既然目标IP地址是微信服务器的IP地址,那么服务器是怎么确定我们好友的ip地址和端口号的呢,当我们登录微信的时候,需要进行TCP三次握手进行建立连接,这个时候服务器就会记录下我们的ip地址和端口号。发送方将数据发送给服务器,服务器就会根据自己维护的登录用户和对应IP地址进行校验,最终找到对方的IP地址和端口号,最后进行发送数据。(这些过程仅仅是为讲解而模拟出来的,具体服务器是怎么进行查找对方ip地址和端口号的,只有微信的程序员才知道)
数据链路层
数据链路层:拿到数据之后,再根据自己的协议进行封装,一般是以太网协议
帧头里面主要存储的是源MAC地址和物理MAC地址,又称为物理地址,物理地址类似于IP地址的存在也是用于标明一台主机,但是物理地址是写死的,只要网卡被生产出来就直接写死了
帧尾里面会有一种算法,会对载荷进行计算得到的一种值就会存储在帧尾里面,通过计算这个数值,发送方解析拿到这个数值之后就可以对载荷进行反验证,相当是验证载荷是否完整,如果数值反验证不正确那么说明数据在传输过程中出现问题了,那么取到的数据就会被丢弃,这个算法比较常见的是CRC冗余校验
物理层
物理层:当物理层拿到数据之后,会把具体的数据转化为光电信号,最后再通过传输介质(网线)传
当光电信号被传输到服务器,服务器再转发到我们的好友主机的物理层,最后再一步一步的分用,最终拿到我们发送的信息进行呈现给好友
小小总结一下:
应用层一般是程序员自己定义协议,数据的格式可以使用XML或者json或者其他都可以,也可以自定义,但是一般不建议自定义,自定义数据格式不好进行维护,比较建议使用json
传输层是使用TCP和UDP协议
网络层是使用IP协议
数据链路层是使用以太网协议
物理层是使用以太网协议
五元组
在TCP/IP协议里面一般使用五元组来标识一个通信
1、源端口号
2、目的端口号
3、源IP
4、目的IP
5、协议
TCP协议的格式
TCP全称为 “传输控制协(Transmission Control Protocol”). ⼈如其名, 要对数据的传输进⾏⼀个详细的控制
TCP协议一次可以读取2byte,第一次读取2byte可以读取到源端口号,我们上面说过端口号,端口号的有效范围是0—65535之间,热门端口号是0—1024,因此真正有效使用的端口号是1024—65535之间
0—15,刚好是两个字节,能够将端口号完整读取,第二次读取的是目的端口
序号和确认序号是用于确认应答机制的,待会会讲到
4位首部字节最大是1111,单位是4byte,因此TCP首部最大是60字节(15 * 4 = 60)
6位标志位:
◦ URG: 紧急指针是否有效
◦ ACK: 确认号是否有效
◦ PSH: 提⽰接收端应⽤程序⽴刻从TCP缓冲区把数据读⾛
◦ RST: 对⽅要求重新建⽴连接; 我们把携带RST标识的称为复位报⽂段
◦ SYN: 请求建⽴连接; 我们把携带SYN标识的称为同步报⽂段
◦ FIN: 通知对⽅, 本端要关闭了, 我们称携带FIN标识的为结束报⽂段
标志位待会会详细讲
是限制滑动窗口大小的,待会会详细讲
校验和就是验证数据是否完整的,和CRC冗余校验算法相同
紧急指针用于标识紧急数,选项我们暂时不做了解
需要注意的是TCP数据报是一长串,而非图片那样一层层的,将其一层层的排只是为了方便观看。
前面五行每行是4个字节,5行就是20个字节,首部长度最大是60字节,那么选项占40字节。
数据部分,理论上是没有长度限制的,但是不同的浏览器可能会有相关的限制
二、特点
1、确认应答
当我和女朋友说:去吃饭吗?女朋友说:好,当我对女朋友提问,女朋友对我答复了这就说明女朋友一定是听到了,只有在一问一答的状态下才可以证明对方肯定是接收到了我的信息。
每个问题都会有一个答复,如此往复下去直到结束都不会有任何的问题
但是如果出现了乱序问题,接收方就不知道对方具体回的到底是哪一个信息,就有可能出现问题
但是如果给每一个回答的编一个号码,那么即便乱序了,发送方也仍然可以正确理解接收方向表达的意思。
在TCP协议当中也是如此设计的,主机A发送一个数据包给主机B的时候,主机B每次应答的时候里面都会告诉主机A下一次从那个地方开始发送。
主机A给主机B发送数据报的时候,在序号里面会存储发送的数据的起点是多少,假设要发送7000字节,那么第一次发送数据长度为1024的时候的起始位置就是1,就会将该起始位置存储到序号里面。
当主机B收到数据报的时候就会计算该数据的长度并读取序号里面的数据,主机B进行应答的时候就会将起始位置 + 数据长度 + 1 存储到确认序号里面,同时还会将标志位ACK置为1,表示这是一个应答,假设主机B计算数据长度得1024,确认序号里面就填1024 + 1
那么主机A收到应答的时候,就会读取确认序号里面的数据并进行校验,如果校验成功那就说明主机B接收数据成功,那么接下来主机B就会按照确认序号里面 的1024为起始位置继续往主机B发送数据
确认应答机制是可靠性机制
2、超时重传
在网络上传输数据会经过很多的网络设备,在这其中就有可能会出现问题,比如是网络波动导致数据报丢失,亦或是断电了等等,都有可能会影响到数据报的传输,因此就有了超时重传的机制,相信大家那么聪明,看到名字就理解是什么意思了
情况1、发送超时
在主机A给主机B发送数据报的时候,中途可能因为网络波动导致数据报丢失了,主机B没有收到数据报,主机B没有收到数据报的情况下就不会给主机A发送ACK应答,那么这个时候主机A也不知道自己没有发送成功,此时主机B就会一直在等待主机A发送数据,主机A就会一直等主机B发送ACK应答,但是主机A不会一直等,当到了某个时间限度的时候主机A就会触发超时重传,重新发送数据报
情况2、应答丢失
主机A给主机B发送数据报,主机B正常收到数据报并计算数据长度将ACK返回,但是在这个过程当中由于网络波动导致ACK应答丢失了,但是主机A和主机B并不知道,主机A依旧在等待ACK应答,主机B在等待下一次数据报,这个时候主机A也不会一直等待,超过了某个时间就会触发超时重传,主机A重新发送数据报,那么这个时候主机B就会接收到相同的数据报,主机B就会根据序号判断出这个数据报重复了,就会丢弃当前数据报并重新发送ACK应答(TCP是能力识别出重复数据报的)
那么这个超时时间应该任何设置呢?
1、在最理想的情况下选择一个最小的时间,保证确认应答一定可以在这个时间之内返回 2、但是这个时间的长短,随着网络环境的不同,是由差异的
3、这个时间如果太短,那么就由可能确认应答还没有正常抵达就已经触发超时重传,导致频繁发送重复的数据报
4,这个时间如果太长,那么就有可能会影响整个重传的效率
TCP为了保证无论在任何环境下都能够有较高性能的通信,因此会动态计算这个最大超时时间
1、Linux(windonws也是如此),超时以500ms为单位进行控制,每次判定超时重传的超时时间都是500ms的整数倍
2、如果重发之后仍然得不到ACK应答,就等到2*500ms后进行重传
3、如果仍然得不到应答,就等待4 *500ms进行重传,以此类推,以指数型式进行递增 4、累计到达一定的重传次数,TCP会认为网络或者多端主机出现异常,就会强制关闭连接
3、连接管理
在TCP建立连接 的时候需要进行三次握手,主要是进行验证双方的收发能力是正常的
在TCP断开连接的时候需要进行四次挥手,保证发送方和接收方有效安全的断开连接
3.1、三次握手
当主机A第一次给主机B发送连接请求的时候,会把标志位SYN设置为1,表示这是一个请求连接的报文,并且会为序号设置一个随机数。
当主机B接收到主机A的连接请求的时候,主机B验证了主机A的发送能力没有问题,主机B会将报文中的序号读取出来,并+
1存储到确认序号当中,设置ACK为1 作为应答返回给主机A。
当主机A收到主机B的应答时,主机A确定主机B的收发能力没有问题。
主机B向主机A发送连接请求,将SYN设置为1,并设置一个随机数存储到序号当中,发送给主机A
当主机A收到主机B的连接请求的时候也会将序号里面的数据取出来并 + 1 存储到确认序号当中,并将ACK置为1作为应答发送给主机B,当主机B收到主机A的应答,此时主机B确认了主机A的接收能力。 自此双方的接收能力都正常,那么就建立连接成功。
大家观察会发现,这好像是发送了四次,可能会想这什么玩意啊?骗我的吧说是三次握手结果你给我讲一个四次握手?大家不要急哈,大家再仔细观察观察,会发现第二次和第三次好像可以重合,好我们试一试。
好像是可以的,在主机B进行应答的时候,我们同时可以把这个应答设置为连接请求,SYN设置为1,重新设置一个随机数存储到序号,ACK设置为1,将接收到的序号解析出来并
- 1存储到确认序号。这样子真正的三次握手就出来了。当三次握手结束,连接建立
3.2、四次挥手
大家可能会奇怪为什么建立连接可以从四次优化变成三次,但是断开连接却是四次?大家不要着急,等主包絮叨絮叨
主机A发送报文给主机B,将FIN设置为1,表示是断开请求。
主机B接收到主机A的断开请求后,主机B知道主机A的任务完成了就发送响应ACK给主机A。
过了一段时间主机B再次发送断开请求报文给主机A
主机A接收到主机B的断开请求后,发送响应给主机B,最终成功断开TCP连接
大家可能会觉得,主包又装,大家都可能会觉得第二次和第三次挥手可以合为一次挥手觉得主包错了,但实际上这两次挥手正常情况下是不会合为一次挥手的,首先第一点,主机A在发送完数据完成它自己的任务之后,但是主机B不一定也完成任务了,他可能并没有处理完数据,因此当主机A发送了断开连接请求之后,主机B也发送应答表示自己知道了,接着主机B会继续处理自己的任务,直到处理完自己的任务后会再发送断开连接给主机A。
主机B的第一次发送ACK应答是操作系统级别做出的回答,这个是TCP协议本身自己设计的,和应用层无关,因此才会有发送响应之后继续做自己的任务,就是应用层层面的任务还没有做完,第二次发送断开连接请求的时候,当调用close方法触发的。因此说第二次和第三次不太可能会合在一起
总结一下:
1、三次握手:
假设是刚开始打电话:
A:喂?(B确认A可以说话)
B:我听见了,你听得见吗?(A确定B可以听见也可以说话)
A:好的,我听见了我听见了(B确认A可以听见)
2、四次挥手
依旧是A和B打电话
A:我说完了
B:哦,好的(继续说自己的,但是A已经不说了)
B:我也说完了
A:好的
4、滑动窗口
TCP的确认应答机制确实很好用,每次数据报的发送都会有对应的响应,一般来说,某一项功能厉害到极致的情况下就会牺牲其他的一些东西,TCP的确认应答机制就是牺牲了效率来保证数据的稳定传输。
因此TCP还有一个机制就是滑动窗口,用来提高传输数据的效率,同时还可以提高网络吞吐量
滑动窗口一次性可以发送多条数据,就是指创建一个虚拟性的窗口,把需要发送的数据全部都装到这个窗口里面,能装多少就装多少,在发送数据的时候,直接发送窗口里面的数据报,中途并不等待应答,这个时候主机B会进入累计应答状态,在接收两条数据后或者是到达某个时间后才会发送应答,应答发送给主机A,主机A接收到应答之后取出确认序号验证后确认主机B该部分的数据已经接收成功了,那么主机A就将窗口中的确认的数据移除,就从期望位置开始添加要发送数据到滑动窗口。
在这一个过程当中,也会出现丢包的情况
情况1、应答丢失
在滑动窗口当中应答丢失不是什么大问题,一次滑动窗口当中有可能会有多个应答。
其中中途一个应答丢失了不会影响什么,因为主机B会统计收到的全部数据的长度,在加上第一条数据的起始位置 再 + 1,返回给主机A,只要返回了任意一个,那么中途丢失的应答就不会有任何影响。
如果是最后一个应答丢失了,那么倒数第二个应答只要发送成功,那么主机A就会读取确认序号,将已经确认的数据从滑动窗口中移除,从期望位置开始发送数据,那么在这个过程当中就会重新发送没有确认的数据,主机B就会接收到重复数据,判断确定的是重复数据就会将其丢弃,最后再重新统计数据长度 + 序号当中的起始位置 + 1 作为确认序号ACK返回
情况2、发送丢失
在滑动窗口当中丢失了其中一个数据,大概率是触发快速重传,当主机B在取数据的时候,发现数据报的上一个序号和下一个序号连接不上的时候,就会发送应答向告诉主机A我当前期望的数据是这个,这个时候主机A还处于滑动窗口发送状态会一直继续发送数据,主机B发送应答后会持续接收所有数据,只要不是期望的 Seq,就缓存并重复发送当前 ACK。当主机A接收到三次重复的ACK的时候就会触发快速重传机制,先发送主机B期望的数据报,当主机B拿到期望的数据报后会根据数据报的序号进行排序,最终讲解数据报丢失的问题
5、流量控制
滑动窗口的存在确确实实提高了数据的传输效率并且提高了网络吞吐量,但是大家想一想,这个窗口可以无线大吗?大到一次直接发完全部的数据,大家记得现实吗?
主包觉得不现实,首先如果一次直接发完为什么不直接使用UDP呢?UDP就是一次发完但是它并不考虑接收方是否能够接收,如果TCP也这样做,那不是相当于也不考虑接收方是否能收到了吗,其次就是在TCP发送的过程中,是从应用层开始写入数据,然后数据会被加载到缓冲区当中(发送方会有发送缓冲区,接收方会有接收缓冲区),当缓冲区被填满或者是被强制刷新后会落盘到网卡当中,紧接着就会被传输到主机B网卡当中,网卡会将数据存储的缓存区当中,在这一个过程当中,接收方的缓冲区一定可以接收每一次的窗口数据吗?
不一定,这个具体得看主机的配置来,配置如果稍微差一些,缓冲区就可能会小,主机在取数据进行计算的时候本身就需要时间,而对方又一直发送同样多的数据,那么迟早缓冲区会被填满,最终导致数据丢失
因此窗口的大小就极为重要,那么这个超时时间应该任何设置呢?
1、在最理想的情况下选择一个最大的窗口,保证缓冲区一定不会填满 2、但是这个窗口的大小,随着缓冲区剩余空间的大小,是由差异的
3、这个窗口如果太小,那么就有可能会影响数据传输的效率 4,这个窗口如果太大,那么就有可能会导致缓冲区爆满
TCP为了保证都能够有较高效率的传输,因此会动态改变窗口的大小,通过主机B的每一个ACK做动态变化。
当主机B要返回ACK的时候,都会计算自己的缓存区剩余空间,并将能够存储最大容量填写到这个“16位窗口大小”当中用于反制发送方,主机A收到应答之后就会根据窗口大小动态调整自己的滑动窗口大小。接受方通过告诉发送方自己还有多少空间的方式来反制发送发的滑动窗口大小
如果主机B剩余的空间大小超过了“16位窗口大小”的最大值,其最大值是65535,如果剩余空间超过了65535,那么在选项当中还包含有一个窗口扩大因子M,可以设置实际窗口大小是窗口字段的值左移M位
如果主机B剩余的空间为0 了,那么主机A会停止发送数据,接下来就会出现主机B接收不到新的数据报就不发送ACK,主机A接收不到ACK就不会发送新的数据报,也就意味着双方通讯停止了。
当发送发过了重发超时的时间以后还没有收到窗口更新的通知,就会向接收方发送一个窗口探测,窗口探测里面不包含任何数据,只是询问接收方是否可以继续处理数据了,只要主机A没有主动发送断开请求,那么就会一直发送窗口探测。
6、拥塞控制
在TCP当中调整滑动窗口大小的依据不仅仅是ACK当中窗口大小,还有当前的网络状态。
现在发送一条信息会 经过很多的网络设备,这些网络设备的网络情况是参差不定的,有些可能会很好,有些可能直接挂机。因此在不了解网络状态的情况下直接发送滑动窗口里面的大量数据,就仍然有可能会引发问题。当TCP三次握手结束之后,发送方拿到的窗口大小大概率是接收方缓冲区的最大值,因此一开始的发送方滑动窗口有可能非常大。
因此TCP引入了慢启动机制,先发送少量的数据,先探路,再决定后面发送多少数据。
这里引入一个拥塞窗口的概念。
1、刚开始的时候定义拥塞窗口的大小为1
2、每收到一个ACK就增大拥塞窗口的大小
3、当拥塞窗口的大小达到慢启动的阈值(ssthresh值)之前是以指数级别增大的,到达阈值之后是线性增长,每收到一个ACK拥塞窗口就 + 1
4、当出现大量丢包的情况(超时重发),就说明出现网络拥塞了,这个时候拥塞窗口就会被调回1,同时阈值(ssthresh值)会变成当前窗口大小的一半。如果是少量丢包会触发快速重传,不会进入慢启动
接下来就是往复1到4步直到断开连接,最终滑动窗口的大小取拥塞窗口和16位窗口大小的最小值
随着TCP的启动,网络吞吐量会变大;随着网络发送拥塞,网络吞吐量开始变小。拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对⽅但是⼜要避免给⽹络造成太⼤压⼒的折中⽅案。
TCP拥塞控制这样的过程, 就好像 热恋的感觉
7、延迟应答
延迟应答(又叫累计应答)在讲解滑动窗口的时候已经提到了,就是说接收到两个数据报或者是某一个时间段后才会进行应答,减少了应答的次数,提高了数据传输效率,同时还可以给接收方从缓冲区当中取数据进行处理的时间,增大了缓冲区的剩余空间,使得发送方能够发送更多的数据报。
具体的数量和超时时间, 依操作系统不同也有差异; ⼀般N取2, 超时时间取200ms;
8、稍带应答
TCP是全双工的,可以发送也可以接收,那么当接收方需要进行发送数据作为响应的时候,如果这个时候接收方刚好需要进行ACK,那么响应和应答就可以合为一体直接发送给发送方
为什么可以合为一体呢?因为响应操作是在应用层工作的,当我们写入“hello”要作为数据发送给发送方的时候,会由应用层传入到传输层进行数据封装,而ACK应答是工作在传输层的,两者就有可能会被合为一条数据报发送出去,本质上是ACK被响应携带发送给了发送方。
9、面向字节流
由于TCP是面向字节流的,当建立TCP连接之后接收方和发送方都会在内核建立发送缓冲区和接收缓冲区
10、粘包问题
由于TCP是面向字节流的,当接收方接收到数据报之后,是网卡先接收到数据报,然后被分用最终会被存储到缓冲区当中,但由于缓冲区是以字节的形式保存数据的,因此一个一个数据报就会粘在一起,主机在提取数据的时候就难以提取到完整的数据,对于数据的解析就会出现问题
解决方法:
方法1、设置分隔符
就是在应用层协议当中规定一个符号作为数据的结尾,这样子数据以字节的形式存储在缓冲区当中的时候,在提取数据的时候只要遇见这个符号就说明当前的数据已经读取完了,但是这样需要规定用户输入的数据当中不可以用户有这个字符,那么这种方式就对用户不太友好了
方法2、开辟一个区域,存储当前数据的长度
在应用层协议当中,定义一个字段用来计算当前用户输入的数据长度,当数据存储到接收方的缓冲区的时候,接收方先读取协议里面规定的这个区域大小,就是先读取数据的长度出来,然后再根据数据的长度来继续读取数据,长度是多少就读取多少数据,这样就可以轻松解决粘包问题,对于用户也是非常友好的(这个区域的大小可以按照业务决定)
11、异常情况
情况1、程序崩溃
系统会回收进程的资源,包括文件描述符表,PCB被销毁,回收的时候相当于调用close方法,触发FIN操作(四次挥手)
情况2、正常关机
和程序崩溃一样,都会触发FIN操作
情况3、发送方主机掉电或断网
如果发送方发生了断电或者断网,接收方会直接丢弃这个连接
我们可以想象一下车联网系统,在车联网系统当中,汽车会定时给服务器发送报文,证明自己还存活着的,这个报文里面可能什么信息都没有只是用于证明该汽车还在线,它有一个名字叫做心跳包,如果汽车长时间没有给服务器发送报文,服务器就会判定该汽车已经下线,有可能出现什么意外了,那么服务器可能就会通知汽车公司的服务人员联系该车主
情况4、接收方主机掉电或断网
如果接收方主机掉电或断网了,那么发送方就收不到ACK,超过重传时间后,发送方会触发超时重传重新发送数据报,多次重传依旧收不到ACK,那么就会重置连接将标志位RST设置为1,如果重置连接也失败了,那么就会放弃连接
三、应用
只要是涉及到网络的,都离不开TCP。
四、总结
各位佬,如果有发现什么错误请指导一下主包进步,大家一起共勉