博主主页: 码农派大星.
数据结构专栏:Java数据结构
数据库专栏:MySQL数据库
JavaEE专栏:JavaEE
关注博主带你了解更多数据结构知识
目录
1.应用层
之前编写完了基本的 java socket ,要知道,我们之前所写的所有代码都在应⽤层,都是为了 完成某项业务.
应用层是我们以后工作开发中经常用到的,主要涉及两种情况:
1.使用别人已经创建好的应用层协议(比如http)
2.自己定义应用层协议:
1)明确前后端交互过程中,需要传递哪些信息
2)明确组织这些信息的格式
针对信息组织方式有很多种(确保前端后端是同一种方式)
关于组织数据的格式:
1.xml
通过标签来组织数据
<request>
<userID>10</userID>
<position>E45I60</position>
</request>
优点:可读性提高
缺点:标签写起来非常繁琐,传输的时候消耗额外网络带宽
2.json
当前使用非常主流的网络通信的数格式
通过键值对来组织数据
{
"userID":"10",
"position":"E40I60"
}
优点:可读性较高,比xml简洁
缺点:传输的时候消耗额外网络带宽
3.yml(yaml)
强制要求了数据的格式
request:
userld: 1001;
position:"E60N45"
优点:可读性非常高,不额外消耗带宽
4.google protobuffer
前三个方案,都是关注可读性, protobuffer关注性能,牺牲了可读性(通过二进制的方式组织数据)
而protobuffer直接通过"位置"约定字段的含义,不需要传输key的名字,也会针对传输的数值,进行二进制的编码,起到一些"压缩"效果,极大缩短了传输的数据体积,带宽消耗就越小,效率越高.
2.传输层
传输层包含两个重要的协议UDP和TCP。
UDP:无连接,不可靠,面向数据报,全双工
TCP:有连接,可靠,面向字节流,全双工
端口号:占2个字节写一个服务器的时候必须手动指定一个端口号,用来区分不同程序。但写客户端的时候不用手动指定,系统会自动分配。同一时刻,同一个机器上,同一个协议,一个端口只能被一个进程绑定,一个进程可以绑定多个端口
1-1023称为知名端口号,给一些比较知名的服务器使用例如:22为ssh服务器,80为http协议,443为https服务器。1024-65535为普通端口号。
1.UDP协议
UDP报头:一共占8个字节,每一个部分都各占两个字节。
UDP报文长度:占2个字节,最长65535即64kb,这个长度是定长的,不能最初修改。
校验和: 网络传输过程中,受到环境因素(电信号,电磁波,光信号)的影响,使里面的传输信号发生改变,校验和存在的目的就是为了"发现"或"纠正",这样的错误.
随着业务发展,UDP的64KB已经不够满足业务要求了 ,所以TCP就很好来解决,并且对长度就没有限制了,自身也有可靠传输机制,代码修改成本也比较低
2.TCP协议
TCP全称为"传输控制协议(Transmission Control Protocol"),要对数据的传输进⾏⼀个详细的控制
源/⽬的端⼝号:表⽰数据是从哪个进程来,到哪个进程去;
32位序号/32位确认号: 后面介绍 ;
4位TCP报头⻓度:表⽰该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最⼤⻓度是15 * 4=60
保留(6)位: 充分吸取UDP报文长度无法扩展的教训,保留(6)位用来考虑未来的可扩展性,用来未来新增某个属性或者某个属性长度不够用,就可把保留位拿出来用.
6位标志位:
◦ URG:紧急指针是否有效
◦ ACK:确认号是否有效
◦ PSH:提⽰接收端应⽤程序⽴刻从TCP缓冲区把数据读⾛
◦ RST:对⽅要求重新建⽴连接;我们把携带RST标识的称为复位报⽂段
◦ SYN:请求建⽴连接;我们把携带SYN标识的称为同步报⽂段
◦ FIN:通知对⽅,本端要关闭了,我们称携带FIN标识的为结束报⽂段
2.1.确认应答
TCP的一个重要机制就是可靠传输,可靠传输就是当发送方把信息发出去,能够知道接收方是否接受了数据,一旦发现对方没有接受数据,那么发送方就会通多一定的手段来进行补救。
当发送方把信息发出去,接收方如收到信息,那么就会返回一个应答报文给发送方,如果发送方收到了这个报文,那么就证明信息发送成功了。但是在这个过程中可能会出现一些问题:例如我连续发送了两次请求,但是我后发送的消息却先响应了。
为了避免出现这样的情况,TCP就需要确保应答报文和发出去的数据能够相互对应,即使出现后发先至的现象时,仍然能够按照正常的顺序来理解数据:我们发数据的时候就给数据编一个号,同时接收端做出响应也编一个号,这个时候就需要用到报头中的32位序号和32位确认序号。
-------------------------完美分割线-----------------------
TCP将每个字节的数据都进⾏了编号.即为序列号
每⼀个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;下⼀次你从哪⾥开始发.
接收方这边调用read的时候如果没有数据,就会阻塞等待(前面代码写的是scanner读取, 本质上就是调用InputStream.read),此时虽然1001-2000这个数据到了,但是B不会让read解除阻塞,读到1001-2000这个数据还是要继续阻塞等待.直到说1-1000这个数据到达之后,read才会解除阻塞才会读取到1-1000,1001-2000,2001-3000数据,确保发送方write的顺序和接收方read的顺序是始终保持一致的
B接收方这边,操作系统内核里,会有一段内存空间,作为"内存缓冲区",收到的数据,就会先在接收缓冲区排队等待,只到开头的数据到了,应用程序才能真正读取到里面的数据.
2.2 超时重传
确认应答描述的是一个比较理想的情况,但是如果数据在网络传输过程中出现了丢包现象该如何处理呢?
为什么会丢包呢?
产生丢包的原因有很多种:
1.数据传输过程中,发生了bit翻转,收到这个数据的接收方/中间路由器什么的,计算机校验和,发现校验和对不上了
此时发现错误,要及时止损,不能将错就错!!!,此时就会把这个数据包丢弃掉,不继续往后转发/不交给应用层使用.......
2.数据传输到某个节点(路由器/交换机),这个节点,负载太高了
负载太高: 某个路由器,单位时间只能转发N个包,现在网络高峰期,这个路由器单位时间需要转发的包超过了N(发不过来了). 后续传输过来的数据就可能被这个路由器直接丢弃掉......
发生丢包,完全随机,不可预测.....
发送方发送数据之后,会给出一个"时间限制",如果在这个时间限制之内,ack没有收到,就会被认为数据丢包了
丢包之后,发送方会设置一个"超出时间",在时间之内,没有收到反馈的ack,就会认为数据丢包了...
发送方无法区分,是下面两种哪种情况,发送方都会在达到超出时间之后,重传数据......
丢失重传,理所应当.
此时B收到了两份一样的数据,TCP会针对上述情况处理: 接收方有一个接收缓冲区,收到的数据,先进入到缓冲区,后续再收到的数据,就会根据序号,再缓冲区中,找到对应位置(排序),如果发现,当前序号1-1000这个数据,已经在缓冲区中存在了,就会直接把新收的这个数据丢弃了(确保应用程序调用read读出来的是唯一的)
超市重传的 时间 设定
这里的时间,不是固定值.而是动态变化的....
发送方第一次重传,超出时间是他t1,如果重传之后,仍然没有ack,还会继续重传,第二次重传,超出时间是t2, t2 > t1,每多重传一次,超出时间的间隔,会变大/重传的频次,会降低
经过一次重传之后,就能让数据到达对方的概率,提升很多,再重传一次,又会提升很多,反之,如果重传几次,都没有顺利到达,说明网络的丢包率,已经达到了一个非常高的程度 => 网络发生了重大的严重故障,大概率没法继续使用了,再重传的快,也无意义
重传也不会无休止的进行,当重传达到一定次数之后,TCP不会尝试重传,就认为这个连接,已经坏了,先尝试9进行"重置/复位连接",发送一个特殊的数据包"复位报文".如果网络这会恢复了.复位报文就会重置连接,是通信可以继续进行,如果网络还是有严重问题,复位报文也没有得到回应,此时TCP就会单方面放弃连接(连接就是通信双方各自保存对方的信息),发送方释放掉之前保存的接收方信息,这个连接也就没了.
确认应答和超时重传互相补充,构造了TCP的可靠传输机制
2.3 连接管理
在正常情况下,TCP要经过三次握⼿建⽴连接,四次挥⼿断开连接
三次握手(建⽴连接):
当A想要和B建立连接时候,会先发送一个不带任何‘业务数据’的包,这个包就称为同步“同步报文段”SYN.当B收到这个包时会给A反馈一个ACK应答报文,同时也会给A发送一个SYN,当A收到此包时,会返回一个ACK此时建立连接就完成了。4次握手是可以的,但是没有必要。
三次握手的作用/意义:
1.投石问路: 初步验证通信链路是否通畅(可靠传输的前提条件),就好比早班第一趟空车跑的地铁
2.确认通信双方的发送能力和接收能力是否正常
3.让通信双方在进行通信之前,对通信过程中需要用到的一些关键参数,进行协商
解释: 针对一些重要的参数进行协商比如tcp的序号从几开始,每次建立的时候,都会协商出一个比较大的,和上次这个设定可以避免“前朝的剑,斩本朝的官”,比如当网络不好时,客户端与服务器会尝试重连,如果这个时候旧连接的数据姗姗来迟那么这种迟到的数据就应该丢掉,不应该影响现在的业务逻辑,那么如何区分这个数据是来自前朝还是当朝呢?就可以通过tcp的序号来判断,当接收方发现接收到的数据和序号和当前数据的序号差别非常大,就可以认为这个数据是前朝的就可以直接丢弃掉。
LISTEN: 服务器进入的状态,服务器把端口绑定好,相当于进入了listen状态了,此时服务器就已经初始化完成,准备好随时迎接客户端了(相当于手机开机,信号良好,随时可以有人来打电话)
ESTABUSHED: 客户端和服务端都会进入的状态, TCP连接建立完成(保存了对方的信息了),接下来就可以进行业务数据的通信了(电话接通可以说话了)
四次挥手(断开连接):
断开连接,不一定是客户端主动,服务器也可以主动断开
通信双方,各自给对方发送"FIN",各自给对方返回"ACK",三次握手,三次,是因为中间两次的交互,合并在一起了,对于四次挥手来说,中间两次,不一定能合并(大概率不能),对于三次握手来说,中间两次,ACK+SYN,都是在内核中,由操作系统负责进行的时机都是在收到SYN之后,此时同一时机,就可以合并了 . 对于四次挥手来说,ACK是内核控制的,但是FIN的触发,则是通过应用程序,调用close/进程退出来触发的
CLOSE_WAIT: 被动断开连接的一方,会进入这个状态,先收到FIN的一方
如果发现,服务器这端,存在大量的 CLOSE_WAIT状态的TCP连接,说明此时服务器代码可能有bug,排查close是否写了,以及是否及时执行了
TIME_WAIT: 主动断开的一方,会进入这个状态,此处的TIME_WAIT按照时间来等待,达到一定时间之后,连接就也释放了
为什么不直接释放,而是要等待一定时间呢? 就是为了防一手最后的ACK丢包
2.4 滑动窗口
TCP的可靠传输会影响传输的效率,滑动窗口可以缩短确认应答的等待时间,就可提高传输效率。
原本是每收到一个报文才发送一个数据:
引入滑动窗口只是,就不等ACK回来就直接发送下一个数据,,但批量传输也是有上限的,达到上限之后,再等待ack,批量传输的数据多少就是滑动窗口的大小。
当A给B批量发送了4份数据,此时A已经达到滑动窗口的大小,就不能再发数据了,当收到一一条ACK之后才能再发数据,窗口越大,等待的ack就越多,此时传输速率就越高
那么如果出现了丢包:
⼀:数据包已经抵达,ACK被丢了.
这种情况下,部分ACK丢了并不要紧,因为可以通过后续的ACK进⾏确认
⼆: 数据包就直接丢了.
当1001-2000这段报⽂段丢失之后,发送端会⼀直收到1001这样的ACK,就像是在提醒发送端"我想要的是1001"⼀样
• 如果发送端主机连续好几次次收到了同样⼀个"1001"这样的应答,就会将对应的数据1001-2000重新发送
• 这个时候接收端收到了1001之后,再次返回的ACK就是7001了(因为2001-7000)接收端其实之前就 已经收到了,被放到了接收端操作系统内核的接收缓冲区中
2.5 流量控制
接收端处理数据的速度是有限的.如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送 端继续发送,就会造成丢包,继⽽引起丢包重传等,所以TCP⽀持根据接收端的处理能⼒,来决定发送端的发送速度.这个机制就叫做流量控制
发送方的发送速率不应该超过接收方的处理能力, 当数据到达B中的内核时,此时接收方有一个接收缓冲区,A给B发的数据会先到达接收缓冲区,在通过read读到应用程序中,但数据被read了就可以从缓冲区内删除。接收方每次接收数据时,都会把缓冲区的剩余大小返回给发送方,发送方就会按照这个数值来调整下一轮速度
假设在这个过程中,接收方的应用程序还没来得级处理任何数据,每收到一个数据,缓冲区剩余空间就减少1。最开始B返回剩余空间为3000,那么A收到这个消息就确定了这一轮的窗口大小,当反馈为0就表示B已经满了,暂停发数据,那么要暂停多久呢?那么超过超时重传的时间,A就会发出一个窗口探测的包,这个包不携带任何业务数据,只是为了触发ack查询当前接收方的剩余缓冲区空间。
2.6 拥塞控制
流量控制是考虑对方的接收能力,拥塞控制就是在通信过程中的节点情况,由于中间某一个节点已经达到了处理能力的上限,那么对传输速率也是会有影响的。
TCP引⼊慢启动机制,先发少量的数据,探探路,摸清当前的⽹络拥堵状态,再决定按照多⼤的速度传输 数据
先以一个较低的速度发送,如果这个过程非常顺利,并且没有包丢失的情况,那么尝试增大速率,随着速率增大即窗口大小不停增大,达到一定程度过后,中间节点就可能出现问题,出现数据包丢失的情况,这个时候,就把窗口的大小调整小,如果还是丢包就调整更小,发现不丢包以后就尝试逐渐变大。
流量控制和拥塞控制都会限制发送窗口,两者同时起作用,最终实际发送窗口大小,取决于两者取得发送窗口的较小值
2.7 延时应答
正常情况下,当A把数据传送给B,此时会立即返回ACK,但是有的时候A传输给B之后,此时B会等一会再返回ACK,本质上是提升传输效率,延时返回ACK就是给接收方更多的时间来读取缓冲区的数据,此时返回缓存区的剩余空间就更大。
2.8 捎带应答
捎带应答就是在延时应答的基础上,引入的提升效率的机制,把返回的业务数据和ack,两者合二为一了
2.9 面向字节流 (粘包问题)
这里有一个粘包的问题,当多个应用从数据从A传输给B时,此时,会先进入B的接收缓冲区,在从接收缓冲区读取数据,但TCP是面向字节流的,每次从缓冲区读取多少数据都是随机的,可以读取一个字节也可以读十个字节,但最终的目的都是为了得到一个完整的应用层数据包,B程序就不知道从哪儿到哪儿才是一个完整的数据包。
但对于UDP协议来说,没有存在这样的粘包问题.
解决粘包问题:
1.指定分隔符
2.指定数据长度
2.10,异常处理
1)进程崩溃
Java中的体现,就是抛出异常,但是没人catch,最终到了JVM这里,JVM进程就会直接崩溃了
看起来是崩溃,挺严重,实际上操作系统会进行善后,当进程崩溃的时候,进程中的PCB就要被回收=>PCB中的文件描述表里对应的所有文件,也会被系统自动关闭,其中针对socket文件,就也会触发正常的关闭流程(TCP四次挥手)
2)主机关机(正常流程关机)
正常流程的点击关机按钮,此时操作系统就会先清理掉所有进程(如1)),清理的过程中,同样会触发四次挥手
a)四次挥手非常快,四次挥手已经完成了,关机动作才真正完成
b)四次挥手没来得及完成,关机就完成了
3)主机掉电(拔电源)
a)接收方掉电
b)发送方掉电
4)网线断开
TCP与UDP
TCP的优势在于可靠传输
UDP优势在于性能