🤡🤡🤡个人主页🤡🤡🤡
🤡🤡🤡JavaEE专栏🤡🤡🤡
🤡🤡🤡上一篇文章:【JavaEE】网络编程——UDP🤡🤡🤡
文章目录
前言
端口号:
概念:是描述同一主机不同程序的
同一时刻,同一个协议下,同一个主机上的端口号只能绑定一个应用程序,但是在不同的协议下是可以一个端口号绑定多个应用程序,无论是否是同一个协议一个应用程序都可以有绑定多个端口号。
在业务中会经常见一个服务器中会有多个端口号
- 给客户端提供业务功能的这样的端口称为"业务端口"
- 程序猿还需要对这个服务器进行更精细的控制,如让服务器重新加载配置/开启某一个功能/重新启动/重新加载数据/修改某个选项设定,服务器就会提供一个端口号“管理端口”
- 日常开发会遇到一些bug,需要查看服务器的运行状态,服务器不能直接用调试器去调试(调试器一条是就会把服务器阻塞住,无法给别的客户端提供服务了),这时候就需要提供一个端口“调试端口”,通过网路的方式,给服务器发送调试请求,服务器返回对应的关键信息。
1.UDP
1.1报文格式
我们主要了解的是UDP的报文格式
UDP数据报主要由UDP报头和UDP载荷组成的。
1.2UDP长度
UDP长度是指UDP报头与UDP载荷一起的大小为64KB
由于一个UDP数据报的长度只有64KB,所以在传输的时候就会有很大的制约,必须确保传输的单个数据不能超过64KB
1.3UDP校验和
在网络传输中会有出错的情况,主要引起的原因是比特翻转,为什么会出现比特翻转,是因为数据本质上是0101这样的数据在传输,而这些数据本身就是光信号和电信号,在网络传输的过程中很难没有其他因素不会干扰光信号和电信号将本身要传输的数据为0,但在这种情况下传过去的数据是1,那么这样传输过去的数据就会出错,所以我们需要校验和这样的方法来保证我们的数据在传输的过程中是正确的。
校验和是什么?
校验和就是引入冗余信息,通过这些冗余的信息来验证原有的数据
打个比方:我们去采购一些物品,我们将要采购的物品一一列举出来,然后我们在统计我们需要采购多少物品,这个统计采购物品的多少就是冗余信息。
要对数据校验有两层
第一层:能够发现是否出错
第二层:最好发现哪一位出错,并且将这一位纠错
在UDP数据报中做到第一层使用的校验和方法是CRC方法
CRC
CRC是一个简单粗暴的计算校验和的方式,使用循环冗余校验
也就是将数据的每一位通过循环的方式累加起来。
除了CRC还有一种方式可以完成第一层校验和那就是md5(做业务的时候常用)
md5的特点:
- 定长:无论你输入的内容是多长,得到的结果都是一样长
- 分散:输入的内容,哪怕只有一点被改变,那么结果都会差异很大
- 不可逆:通过原数据计算md5值成本很低,但是将md5还原成数据成本就很高。
1.4UDP的主要用途
应用于对于性能要求比较高,但是对于可靠性要求不高的常见——分布式系统中,多个服务器之间的相互通信(多个机器在同一个机房中,网络结构简单&带宽充裕)
2.TCP
2.1报文格式
报文分报头和载荷(数据),TCP报头中有一个位置是叫选项,这个选项就是可有可无的,在选项以上的是必须的,这个选项类似我们在买车的时候,有些东西是必需的,有些是按照你自己需求加配的。
4位首部长度:表示的长度是报头的长度,这个长度为60个字节。
选项:除去选项上面的长度固定的20个字节,那么60-20=40个字节,选项最多有40个字节。
保留6位:提前申请了一块空间,这个空间暂时不需要用,等到以后需要扩展的时候,那么这个空间就能用上了。
这6个标志位是TCP报文中的灵魂。
ACK:置1表示应答报文
RST:置1表示复位报文
SYN:置1表示同步报文
FIN:置1表示结束报文
16位校验和:与UDP的校验和类似
32位序号:编号是按照字节来编号的,一个字节就分配一个序号,这个序号是针对发送报文的。
32位确认序号:取应答数据最后一个字节的序号加1,这个序号是针对应答报文的。
16位窗口大小:就是指传输数据的窗口大小,这个窗口大小关系到传输数据的效率。
2.2TCP十大核心机制
2.2.1 确认应答
发送方将数据发送给接收方,接收方要有一个回应,发送方才确认数据发送给了接收方,接收方这一过程就是确认应答,接收方确认应答这一个回应,也是将回应的数据发送给发送方,那么这样的数据报文称为"应答报文"。
应答报文:如果此时接收方要将应答报文发送到发送方,那么标志位ACK就会置1,提示这个报文就是一个应答报文。
如果发送方一下就发送了许多条数据,那么接收方还是根据发送方的顺序来接收数据的吗?
肯定不是的,因为网络传输有一个特性"后发先至"什么叫后发先至?
后发先至:由于网络是一个多线路的通信,一开始有可能都是在一条线路上通信,但是当这条线路发生堵塞,那么其他的数据就会寻找其他线路去传输数据,这样就会导致后发先至的情况发生。
后发先至是无法避免的,为了解决这个问题只能对数据进行编号,并让应答报文的编号与发送报文的编号能够对应起来,这样即使出现了后发先至也不影响数据的传输。
这里的编号不是按照一条一条来编号的,而是按照字节来编号的,一个字节分配一个32位序号,载荷中第一个字节的序号就放在报头中,由于序号是递增的,所以其余的序号可以根据报头中的序号得到。应答报文中报头中的序号就是发送报文中的最后一个序号加1.
如果在传输数据过程中出现了丢包的怎么办?
什么是丢包?
每一个交换机和路由器的转发能力都是存在上限的,一旦一台设备需要转发的数据量超出自身的极限,此时多出来的部分就可能被直接丢弃掉,这个就是丢包。
2.2.2 超时重传
超时重传就是解决丢包的策略。
发送方可以通过确认应答来知道接收方是否接收到数据,发送方就可以根据是否接收ACK来判断是否丢包。
发送方发送出数据之后,到正常收到ACK肯定会经历一段时间,发送方就会进行一段时间等待,如果等待时间超出了某个阈值还没有收到ACK,那么可以认为出现丢包,发现丢包,那么发送方就重新传一份数据给接收方。
根据上图:站在主机A的角度由于接收不到主机B返回的ACK,就会触发重传,那么主机B就会收到两份一样的数据,TCP接收方就会根据序号来对数据去重,在TCP这一层它不会在意数据重复的事情,它只要保证应用层在读取的时候不要读取到重复的数据即可。
接收方的操作系统内核存在一个数据结构"接收缓冲区"类似于优先级阻塞队列
这个接收缓冲区有两个重要的特点:
1.去重
接收方将接收到的数据放入这个队列中,在放的时候会根据当前数据的序号在队列中进行判断,判断这个数据是否在队列中存在或者曾经在队列中存在过(这个数据被应用程序读取走了),只要存在过,那么新的数据就不会进入队列而是直接丢弃。
2.排序
网络传输的过程中是后发先至,是乱序的,但是不要紧在接收缓冲区里会对收到的数据线排个序,让序号小的在前头,序号大的在后头,并且数据与数据之间的序号始终都是连续的。
超时重传的超时的时间是动态的,比如第一次等待的时间是100ms就触发重传,下一次有可能就是等待200ms再触发重传,随着重传的次数等待的时间就会增加,这个等待的时间不一定是线性增加,具体怎么增加取决于系统的具体实现,换而言之,重传的频率越来越低。
如果网络出现了严重的故障,重传了很多次,还是不成功达到一定的阈值,就会尝试"重置连接"
重置连接:触发一个"复位报文"(就是标志位RST置1)尝试重置连接(相当于连接重新开始)重置就是通信双方清空之前的TCP传输过程中的中间状态(比如把接收缓冲区里的数据都不要),如果RST报文也无法顺利完成,此时重置也失败,只能断开连接(释放掉对端信息的数据结构)
TCP的可靠传输:是基于确认应答和超时重传这两个机制。
2.2.3 连接管理
建立连接的流程:三次握手
断开连接的流程:四次挥手
握手:两个主机之间第一次连接,只是打个招呼,没有实质上的数据交互。
两个主机在挥手和握手的过程中,传输的网络数据报,不携带任何业务上的数据。
2.2.3.1三次握手
建立连接,就是通信双方各自保存对端的信息,具体完成上述过程,需要经过三次网络交互。
三次握手第一次交互是由客户端发起的
客户端先发送报文给服务器,这个报文叫作同步报文也就是标志位的SYN,当该标志位置为1,则就是发送同步报文,此处的同步就是希望客户端和服务器之间达成某种配合的关系,使双方构建联系,当服务器接收客户端发送过来的同步报文,服务器就会立即发送ACK报文并紧接着发送同步报文给客户端,这两个过程可以合并成一个网络数据(所谓的合并就是在一个报头中将ACK和SYN都置为1),当客户端接收到了服务器发送过来的ACK和SYN报文立即就会返回ACK报文给服务器。传输的SYN报文的载荷是一样的(载荷数据都是为空)报头是不一样的。
为什么是三次交互不是四次交互呢?
四次交互也是没有问题的,但是能合并就合并,毕竟每多一次交互,传输的效率就会降低,由于数据从客户端传输到服务器途中需要经历不断的封装分用。
三次握手的时候,相当于让双方各自保存对方的信息,必须得双方都保存好信息,连接才算建立完成。
在编写代码的时候,客户端在实例化Socket对象的时候就在系统底层触发三次握手,当调用accept的时候其实已经三次握手完成了,应用层把传输层(系统内核)搞好的连接信息给取出来了。
应用层 是由应用程序完成的
传输层/网络层 都是在系统内核完成的
数据链路层 是在驱动程序完成的
物理层 是由硬件完成的
三次握手的意义:
1)"投石问路"在正式传输业务数据之前先确认一下通信链路是否畅通,也相当于TCP可靠传输的一种保证方式,但是只是一种辅助的机制。
2)通过三次握手来验证通信双方的发送能力和接收能力是不是正常的。
试想一下,三次握手变成两次握手是否可行?
显然是不行的,此时的服务器对自己的发送能力和客户端的接收能力是无法确认正常的,需要第三次交互,客户端将所掌握的情况告知给服务器。
3)三次握手的过程中还需要协商一些必要的参数
协商:是指双方通信共同商量一下,并不是单方面可以确定的,需要双方共同来确定下来的。
比如协商出来的参数有序号
序号并不是从0或1开始的,而是通过双方协商确定的,第一次连接和第二次连接协商出来的序号是差异很大的。
举个例子:
通信双方先建立连接,进行了若干次传输,但在某个时刻断开了连接,过了一会儿又重新建立了新的连接,进行了若干次传输,但在第一次连接断开之前还有一个数据报由于在网络通信的过程中"绕了远路"导致在断开连接之前还未到达服务器,但此时第二次连接已经进行了多次网络通信,这个数据报才到达服务器,那么服务器会接收这个数据报?肯定是不会的,新的连接,对应的服务器都是不一样的,应用程序也有可能是不一样的,所以选择直接丢弃这个数据报。
根据上方描述应该如何去区分不同连接的数据报?
可以根据序号来区分,因为不同的连接协商出来的序号差异性很大,只需要将上一个连接的数据报的序号与当前的数据报的序号比较,只要相差很大,那么就不是同一个连接。
小结:三次握手的意义(经典的面试题TCP为什么要三次握手)
1)"投石问路"确定通信路径是畅通的
2)验证通信双方的接收能力和发送能力是正常的
3)协商必要的参数如序号等等
经典面试题:TCP为什么必须要三次握手,两次握手可不可以,四次握手可不可以?
两次:不行,服务器这边对于通信双方的发送能力和接收能力还没有验证完成
四次:行,但是没必要,拆开中间这次的交互,虽然不会影响TCP的正常功能,但是性能会有损失。
TCP三次握手的状态:
listen:是服务器出现的状态,当服务器绑定端口成功之后,就会进入listen状态。
estabished:双方连接建立完成,就是estabished状态。
2.2.3.2四次挥手
三次握手是客户端先发起的,但在四次挥手并不一定是客户端先发起,服务器也可以先发起。
但此时我们以客户端先发起为例
客户端在调用socket.close方法或者客户端进程结束就会发送FIN报文给服务器,那么服务器有两种可能:
第一种就是立马返回一个ACK报文,然后等服务器调用close方法之后再发送FIN报文给客户端,发送ACK报文和FIN报文之间隔了一段时间,这种情况就不会将这两次发送的报文合并在一起,第二种就是TCP的另一种机制(延时应答)会阻碍一会再返回ACK报文,这种情况就可以将服务器发送的ACK报文和FIN报文合并成一个网络数据,但是这种是特殊情况。
断开连接是指通信双方将各自保存对方的数据全部删除,达到互删的状态。
TCP四次挥手的状态:
Close wait :就是被动方在等待调用close方法或者直接进程结束的状态,所以及时调用close这种状态就越不容易看到
Time_wait:是一个确认被动方是否收到ACK报文的状态,如果被动方没有收到这个ACK报文,那么这个状态就会存在,也就是防止ACK报文丢包的情况,如果主动方发送的ACK报文丢包了,那么被动方就会重新传一个FIN报文给主动方,那么主动方会再一次传ACK报文给被动方,在这个过程中,如果没有这个状态来标记,被动方传完FIN报文,TCP就断开连接的话,假设主动方发送的ACK报文丢包了,触发了被动方的超时重传,那么主动方又面对由被动方发送的FIN报文就会不知所措,所以需要一个状态来标记一下,这个就是Time_wait状态存在的意义。
Time_wait状态也有存在时间的期限,时间大概2MSL,如果超过了这个时间,没收到被动方发送过来的FIN报文,那么就认为对面没传,这个2MSL是拍脑门拍出来的一个MSL通常是1min(这个时间是可以配置的)。
面试题:如果发现服务器端出现大量的Time_wait是什么原因?
出现了大量的Time_wait说明服务器端这边触发了大量的主动断开TCP连接的操作,但是这个操作对于服务器是不科学的,一般都是客户端主动断开连接。
2.2.4 滑动窗口
TCP为了可以高效的完成对数据的传输,则引入了滑动窗口这种提高传输效率的机制。
不引入滑动窗口,每发送一条业务数据,需要等待ACK报文到达,才可以发送第二天业务数据,这样的效率太低了。
引入滑动窗口之后的效果,窗口是指批量发送业务数据的数量多少也称为窗口大小,批量发送就会让等待ACK报文的时间重叠,这样就可以提高传输效率,滑动是指这个批量中的第一个数据如果传输完毕之后,就会自动往后移一位,类似于滑动的效果。
滑动窗口中丢包咋办?
1.ACK丢包
如果出现ACK报文不是按顺序返回的,其实并没关系,因为序号这个概念,假设第一天返回的ACK报文是1001,第二个ACK报文返回的是3001,此时的2001ACK报文没返回,其实当返回了3001的时候就默认2001ACK报文已经返回了,只要不是全部的ACK丢包,那就没关系。
2.数据包丢了
假设1-1000传输过去了,也返回了1001,但是到发送1001-2000的时候数据包丢了,那么发送方继续发送之后序号的数据包,返回方就会当每次发送方发来的数据返回1001ACK报文,提示1001-2000的数据报我没收到,直到发送方发送1001-2000的数据报给返回方,返回方才会返回当时最新的序号给发送方。
其实在这个过程中发送方发送过来的数据报是会存储接在接收方缓存区的,直到补全这个缓存区,才会返回最新的ACK报文
上述丢包处理整个过程是很高效的主要是因为快速重传的的机制存在,对于这个快速重传机制与超时重传机制的关系,其实快速重传机制是在滑动窗口的基础下超时重传的一种特殊情况。
2.2.5流量控制
这里的流量控制具体是针对滑动窗口中的窗口大小的描述。
一般来说,窗口大小越大,那么传输的数据就越多,这样效率就越高,反之,窗口大小越小,效率就越低。
但是TCP是一个可靠传输,所以不能盲目的让窗口的大小很大,这样发送方传输的速率很快,但是接收方处理不过来,这样的话就会导致丢包,那么就需要接收方和发送方协商一下,发送的速率慢一些,这个机制就是流量控制。
接收方根据自己的处理能力来制约发送方发送的速率,是双方处在一个平衡的状态。
接收方有一个"接收缓冲区",可以根据缓冲区中空余的空间大小作为窗口的大小,在报文中报头有一个16位窗口大小的字段,将这个空间大小存储在这个字段中,在选项中有一个窗口扩展因子,由于这个窗口扩展因子的存在,这个字段的大小就不会只有64kb,然后通过返回ACK报文的时候传输到发送方。
根据上图的描述,发送方会周期性的发送"窗口报文",这个报文是不携带业务数据的TCP数据报,主要的目的是为了触发ACK,从而直到接收方缓冲区的情况。
2.2.6 拥塞控制
拥塞控制与流量控制都是搭配滑动窗口使用的。
流量控制是站在接收方来制约发送方的速率,而拥塞控制是站在接收方和发送方之间的中间节点的角度,毕竟在发送方与接收方中间肯定有许多的中间节点,那么在传输数据的时候,中间节点会不会丢包呢?那肯定是会的,超出中间节点的阈值就会丢包,这个阻塞控制就是为了解决这个问题的。
由于中间节点肯定是有多个,那么我们处理的时候就将他们作为一个整体来处理。
在这里处理这个问题,我们采取"实验"的方式
刚开始按照小的速度,小的窗口来发送数据,如果没有出现丢包的情况,那么就加大速度,继续增加窗口大小,增加到了一定程度发现出现了丢包的情况,立马将速度减小并减小窗口大小,然后再观察是否丢包,一直重复这个操作,直到找到一个合适的窗口大小不会出现丢包并且可以以比较快的速度完成传输,按照上述方式动态调整随时适应网络中的变化就是"动态平衡".
上述的实验过程就是拥塞控制。
那么在滑动窗口中的窗口大小是根据流量控制的大小来设定还是和拥塞控制的大小设定呢?
这里就可以运用"木桶原理"哪个小就用哪个。
拥塞控制,拥塞窗口大小动态变化具体咋变的,是否有规律?
2.2.7 延时应答
由于发送方发送过来数据,系统内核就会自动立马返回ACK,延时应答就是不会让ACK立即返回,延时返回。
为什么需要延时返回,为了提高传输的效率,决定传输效率的关键因素是窗口大小,通过延时可以将窗口的大小尽量的变大,此处的延时其实是给应用程序时间来调用数据的。
那是怎么做到提高效率的呢?
发送方传输1kb的数据给接收方,此时假设接收方缓冲区的存储空间是10kb,那么还剩下空余9kb的空间,如果不延时立即返回ack,那么就会将接收方缓冲区还余9kb的信息告诉发送方,那假设延时返回ack,这样就利用着延时的时间给应用程序区消耗,那么发送ACK的话,告诉发送方余下的空间肯定比不延时的空间多,那么窗口大小就比之前大了,这样发送方下次就可以发送大量的数据过来,进而提升了传输数据的效率。
延时的时间具体是延时多少呢?
有两种方式
1)按照一定时间来指定延时多久
2)根据接收方接收的数据量
一般都是这两种方式结合使用。
2.2.8捎带应答
建立在延时应答的基础之上的提升效率的机制
2.2.9面向字节流(粘包问题)
在字节流读取的时候,会出现一个经典的问题那就是粘包问题。
应用程序在调用read方法的时候在读取缓冲区中的内容,无法分清客户端发送过来的哪个到哪个是一个完整的字符串,这样的问题就是粘包问题。
那么应该如何去解决粘包问题呢?
1)使用分隔符
2)约定包的长度
比如:
我们在传输数据的时候约定四个字节空间来存储我们要传输的数据的长度,这样就可以在read的时候读取到数据的长度。
2.2.10 异常情况
1)服务器或者客户端进程崩溃了
2)某个主机关机了
3)某个主机掉电了
4)网线断开
2.3TCP小结
TCP传输的可靠性的机制:
确认应答,超时重传,连接管理,序列号
提升TCP传输的效率机制:
滑动窗口,流量控制,拥塞控制,延时应答,捎带应答