[关闭]
@dungan 2021-04-30T09:12:31.000000Z 字数 6904 阅读 150

网络

网络协议-传输层


传输层协议


1. 传输层的作用及端口号

网络层根据 IP 地址将数据包转发给特定的主机,而传输层根据数据包中的端口,将数据转发给主机上监听对应端口的应用程序。

端口号用来识别同一台计算机中进行通信的不同应用程序,因此,它也被称为程序地址。


2. UDP

UDP(User Datagram Protocol) 全称 用户数据报协议


2.1 UDP 特点

UDP 提供面向无连接的通信服务,是不具有可靠性的数据报协议。换句话说,它将部分控制转移给应用程序去处理,自己却只提供作为传输层协议的最基本功能。

UDP 虽然可以确保发送消息的大小,但不能保证消息一定会到达,传输途中如果出现丢包,UDP 不负责进行重发。因此,应用有时候会根据自己的需要进行重发处理。甚至当包的到达顺序错乱时,UDP 也没有纠正功能。

此外,UDP 也不提供复杂的控制机制,即使出现网络拥堵,UDP 也无法进行流量控制等避免避免网络拥塞的行为

UDP 适用场景:


2.2 UDP 首部

UDP 报文首部由源端口号、目标端口号、包长和校验和组成:

udp


3. TCP

TCP(Transmission Control Protocol) 全称 传输控制协议


3.1 TCP 特点

TCP 提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好两端之间的准备工作。


3.2 TCP 首部

传输层在应用程序数据前面增加了 TCP 头;网络层在 TCP 数据包前增加了 IP 头;而网络接口层,又在 IP 数据包前后分别增加了帧头和帧尾。

以太网一帧的 MTU 为 1500 字节,这个长度指的是链路层的数据部分(图中帧头和帧尾之间),这部分数据中包含了IP首部,UDP/TCP首部,以及真实的数据部分,这个1500字节其实就是网络层IP数据报的长度限制,因为IP数据报的首部为20字节,所以IP数据报的数据部分长度最大为1480字节;

而IP数据报的数据部分又会包含UDP和TCP报文段,但其中UDP报文首部8字节,所以UDP数据报的数据区最大长度为1472字节,这个1472字节就是我们可以使用的字节数。TCP首部为20字节,所以最大长度为1460字节。


3.3 TCP 三次握手与四次挥手

三次握手

所谓三次握手是指建立一个 TCP连接时需要客户端和服务器端总共发送三个包以确认连接的建立。在socket编程中,这一过程由客户端执行 connect 来触发。

三次握手的目的是为了确保对连接双方来说,链路都是通的。三次握手除了双方建立连接外,主要还是为了沟通一件事情,就是 TCP 包序列号的问题, 。 每个连接都要有不同的序列号,这个序列号的起始序号是随着时间变化的。

TCP 三次握手的流程如下:

handsharke.png

第一次握手: 一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。然后客户端随机产生一个序列号 seq=x,并将该数据包发送给服务器端,客户端进入 SYN_SENT 状态,等待服务器端确认。

第二次握手: 服务端收到发起的连接,随机产生一个序列号 seq=y,并且 ACK 客户端的 SYN,并将该数据包发送给客户端以确认连接请求,服务器端进入 SYN_RCVD 状态。

第三次握手: 客户端收到服务端发送的 SYN 和 ACK 之后,发送 ACK 的 ACK,这样连接就建立成功了, 客户端和服务器端进入 ESTABLISHED 状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。

四次挥手

四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。

由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

TCP 四次挥手的流程如下:

2020-08-04_162338.png

中断连接可以由任一端发起,可以是客户端,也可以是服务器端。

第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据。

第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文。

第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了。服务器端进入LAST_ACK状态。

第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次握手。

实际中还会出现同时发起主动关闭的情况:

ccc.png


3.4 顺序传输

因为网路是复杂的,后发的包完全有可能先于先发的包到达接收端,这样就会造成包时乱序的。因此发出去的包我们需要打上序列号。

当接收端收到数据后,查询 TCP 首部中的序列号和数据长度,将自己下一步应该接收的序列号作为确认应答(ack)发送回去。

2020-08-04_133124.png

可以看到 ack 的号是收到的最后一个数据包的序号 seq + 1,这告诉对端 seq 之前的包都受到了,下次的包从 seq + 1 开始发送,这种应答方式称作累计应答


3.5 丢包重传(可靠传输)

如果在特定的时间内,发送到目标主机的 TCP 包没有得到相应的确认应答,则会进行重发, 这个等待的时间就是重发超时时间

timeout.png

超时时间的计算逻辑如下:


当然,数据也不会被无限、反复重发,达到一定重发次数后,如果仍没有确认应答返回,就会判断为网络或对端主机发生了异常,强制关闭连接,并且通知应用通信异常强行终止。


3.6 窗口控制(传输效率)

TCP 数据包的单位是 MSS(MSS,Maximum Segment Size) ,全称最大报文段大小,TCP 在传输大量数据时,是以 MSS 的大小将数据分割发送。也就是说,如果要发送的数据很大,在 TCP 层是需要按照 MSS 来切割成一个个的 TCP 报文段,重发时也是以 MSS 为单位。

如果数据比较大,发送端每发送一个数据包,都需要得到接收端的确认应答以后,才可以发送下一个数据包。这样一来,就会在等待确认应答包环节浪费时间。

每次只传输一个包有一个显著的缺点,就是包往返的时间越长通信性能就越低。为了避免这种情况,TCP引入了窗口概念,即一次发出去多个包。这样发送端主机,在发送了一个包以后不必要一直等待确认应答,而是继续发送下一个包。

窗口是有大小的,窗口大小指的是不需要等待确认应答包而可以继续发送数据包的最大数量。它由两部分构成:

例如窗口大小为 3,已经发出了两个数据包但未收到应答,如果继续接着发新的包时,只能发一个,因为窗口的大小为 3,如图:

可以看到上图的确认应答是通过 累计应答的方式告知发送端下一个数据包的序列号是多少。

这样窗口就通过滑动的方式,向后移动,确保下一次发送仍然可以发送窗口大小的数据包。这样的发送方式被称为滑动窗口机制

每 1000 个字节表示一个数据包。发送端同时发送了 3 个数据包(2001-5000),接收端响应的确认应答包为“下一个发送4001”,表示接收端成功响应了前两个数据包,没有响应最后一个数据包。最后一个数据包要保留在窗口中重传

窗口控制中的重传

在使用窗口控制中,出现丢包一般分为两种情况.

第一种:前面发送的数据包没有收到对应的确认应答。但最后面发送的包收到了确认应答,表示前面的数据包已经成功被接收端接收了,发送端不需要重新发送前面的数据包了。

第二种:发送端发送的部分数据包没有达到接收端,这种情况下,发送端会一直收到相同的确认应答,是需要进行重发的。

bbb

最终接收端收到重发数据包以后,查看这次是自己应该接收的数据包 2001-3000,计算窗口大小后并返回确认应答包,告诉发送端,下一个该接收 8001 的数据包了。

这种机制比之前提到的丢包重传更加高效,因此也被称为高速重发控制。


3.7 流量控制(窗口大小)

在使用滑动窗口机制进行数据传输时,发送方具体一个窗口的传输几个包,这个取决于接收端的接收能力,每次接收端 ACK 发送端时,顺带会传过来一个值,叫窗口大小(win)。这样发送端就能控制自己的发送速率,从而实现流量控制,同理,每次发送端发包时也带上自己的窗口大小,表示自己的接收能力。

TCP 首部的窗口大小就来用来告诉对方自己的接收能力的。

win-size


3.8 拥塞控制(网络吞吐)

计算机网络都处在一个共享的环境,有可能因为其它主机之间的通信导致网络拥堵,在网络拥堵时,如果发送一个较大量的数据,极有可能导致整个网络的瘫痪。

因此,为了调节发送端所要发送数据的量,定义了一个叫做拥塞窗口的概念:

一开始的时候,将这个拥塞窗口的大小设置为1个数据段(1MSS)发送数据,之后每收到一个确认应答(ACK),就将拥塞窗口的值加1,在发送数据包时,将拥塞窗口的大小与接收端主机通知的窗口大小做比较,然后按照它们当中较小的那个值,发送比它还要小的数据量。

此外,为了防止超时重发时,随着包往返导致的拥塞窗口快速增长(指数增长),引入了慢启动阀值的概念:

它是出现在你传输到一定速度的时,认为应慢点去传的时,原来的指数传输方式,增长速度方式变成现行的速度增长,来尽量的规避和避免网络的拥塞。

所以说,滑动窗口一开始不会给太大:

TCP 连接会随着时间进行调节滑动窗口,起初会控制发送端的发送速度,如果数据成功传输,会随着时间的推移提高传输的速度。这种被称为 TCP 慢启动

例如:

它发出第一轮数据,实际上只发两个数据包。等待这两个数据包的完全确认,如这两个数据包完全收到ACK确认数据之后,会认为第一轮数据你都已经收到了,这时它会把发包的数量调整成4个。如果第二个也收到了,就调成8个,16个,32个这样的一个增长过程,就是慢启动的过程。那这个过程实际上就是一个最开始在TCP刚开始建立的时候的一个最初始的过程。

总的来说:

3.9 TCP 的 KeepAlive

TCP 连接建立之后,如果其上的应用程序一直不发送数据,或者隔很长时间才发送一次数据,当链接很久没有数据报文传输时如何去确定对方还在线,到底是掉线了还是确实没有数据传输,链接还需不需要保持,这种情况在TCP协议设计中是需要考虑到的。

TCP 协议通过一种巧妙的方式去解决这个问题,当超过一段时间之后,TCP自动发送一个数据为空的报文给对方,如果对方回应了这个报文,说明对方还在线,链接可以继续保持,如果对方没有报文返回,并且重试了多次之后则认为链接丢失,没有必要保持链接。

在Linux中我们可以通过修改 /etc/sysctl.conf 影响到KeepAlive的行为:

  1. # 距离上次传送数据多少时间未收到新报文判断为开始检测,默认7200s(2小时)
  2. net.ipv4.tcp_keepalive_time=7200
  3. # 检测开始每隔多少时间发送心跳包,默认75s
  4. net.ipv4.tcp_keepalive_intvl=75
  5. # 发送几次心跳包对方未响应则close连接,默认9次
  6. net.ipv4.tcp_keepalive_probes=9

TCP KeepAlive 的不足和局限性
keepalive只能检测连接是否存活,不能检测应用是否真的可用。例如,应用程序出现了 500 错误,无法在连接上进行任何读写操作,但是操作系统仍然可以响应网络层keepalive包。

TCP keepalive 机制依赖于操作系统的实现,灵活性不够,默认关闭,且默认的 keepalive 心跳时间是 2 小时, 时间较长。

和 Http Keep-Alive 的区别

参考

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注