目录
客户端与服务器进行交互的时候,客户端需要发送或是接收数据。一方将数据发送给另一方时,需要将这些数据通过某种特定的方式进行打包,而接收的那一方同样也需要按照特定的方式去拆包,这样才能拿到他们想要的数据,而这种特定的拆包装包的方式,就叫作协议。
当前较为常见的两种应用层协议为UDP和TCP,下面我们就这两种常见的协议,谈谈他们背后的一些原理结构。
UDP协议
UDP的特点:无连接、不可靠、面向数据报、全双工。
1.UDP报文结构
源端口和目的端口我们就不在此赘述了,这两个数据我们可以认为它们描述了从哪里来要到哪里去。
报文长度
校验和
举个例子,金庸写了不少武侠小说,我们如果刻意去记这些书名很可能有所遗漏。网络上有人将金庸老爷子的小说中的第一个字连起来编成了一句对联,假如这些书名就是我们要传递的数据,那么我们接收之后就可以将这些书名连起来看看是否符合这句对联。这句对联就可以被看做是校验和。
我们需要注意的是,数据和校验和对上不一定代表数据一定是正确的,但如果对不上,那就一定有问题!
TCP协议
TCP的特点:有连接、可靠传输、面向字节流、全双工。
1.TCP报文结构
下图是TCP的具体结构,除了UDP兼有的源、目的端口号、校验和之外还包括16位的窗口大小、6个标志位、以及16位紧急指针等。细节我们会在后面详细说,此处无法展开。
2.TCP的可靠传输机制
1.确认应答
当然,此时我们的应答报文布面存在一个问题。举个例子:
我想请我女神吃麻辣烫,于是我给我女神发了一个消息,过了一会,我又给女神发女神,能不能做我女朋友?这个时候如果是按图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可以合为一次,就好比我在淘宝的同一家店买了两件衣服,上架会将这两件衣服打包在一起发给我。当然也是可以分开的,但封装一次必然要比封装两次来的更为高效)
为什么要三次握手?它和可靠性有什么关系?
2)如何断开连接
建立好连接之后,操作系统的内核中,就需要使用一定的数据结构来保存相关的信息。这些信息就是所谓的五元组:1、源端口 2、目的端口 3、源IP 4、目的IP 5、TCP协议。既然是保存了信息,那就需要占用一定的系统资源。如果连接断开了,那么对应的空间也可以释放了。
我们将TCP断开连接的过程抽象成了“四次挥手”。
需要注意的是:1、三次挥手一定是客户端主动发起的,而四次挥手(断开连接)的发起方并不固定。2、三次握手的中间两次可以合并,但四次挥手大概率无法合并(这也是为什么叫四次挥手不叫三次挥手)。
注:三次握手中的ACK和SYN都是内核发起的因此可以合并,而FIN是由应用层代码控制的(调用了socket.close()方法),与ACK的发起时机并不相同,因此大概率无法合并。
在状态转换的过程中,我们需要注意的是TIME_WAIT是哪一方主动发起FIN,谁就进入该状态,目的是给最后一次ACK提供重传的机会。
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丢了
2)数据丢了
由于1001——2000的数据丢了,这时主机B就会通过ACK反复向主机A索要1001——2000的数据,当A发现之后就会重传1001——2000的数据。
5.流量控制
流量控制是滑动窗口的延伸,目的是为了保证可靠性。
如果窗口过大,发送的速度太快导致接收方来不及接收和处理导致数据丢失,这样反而会适得其反。因此我们通过流量控制来控制窗口的大小保证传输的可靠性。
这个过程就像是在一头给水池加水,另一头给水池放水。当水池水的增加速度太快了,可能要溢出来了,那么我们就把加水的那一头减少一些。如果是放水的速度快,那我们就把水龙头开大一些。ACK的报文内有16位窗口大小,并以此来衡量当前接收方剩余空间的大小。当发送方收到ACK之后就会根据这个数据来调整窗口的大小了。
当剩余空间为0时,是否意味着A不再发送数据了呢?
6.拥塞控制
这一机制也是滑动窗口的延伸,也是为了保证数据发送的可靠性。
A跟B之间并不是直接相连的,也就是说A向B发送的数据需要经过一系列的中间链路,所以数据发送的稳定性不仅取决于B的接收能力,还受到中间链路的因素以及网络因素的影响。
下图描述了拥塞控制下的窗口大小的变化规律。 当然,最终窗口大小是取流量控制和拥塞控制之下的最小值的。
初始情况下,窗口的大小是指数式增长的,当窗口大小即将触碰到极限之后(上次丢包的窗口大小)就会转变为线性增长。随着窗口增大,一旦产生丢包,发送方就会减小窗口大小(初始窗口大小),然后重复上述过程。
7.延时应答
延时应答是流量控制的延伸,是在保证可靠性的同时尽可能的提高传输速度。
8.捎带应答
9.粘包问题
A将数据发送到B之后,B将各个包内的数据取出分用(将TCP数据进行了解析,取出了其中应用层数据放到接收缓冲器以备应用程序取用)。当B的应用程序通过read方法从接收缓冲区取用数据时,因为TCP是面向字节流的,取的时候是直接取若干个字节,这时问题来了:从哪取到哪才是一个完整的应用层数据报呢?
10.TCP异常处理
1)进程终止
2)机器关机
3)断电/断网
小结
TCP VS UDP
1)什么时候使用TCP?
2)什么时候使用UDP?
3)如何提高UDP的可靠性?
IP协议
1. ip报头结构
1. 4位版本号
2. 4位首部长度
3. TOS8位服务类型
4. 16位总长度
5. 16位标识、3位标志、13位片偏移
那么,如何区分这多个IP包是从同一个包拆分而来的呢?
6. 8位生存时间TTL
7. 8位协议
8. 16位校验和
9. 32位源IP地址、 32位目的IP地址
2. 地址管理
上面我们谈到了IP地址是一个点分十进制构成的数据,我们将IP地址分成了两个部分:网络号(描述当前网段信息)和主机号(区分了局域网内的主机)。
我们要求,同一个局域网内,主机之间的网络号是相同的,但主机号不能相同。不同的局域网的网络号也是不一样的。
如今,随着智能手机和网络的普及,IPv4所能分配的IP数目已经渐渐不够用了,那么如何去解决这个问题呢?
1. 动态分配IP地址
2. NAT机制
以上两个方法虽然可以缓解IP不够的局面,但是随着社会的发展终究只是治标不治本,而IPv6的出现才彻底解决了这个问题。
3. 路由选择
路由选择,也就是路径规划(两个设备之间,要找出一条通道,可以完成传输过程)