目录
UDP协议
1.UDP报文结构
报文长度
校验和
TCP协议
1.TCP报文结构
2.TCP的可靠传输机制
1.确认应答
2.超时重传
3.连接管理
4.滑动窗口
5.流量控制
6.拥塞控制
7.延时应答
8.捎带应答
9.粘包问题
10.TCP异常处理
小结
TCPVS UDP
IP协议
1. ip报头结构
1. 4位版本号
2. 4位首部长度
3. TOS8位服务类型
4. 16位总长度
5. 16位标识、3位标志、13位片偏移
6. 8位生存时间TTL
7. 8位协议
8. 16位校验和
9. 32位源IP地址、 32位目的IP地址
2. 地址管理
3.路由选择
客户端与服务器进行交互的时候,客户端需要发送或是接收数据。一方将数据发送给另一方时,需要将这些数据通过某种特定的方式进行打包,而接收的那一方同样也需要按照特定的方式去拆包,这样才能拿到他们想要的数据,而这种特定的拆包装包的方式,就叫作协议。
一般来说,在网络数据传输中,发送端应用程序,发送数据时的数据转换(如java一般就是将对象转换为某种协议格式),即对发送数据时的数据包装动作来说:
1.如果是使用知名协议,这个动作也称为封装
2.如果是使用小众协议(包括自定义协议),这个动作也称为序列化,一般是将程序中的对象转换为特定的数据格式。
接收端应用程序,接收数据时的数据转换,即对接收数据时的数据解析动作来说:
1.如果是使用知名协议,这个动作也称为分用
2.如果是使用小众协议(包括自定义协议),这个动作也称为反序列化,一般是基于接收数据特定的格式,转换为程序中的对象
当前较为常见的两种应用层协议为UDP和TCP,下面我们就这两种常见的协议,谈谈他们背后的一些原理结构。
UDP协议
UDP的特点:无连接、不可靠、面向数据报、全双工。
无连接:知道对方的端口号和IP就可以直接进行传输,不需要建立连接
不可靠:没有任何安全机制,发送端发送数据之后就不管对方有没有收到了
面向数确起到认据报:应用层交给UDP多长的报文长度,就原样发送,既不会拆分也不会合并
全双工:UDP的socket既可以读也可以写,这就叫全双工(派出一个间谍,间谍和上层可以双向联系就是全双工)
1.UDP报文结构
UDP报文结构可以分为报头和数据载荷两部分,其中报头可以分为四个部分:16位源端口号;16位目的端口号;64K报文长度以及2个字节大小的校验和。
源端口和目的端口我们就不在此赘述了,这两个数据我们可以认为它们描述了从哪里来要到哪里去。
报文长度
UDP报文长度的大小只有两个字节,也就是64K(0 - 65535),这就导致了它无法一次传递太多的数据,那么如果说我们想要传递的长度超过了这个大小,那么我们该怎么办呢?
1.在应用层将数据分包传递,接收后将它们拼接成一个完整的数据(下策)
2.改用TCP
校验和
校验和是用来检验网络传输的数据是否正确的,校验和可以帮助我们辨别数据传输中发生的错误
举个例子,金庸写了不少武侠小说,我们如果刻意去记这些书名很可能有所遗漏。网络上有人将金庸老爷子的小说中的第一个字连起来编成了一句对联,假如这些书名就是我们要传递的数据,那么我们接收之后就可以将这些书名连起来看看是否符合这句对联。这句对联就可以被看做是校验和。
我们需要注意的是,数据和校验和对上不一定代表数据一定是正确的,但如果对不上,那就一定有问题!
目前,用来生成校验和的一些算法:crc、md5、sha1等,都是基于数据内容进行生成的
TCP协议
TCP的特点:有连接、可靠传输、面向字节流、全双工。
1.TCP报文结构
下图是TCP的具体结构,除了UDP兼有的源、目的端口号、校验和之外还包括16位的窗口大小、6个标志位、以及16位紧急指针等。细节我们会在后面详细说,此处无法展开。
2.TCP的可靠传输机制
1.确认应答
确认应答指的是发送方将数据发送出去之后,能够知道对方有没有收到。关键就是接收方收到消息之后会发送给发送方一个ACK应答报文表示已收到数据。
当然,此时我们的应答报文布面存在一个问题。举个例子:
我想请我女神吃麻辣烫,于是我给我女神发了一个消息,过了一会,我又给女神发女神,能不能做我女朋友?这个时候如果是按图1的顺序就不会引起歧义,但是因为某些因素的影响,导致了女神回复我的消息后发先至如图2,那么就会引起歧义。
图1
图2
为了应对这种情况,我们就给每一个应答都加上了一个确认序号,确认序号和数据序号一一对应,就不会有这种情况了。
如图所示,TCP对每个字节都进行了编号。在发送ACK上也会有所体现。
主机A给主机B发送了1000个字节的数据,当主机B收到之后就会返还一个ACK,ACK之中就会有一个确认序号1001,表示前1000个字节的数据已经收到了。
2.超时重传
刚刚我们所说的确认应答是传输正常的时候,当因为网络拥堵发生了丢包的时候,超时重传机制就要起到效果了。
这里我们可以将丢包分成两种情况:1、A发送给B的数据丢了,导致A没有收到ACK应答 2、A的数据B收到了但是B发送给A的ACK丢了
针对第一种情况,当A一段时间没有收到ACK就会出发超时重传,A就会将刚才没收到的ACK所对应的那一段数据重新发送。
这个时候问题来了,如果是第二种情况,B是收到了了数据了的,也就是说A重复发送了同一份数据。其实不用担心,B的接收缓冲器有去重的功能,当它会根据序号来判断接收的数据是否在缓冲器已经存在。如果发现是已经存在的数据就会直接丢弃。
当然,超时重传不一定会成功,但是连续重传失败的可能是非常低的。如果连续多次未成功,就认为网络有严重的情况,就会自动断开TCP连接。
3.连接管理
1)如何建立连接
当要建立连接时,客户端会先向服务器发送一个SYN同步报文段。先前我们在介绍TCP结构时,有TCP上有一部分为六位标志位如下图,当代表SYN的这一位为1的时候就代表当前报文为同步报文段。
建立连接的过程如下图:
我们吧这个过程成为客户端和服务器的三次握手(ACK和SYN可以合为一次,就好比我在淘宝的同一家店买了两件衣服,上架会将这两件衣服打包在一起发给我。当然也是可以分开的,但封装一次必然要比封装两次来的更为高效)
为什么要三次握手?它和可靠性有什么关系?
1、三次握手的过程其实我们可以视为投石问路,客户端和服务器可以检查双方的接收和发送能是否健全:
A - > SYN(检查自身发送能力)
B -> ACK/SYN (检查B的发送和接收能力,此时B已经确认了自身的接收和A的发送)
A - >ACK(A已确认B的发送和接收能力正常,表示自身接收也OK)
2、双方可以协商一些必要信息
2)如何断开连接
建立好连接之后,操作系统的内核中,就需要使用一定的数据结构来保存相关的信息。这些信息就是所谓的五元组:1、源端口 2、目的端口 3、源IP 4、目的IP 5、TCP协议。既然是保存了信息,那就需要占用一定的系统资源。如果连接断开了,那么对应的空间也可以释放了。
我们将TCP断开连接的过程抽象成了“四次挥手”。
需要注意的是:1、三次挥手一定是客户端主动发起的,而四次挥手(断开连接)的发起方并不固定。2、三次握手的中间两次可以合并,但四次挥手大概率无法合并(这也是为什么叫四次挥手不叫三次挥手)。
注:三次握手中的ACK和SYN都是内核发起的因此可以合并,而FIN是由应用层代码控制的(调用了socket.close()方法),与ACK的发起时机并不相同,因此大概率无法合并。
在状态转换的过程中,我们需要注意的是TIME_WAIT是哪一方主动发起FIN,谁就进入该状态,目的是给最后一次ACK提供重传的机会。
表面上看A发完最后一个ACK之后就可以释放资源了,但是这最后一个ACK是有丢包的可能的。假设ACK丢了,那么B在一段时间后因为没有接收到的ACK触发了超时重传,那么B就会重新发送FIN。而这个时候就体现出来了这个状态的重要性。
4.滑动窗口
在没有滑动窗口这一机制的情况下,A给B发送一条数据就需要等B返回一条ACK才能发送下一条数据,这样的话我们大部分的时间都浪费在了等待ACK上了,这就导致了数据发送的效率大大地降低了。为了提高数据传输的效率,滑动窗口这一机制就诞生了。
滑动窗口的本质就是一次发送一波数据,然后一份等待时间等待一波ACK。如果一波发送的数据量为N,此时N就称为窗口大小
滑动窗口中“滑动”的意思是:我们在发送一波数据之后,不需要等待这一波所有的ACK都到了之后再发送下一波数据。例如:我们当前正在等待1001,2001,3001,4001四组ACK,我们不需要等待4001到了之后再发送,只要1001到了就可以发送下一组数据(发送4001——5000),ACK的等待范围就从1001,2001,3001,4001更新成了2001,3001,4001,5001。
在上述发送接收的动作下,这个窗口就像是在不停地向前滑动,所以,这一机制被称为滑动窗口,窗口越大,传输的速度也就越快。
那么,假如说在传输过程中ACK丢了或是传输的某一条数据丢了怎么办?
1)ACK丢了
在发送4001之前,我们没有收到1001的ACK,但是我们收到了2001的ACK。我们需要注意的是ACK的含义是表示2001之前的数据都已经收到了,因此有没有收到1001的ACK是无足轻重的。
2)数据丢了
由于1001——2000的数据丢了,这时主机B就会通过ACK反复向主机A索要1001——2000的数据,当A发现之后就会重传1001——2000的数据。
5.流量控制
流量控制是滑动窗口的延伸,目的是为了保证可靠性。
在滑动窗口中,我们不仅要考虑数据传输的效率,我们还需要考虑数据传输的可靠性。因此衍生出了流量控制这一机制。
如果窗口过大,发送的速度太快导致接收方来不及接收和处理导致数据丢失,这样反而会适得其反。因此我们通过流量控制来控制窗口的大小保证传输的可靠性。
A将数据发送给B,B会现将这些数据放在接收缓冲区,然后再读取调用。随着数据的发送,接收缓冲区剩余空间逐渐变小。如果剩余空间比较大,那么就会认为B的处理能力较强,就会调整窗口大小,让窗口大一些;反之就缩小窗口。
这个过程就像是在一头给水池加水,另一头给水池放水。当水池水的增加速度太快了,可能要溢出来了,那么我们就把加水的那一头减少一些。如果是放水的速度快,那我们就把水龙头开大一些。ACK的报文内有16位窗口大小,并以此来衡量当前接收方剩余空间的大小。当发送方收到ACK之后就会根据这个数据来调整窗口的大小了。
当剩余空间为0时,是否意味着A不再发送数据了呢?
当剩余空间为0,A会定期给B发送一个探测报文,这时B就会返回一个ACK,A则又会根据这一ACK中的数据来发送数据。
6.拥塞控制
这一机制也是滑动窗口的延伸,也是为了保证数据发送的可靠性。
A跟B之间并不是直接相连的,也就是说A向B发送的数据需要经过一系列的中间链路,所以数据发送的稳定性不仅取决于B的接收能力,还受到中间链路的因素以及网络因素的影响。
A一开始是用较小的窗口发送数据的,如果数据非常流畅地就到达了,那么A就会逐渐加大窗口大小直到出现了丢包现象。
下图描述了拥塞控制下的窗口大小的变化规律。当然,最终窗口大小是取流量控制和拥塞控制之下的最小值的。
初始情况下,窗口的大小是指数式增长的,当窗口大小即将触碰到极限之后(上次丢包的窗口大小)就会转变为线性增长。随着窗口增大,一旦产生丢包,发送方就会减小窗口大小(初始窗口大小),然后重复上述过程。
7.延时应答
延时应答是流量控制的延伸,是在保证可靠性的同时尽可能的提高传输速度。
沿用刚才水池的例子,我们在收到水之后并不立即返回剩余空间的大小,而是延时一段时间之后再报告,也就是说我们并不会立刻返回ACK而是等待了一段时间。这么做的好处就是在延时的这一段时间之内水池也是一直在放水的(B一直在读取接收缓冲区的数据),那么我们返回ACK的时候就能告知有更大的剩余空间了,使窗口的大小变得更大,数据发送速度也就更快了。
8.捎带应答
捎带应答是延时应答的延伸。如果延时应答导致ACK的返回时机和应用代码中的响应时机重合了,就可以将ACK和响应合二为一,提高传输效率。
9.粘包问题
A将数据发送到B之后,B将各个包内的数据取出分用(将TCP数据进行了解析,取出了其中应用层数据放到接收缓冲器以备应用程序取用)。当B的应用程序通过read方法从接收缓冲区取用数据时,因为TCP是面向字节流的,取的时候是直接取若干个字节,这时问题来了:从哪取到哪才是一个完整的应用层数据报呢?
关键就是要在应用层协议这里,加入包和包之间的边界,例如:约定每个包以(;)结尾
10.TCP异常处理
1)进程终止
TCP连接是通过socket连接的,socket本质上是进程打开的一个文件。进程的PCB里面有一个文件描述表,每当打开一个文件就在文件描述表中增加一项,每关闭一个文件就在里面删除对应的那一项。
如果直接杀死进程,也就意味着PCB没了,那么文件描述表也没了。这个过程和手动调用socket.close()是一样的,都会触发四次挥手操作。
2)机器关机
操作系统会杀死所有进程然后关机,同样也会触发四次挥手。
3)断电/断网
假如A正在不停给B发送数据,这时B突然断电了或者断网了,那么A也就无法收到B的ACK了,于是一段时间后A会触发超时重传的机制,将数据重新发送,但还是无法收到ACK。重复多次后A就会意识到B的网络或是因为其它原因无法做出响应,就会主动断开连接。
如果说是A突然断电了,那么B因为长时间没收到A的数据,就会定时发送探测报文。多次未获得回应之后就会放弃连接。
小结
TCPVS UDP
1)什么时候使用TCP?
对可靠性有一定要求的时候(日常开发中大多数情况其实都是基于TCP)
2)什么时候使用UDP?
对可靠性要求不高,对效率要求更高的时候
3)如何提高UDP的可靠性?
抄作业,TCP是如何保证可靠性的,那么也同样在应用层实现这些机制
IP协议
1. ip报头结构
1. 4位版本号
报头中的4位版本号是用来声明当前协议的版本的,取值只有4和6,我们目前主要讨论的是IPv4
2. 4位首部长度
和TCP类似,这4位首部长度也是可变的,带有选项。其取值范围是0-15,单位为4字节,如果取值为1111,实际代表首部长度为60字节
3. TOS8位服务类型
此处的TOS用于切换形态,只有4位是真实有效的,且一次只能选择一个。
4. 16位总长度
表示单个IP数据报最大长度不能超过64K,如果需要传输的长度超过了64K则需要分包,IP协议自身是实现了分包和组包这样的操作的。
5. 16位标识、3位标志、13位片偏移
如果整个IP数据报太长了,IP协议就会把这个大包拆分成多个小包,保证每个包的长度不会超过64K。
那么,如何区分这多个IP包是从同一个包拆分而来的呢?
从同一个包中拆出来的16位标识是相同的,且越靠前的包片偏移越小。3位标志位只有一位是有效的,如果标志位为1则表示是最后一个包了,反之标志位为0.
6. 8位生存时间TTL
生存时间表示了该数据报在网络上还能存在多久。这里的存在时间不是用秒或者其他时间单位来衡量的,而是表示转发次数。IP数据报被发送的时候有一个初始TTL值,每当数据报经过一个路由器TTL就会-1,如果TTL减到0,收到这个包的路由器就会把它丢弃。
7. 8位协议
表示传输层使用的是哪种协议
8. 16位校验和
用来确定传输的数据是否有误
9. 32位源IP地址、 32位目的IP地址
对于IPv4,IP地址本质是32位的整数,通常会用点分十进制这样的方式来表示这个IP地址。例如:192.168.0.5。每一位的取值是0-255。
2. 地址管理
上面我们谈到了IP地址是一个点分十进制构成的数据,我们将IP地址分成了两个部分:网络号(描述当前网段信息)和主机号(区分了局域网内的主机)。
我们要求,同一个局域网内,主机之间的网络号是相同的,但主机号不能相同。不同的局域网的网络号也是不一样的。
1. 如果IP的主机号全为0,该IP就表示网络号。
2. 如果IP的主机号全为1(255),该IP就表示广播地址,往这个地址上发送的消息整个局域网都能收到。
3.IP地址为127开头的,则为环回IP,表示主机自己
4. IP地址是10开头,192.168开头,172.16-172.31开头,表示该地址是一个局域网内部的IP,除此之外都是外网IP。
外网IP一定是唯一的,每一个外网IP都会对应到一个设备。
如今,随着智能手机和网络的普及,IPv4所能分配的IP数目已经渐渐不够用了,那么如何去解决这个问题呢?
1. 动态分配IP地址
使每一个设备只有上网的时候才会被分配IP,不联网就没有IP地址
2. NAT机制
让多个设备共用一个IP地址
将网络分成了局域网和广域网,要求外网IP为唯一IP地址,而局域网内的设备可以共用一个外网IP。而同一个局域网内的不同设备则根据它们不同的端口号进行区分。
以上两个方法虽然可以缓解IP不够的局面,但是随着社会的发展终究只是治标不治本,而IPv6的出现才彻底解决了这个问题。
IPv6在报头中使用了16个字节来表示IP地址,它能表示的IP数远远大于IPv4所能表示的IP数,毫不夸张地说,它可以给地球上的每一粒沙子都分配一个IP。但是现在很大的问题是IPv4和IPv6是不兼容的,因此它的普及还在路上。
3.路由选择
路由选择,也就是路径规划(两个设备之间,要找出一条通道,可以完成传输过程)
IP数据包中的目的地址就表示了这个包要发送到哪里去。如果这个地址当前的路由器认识,就会直接告诉你怎么走,如果它不认识就会告诉你一个大致的方向,这个包走到下一个路由器则重合上一个的操作,直到遇到了认识目的地址的路由器。