目录
一、传输层
(一)再次理解传输层
传输层是计算机网络中的一层,位于网络层和应用层之间。它主要负责在网络中的两个端系统之间提供可靠的、端到端的数据传输服务。简单理解,传输层就是负责在源主机和目标主机之间提供端到端的数据传输服务。
传输层的两个主要协议有传输控制协议(TCP)和用户数据报协议(UDP)两种。
- 传输控制协议(TCP):
TCP是一种面向连接的协议,提供可靠的、有序的数据传输。它确保数据按照发送的顺序被接收,并提供差错检测和重传机制以确保数据的可靠性。
一个常见的TCP的示例用途是网页浏览器通过HTTP协议进行网页请求。当您在浏览器中访问一个网页时,浏览器会与服务器建立一个TCP连接,然后使用该连接传输HTML、图片和其他资源数据,确保这些数据按照正确的顺序到达,以显示完整的网页。
- 用户数据报协议(UDP):
UDP是一种无连接的协议,不提供可靠性保证,但传输效率较高。它将数据分割成数据报,每个数据报都是独立的,可以独立传输,因此没有复杂的连接建立和维护过程。
一个常见的UDP的示例用途是音视频流传输。在语音通话或视频聊天中,为了实时传输数据,能够立即播放声音或图像,UDP被广泛使用。尽管UDP可能会丢失一些数据包,但对于实时通信来说,及时性比可靠性更重要。
上述示例只是传输层协议在网络中的部分应用示例,传输层还有其他更广泛的用途,如文件传输、电子邮件传输等。所以无论使用TCP还是UDP,传输层的目标是确保数据在源和目标之间有效且可靠地传输,同时提供所需的服务质量。
(二)再次理解端口号
端口号(Port)标识了一个主机上进行通信的不同的应用程序。在TCP/IP协议中,用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信(可以通过netstat -n查看)。
我们来详细思考一下其中的细节:IP地址标识了全网了唯一一台主机。Port端口号标识了一台机器上的一个应用程序,也可以理解为Port端口号标识了一台机器上的唯一一个进程(一个端口号只能绑定一个进程)。IP地址和Port端口号不就是标识的全网了唯一进程吗!!!网络通信本质上不就是进程间通信吗?那么现在再回去看"源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信也就不难理解了。
1. 端口号范围划分
端口号范围划分是为了网络通信中识别和区分不同的应用程序或服务而定义的。根据传输层协议的不同,端口号的范围也有所区别。
在TCP和UDP协议中,端口号范围被划分为以下几类:
- 知名端口(Well Known Ports):从0到1023的端口号被预留给一些常见的服务或应用程序使用,如HTTP(端口号80)、FTP(端口号21)等。这些端口号在整个互联网上是公认的,并且只能由特权用户(例如管理员)打开。
- 注册端口(Registered Ports):从1024到49151的端口号被分配给用户注册的应用程序和服务。这些端口号可以被普通用户打开,但应该避免与已知的知名端口冲突。
- 动态/私有端口(Dynamic/Private Ports):从49152到65535的端口号是动态或私有端口,用于临时的、非注册的应用程序和服务。这些端口号可以被任何用户的应用程序使用,通常用于客户端与服务器之间的临时通信。
需要注意的是,端口号的范围划分并不是严格规定的,而是一种通用的约定。在实际应用中,为了避免冲突和提高安全性,建议遵循这种范围划分并选择未使用的端口号进行应用程序的开发和部署。
2. 认识知名端口号
当提到知名端口号时,我们指的是一些常用的端口号,这些端口号在整个互联网上是公认的,并且被用来提供特定的网络服务。以下是一些常见的知名端口号及其对应的服务:
- 20/TCP, 21/TCP: FTP(文件传输协议)数据传输和控制
- 22/TCP: SSH(安全外壳协议),用于安全远程登录和文件传输
- 23/TCP: Telnet(远程终端协议),用于远程管理、终端访问
- 25/TCP: SMTP(简单邮件传输协议),用于电子邮件的发送
- 53/TCP,UDP: DNS(域名系统),用于将域名解析为IP地址
- 67/UDP, 68/UDP: DHCP(动态主机配置协议),用于自动分配IP地址、网关等网络配置信息
- 80/TCP: HTTP(超文本传输协议),用于Web服务器的通信
- 110/TCP: POP3(邮局协议),用于接收电子邮件
- 143/TCP: IMAP(Internet邮件访问协议),用于接收和管理电子邮件
- 443/TCP: HTTPS(超文本传输安全协议),用于通过安全的加密连接进行Web通信
- 3389/TCP: RDP(远程桌面协议),用于远程访问和控制计算机桌面
这些端口号只是一部分知名端口,实际上还有许多其他的端口号用于提供各种特定的网络服务。
我们可通过cat /etc/services可查看知名端口号。
(三)网络常用指令netstat 与 pidof
netstat(Network Statistics)是一个用于显示网络连接、路由表和网络接口等相关信息的命令。它可以提供以下方面的详细信息:
- 网络连接状态:显示当前计算机与其他设备之间的所有活动网络连接,包括TCP连接、UDP连接、监听端口等。
- 路由表:显示系统的 IP 路由表,其中包含关于数据包如何被转发的信息。
- 网络接口:显示计算机上的所有网络接口,例如以太网、Wi-Fi、回环接口等。
- 进程标识符(PID):可以通过"-p"选项将每个连接的相关进程ID(PID)显示出来,从而了解哪个进程正在使用某个特定的网络连接。
具体选项如下:
netstat -a // 显示所有活动的网络连接和监听端口
netstat -n // 以数字格式显示所有连接
netstat -p // 显示与连接关联的进程ID
netstat -t // 显示TCP连接
netstat -u // 显示UDP连接
netstat -l // 显示Listen(监听)的服务
pidof命令用于根据进程名来查找运行中的进程的PID(进程标识符)。 常用用法:pidof+进程名,来查询进程的PID。
了解完一些基本概念后,接下来我们来看一下UDP协议与TCP协议详解。
二、UDP协议
UDP(User Datagram Protocol,用户数据报协议)是一个简单的面向数据报的传输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
(一)UDP协议的报文
我们直接先来学习一下UDP的报文,如下图:
根据上图,我们能很好的总结出UDP报文的结构如下:
UDP报文头部(固定8字节):
- 源端口(源端口号,2字节):表示发送端的端口号。
- 目的端口(目的端口号,2字节):表示接收端的端口号。
- UDP长度(2字节):表示UDP报文的长度,包括头部和数据部分。
- UDP校验和(2字节):用于验证UDP报文的完整性,检测是否出现传输错误。
数据部分: UDP报文的数据部分可以包含任意大小的数据(可以没有),没有固定的格式要求。它是应用层传递给UDP协议的数据,在传输过程中,UDP会将整个数据部分封装为UDP报文并发送。
任何协议都要解决的两个问题:1、如何分离和封装;2、如何向上交付。
怎么分离和封装呢?首先UDP报头是有固定长度(通常为8字节)的,这就很好的解决了。怎么理解UDP报文的报头呢?其实UDP报头在底层就是用一个结构体来封装的,再加上位段就可以很好的实现对UDP报头的封装。代码如下:
struct udp_hdr {
uint32_t source:16; // 源端口号
uint32_t dest:16; // 目标端口号
uint32_t len:16; // UDP报文长度(包括报头和数据部分)
uint32_t check:16; // 校验和
};
UDP的报头加上传输数据不就是UDP报文吗!分离时报头中有报文长度字段(16位UDP长度,转化成十进制),直接减去报头的8个字节,即 XXXXXXXXXXXXXXXX(转化成十进制) - 8 = 剩下的就是所传输的数据(字节)。
向上交付也不难,有目的端口和目的IP地址,就可以很好的找到目标进程进而传输数据。
(二)UDP的特点
UDP(User Datagram Protocol,用户数据报协议)是一种在网络通信中常用的传输层协议。与TCP(Transmission Control Protocol,传输控制协议)不同,UDP是一种无连接协议,它提供了一种简单的、不可靠的数据传输服务。
以下是UDP的特点详解:
- 无连接性:UDP是一种无连接协议,发送方不需要在发送数据之前与接收方建立连接。每个UDP数据报都是独立的,有自己的头部信息,可以独立地进行传输。这种无连接性使得UDP具有较低的传输延迟和较小的开销。
- 不可靠性:UDP不提供数据包的可靠交付。一旦发送数据,UDP就不会对数据包进行确认或重新发送丢失的数据包。这意味着UDP在数据传输过程中可能会发生数据包丢失、重复、乱序等情况。因此,如果应用对数据传输的可靠性要求较高,则不适合使用UDP。
- 高效性:由于UDP无需建立连接和维护状态信息,因此它的开销较小,数据报的头部相对较小。同时,UDP也不会进行拥塞控制,因此在网络负载较大时,UDP可以更高效地传输数据。
- 面向数据报文:UDP把应用层交给传输层的报文信息进行封装,形成UDP数据报进行传输。这意味着UDP在传输过程中不会对数据进行拆分和合并,保持了数据的完整性。
- 大小受限:UDP协议首部中有一个16位的最大长度,表示整个数据报(UDP首部+UDP数据)的最大长度, 即数据报最大大小为2^16byte = 64KB。也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部)。
并且,UDP存在接收缓冲区,但不存在发送缓冲区。UDP没有发送缓冲区,所以在调用send to时会直接将数据交给内核,由内核将数据传给网络层协议进行后续的传输动作。UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报文的顺序和发送UDP报的顺序一致,如果缓冲区满了再到达的UDP数据报就会被丢弃。
- 为什么UDP不需要发送缓冲区? 因为UDP不保证可靠性,它没有重传机制,当报文丢失时,UDP不需要重新发送,而TCP不同,他必须具备发送缓冲区,当报文丢失时,TCP必须保证重新发送,用户不会管,所以必须要具备发送缓冲区。
面向数据报的简单理解:应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并。用UDP传输100个字节的数据,如果发送端调用一次sendto, 发送100个字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节。 而不能循环调用10次recvfrom, 每次接收10个字节。
需要注意的是,由于UDP的不可靠性和无连接性,它在某些情况下可能会导致数据丢失或乱序。这里需要注意的是,UDP的不可靠并非是一个缺点,也并非是贬义词。只是在相比之下,与UDP相对的是TCP(Transmission Control Protocol),它是一种可靠的协议,提供了数据传输的可靠性和顺序性。但UDP协议的简洁和低延迟特性使其在某些特定的应用场景中还是具有一定的优势。
三、TCP协议
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP在传输数据之前,会先与目标主机建立连接,然后通过可靠的数据传输机制进行数据的传递。如果发送方没有收到对方的确认,它会自动重传数据。
(一)TCP协议的报文
我们直接先来学习一下TCP的报文,如下图:
TCP报文由以下几个字段组成:
-
源端口号和目标端口号:每个TCP连接都有一个源端口号和一个目标端口号。源端口号标识发送端的应用程序,目标端口号标识接收端的应用程序。这样可以通过端口号来区分不同的应用程序和建立多个并发的TCP连接。
-
序号(Sequence Number):TCP协议使用序列号来标识发送的数据流中每个字节的位置。TCP将要传输的每个字节都进行了编号,序号是本报文段发送的数据组的第一个字节的编号,在建立连接时,双方会约定一个初始序号,之后在每次发送数据时递增序列号,增加的量等于该段中数据的字节数,接收方可以根据序列号重新按顺序组装数据。比如:一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为401。
-
确认序号(Acknowledgment Number):确认序号用于对接收到的数据进行确认。当一个主机收到另一个主机发送的数据后,会向对方发送一个确认报文,确认号为收到TCP报文段的序号值加1(表示当前的应答报文针对的是哪个消息进行的确认应答)。通过确认号,发送方可以知道哪些数据已经成功送达。
-
4位首部长度(数据偏移 Data Offset):该字段表示TCP报文的首部长度,有多少个32位bit(有多少个4字节),以4字节为单位,所以TCP头部最大长度是15 * 4 = 60,即区间为[0000, 1111] == [0, 60]。其中前20个字节是固定的(TCP报头最短长度是20,为标准报头)剩下的就是选项(可有可无)里面的,选项可以是0个字节,最多是40个字节。如果为标准报头,4位首部长度应该填充多少?---》x * 4 = 20 ==>x = 5 == > 0101。
-
6位控制标志位(Flags):
URG:它为了标志紧急指针是否有效。
ACK:标识确认号是否有效。
PSH:提示接收端应用程序立即将接收缓冲区的数据拿走。
RST:它是为了处理异常连接的, 告诉连接不一致的一方,我们的连接还没有建立好, 要求对方重新建立连接。我们把携带RST标识的称为复位报文段。
SYN:请求建立连接; 我们把携带SYN标识的称为同步报文段。
FIN:通知对方, 本端要关闭连接了, 我们称携带FIN标识的为结束报文段。 -
窗口大小(Window Size):窗口大小表示本端能够接收数据的缓冲区大小。接收方在接收到数据后,会向发送方发送一个窗口大小的通知,因为缓存区的有限,以控制发送方的数据发送速率,防止对方发送的数据过快而导致数据丢失,保证数据传输的可靠性。
报头和有效载荷如何分离?
分离时报头中有首部长度字段,首部长度 * 4 (字节) - 报头的前20个字节,即 XXXX * 4 (字节) - 20 = 选项 + 数据,再减去选项的字节就为有效载荷。
(二)TCP协议的一些机制和策略
1. 确认应答
从头到尾,我们一直在说TCP是可靠的。那么可靠到底体现在哪里了呢?其中确认应答机制就是一个TCP可靠的体现,也较为重要。什么是确认应答呢?我们接着往下看。
所谓确认应答,就是不管是客服端还是服务器,收到对方报文后都会给对方应答。举个例子来理解一下:
上述再小王和小红通过网络通信时,小王问:“吃晚饭了吗?”。小王怎么确定小红收到了自己所发的信息呢?当小红回复了,且回复的内容是问所问的问题匹配,就能够说明小王的消息被小红收到了。
那么存不存在100%可靠的协议呢?答案是不存在的!无论是小红,还是小王,都无法保证自己作为最后发送数据的一方且发送出去的数据被对方收到。
我们所发出去的所有的消息,只要有匹配的应答我就能保证我刚刚发出去的消息对方一定收到了。TCP协议的确认应答机制:只要一个报文收到了对应的应答,就能保证本端发出的数据对方收到了!我们先这样简单理解TCP的确认应答机制,后文还会继续补充。
这里又引出一个问题:我可能同时发了多条信息,对方按顺序收到数据也是十分重要的。如果收到的数据是乱序的,那会出现很多意想不到的错误。怎么来保证按顺序到达呢?
2. 按序到达
我们再来了解一下TCP报文中的序号和确认序号:
- 每个TCP报文段都会分配一个唯一的序号(seq),用于标识在发送端发送的字节流中的每个字节。序号字段的大小为32位,它指示了报文段中第一个字节的序号。序号的起始值由发送方选择,并根据已发送的字节数进行递增。因为序号是相对于特定连接而言的,所以每个连接有独立的序号空间。(注:开始序号 + 数据长度 - 1 => 最后一个序号)。
- 确认序号(ack)是指接收方发送给发送方的一个值,用于确认接收到的正确字节流的下一个字节。确认序号字段的大小也是32位。接收方在接收到一个TCP报文段后,将确认序号设置为已成功接收的字节流的末尾加1(也就是序号再+1)。这样,发送方就可以知道哪些字节已经被接收方正确接收了。
举例说明如下: 假设有一个TCP连接,发送方要传输一段长度为2000字节的数据。发送方将每个报文段的序号设置为字节流中对应字节的序号。假设发送方将前1000个字节发送到接收方,此时发送方的序号为1~1000,接收方的确认序号就为1001。接收方成功接收到这些字节后,会设置确认序号为1001,表示接收方期待接收的下一个字节为1001。发送方继续发送剩余的1000个字节,此时发送方的序号为1001~2000,接收方的确认序号就为2001。接收方接收完成后,将确认序号设置为2001。具体可结合下图理解:
这里的确认应答的意思是,1001序号之前(即1~1000)的数据,接收端已经接收到了。
首先发送端发出去后,服务器会根据收到多个报文的序号进行排序,这样就可以保证有序了。其次,服务端应答时,发送端收到的确认序号一定是有序的吗?答案是不一定。因为,网络阻塞等情况,发送端收到的确认序号不一定是有序的。但是不要忘记了确认序号设置为已成功接收的字节流的末尾加1。发送端也能够很好的确认发出的数据是否已经被成功接收。
假如出现了如下情况:接收方收到了2000确认序号,但没有收到1000确认序号,这就意味着1000之前会有一段的数据丢失了吗?实际上并不是。我们再来强调一下确认序号的概念:确认序号标示的意思是确认序号之前的数据已经全部收到。这种情况只是应答的数据丢包了。假如我们丢失了1000之前会有一段的数据,接收方就不会收到2000确认序号。这就涉及到超时重传了,后续会详细讲解。
目前,我们发现是不是只用一个序号就能够完成我们上述的所有工作,那好要确认序号干什么?TCP是全双工的!任何一方在收到数据时,也可以发送数据。这时候就必须需要两个序号来完成了。
我们目前先来总结一下序号个确认序号的特点与作用:
- 序号和确认序号是的请求和应答一一对应;
- 确认序号标示的意思是:确认序号之前的数据已经全部收到;
- 允许部分确认数据丢失,或者不给应答;
- TCP是全双工的,任何一方在接收数据时,都有可能还要发送数据。所以必须是两个序号:序号与确认序号;
- 接收方会根据收到的多个报文的序号进行排序,保证收到数据是有序的。
3. 超时重传
如果在正常的通信中,本来是应该受到应答的,但是过了一段时间任然没有收到应答,这时应该怎么办呢?看如下情况:
主机A发送数据给B之后,可能因为网络拥堵等原因造成了丢包问题,数据无法到达主机B。具体如下图:
上述的丢包有两种情况:
- 一种是数据压根就没传输到,这种情况也不会受到响应。
- 另一种情况就是应答的数据丢了。
如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发,因此主机B会收到很多重复数据。那么TCP协议需要能够识别出哪些包是重复的包,这时候我们可以利用前面提到的序号,接收方会根据发送方数据的序号来进行判断该数据是否已经被我接收到了,并且把重复的丢弃掉,达到去重的效果。
接收方怎么判定超时了呢?
- 最理想的情况下,找到一个最小的时间,保证"确认应答一定能在这个时间内返回"。
- 但是这个时间的长短,随着网络环境的不同是有差异的。
- 如果超时时间设的太长,会影响整体的重传效率;
- 如果超时时间设的太短,有可能会频繁发送重复的包。
TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。
- Linux中(BSD Unix和Windows也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
- 如果重发一次之后,仍然得不到应答,等待2*500ms后再进行重传。
- 如果仍然得不到应答,等待4*500ms进行重传。依次类推,以指数形式递增。
- 累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接。
有时候超时重传的超时时间也是不固定的。在网络状态好的时候,超时的时间就会短一点。在网络情况较差时,超时时间就会相对长一点。
4. 六个常见标记位
TCP报文中还有六个常见标记位。其中每个标记为占一个字节。为什么需要这么多的标记为呢?其实这六个标记为是用来标记报文类型的。同时组合起来使用,可以使用各种方式来实现不同的通信状态和协议行为。TCP协议中的六个常见标记是SYN、ACK、FIN、RST、PSH和URG:
- SYN(Synchronize):用于建立连接的初始握手。发送方发送一个SYN报文段给接收方,请求建立连接。
- ACK(Acknowledgement):用于确认数据的传输。当成功接收到数据后,接收方发送一个带有ACK标记的报文段回复发送方,确认已经收到了数据。
- FIN(Finish):用于关闭连接。当发送方发送完所有数据后,会发送一个带有FIN标记的报文段,请求关闭连接。接收方在收到FIN报文段后,发送一个带有ACK标记的报文段进行确认,并使用一个定时器在一段时间后关闭连接。
- RST(Reset):用于强制关闭连接。当出现错误或不正常情况时,发送方或接收方可以发送一个带有RST标记的报文段,强制关闭连接,并丢弃已发送或未发送的数据。
- PSH(Push):用于立即传送数据。发送方可以设置PSH标记,通知接收方尽快将数据传送给应用层,而不是等待缓冲区填满或者等待延迟。
- URG(Urgent):用于指定紧急数据。发送方可以设置URG标记,表示报文段中有紧急数据需要尽快处理。接收方收到URG标记后,会立即传送该数据给应用层,并进行相应的处理。
我们前面所说的确认应答,接收方所应答的就是一个带有ACK的报文。注意,所有的发送与应答都是必须是以报文为基础单位发送和接受的!
URG标记是配合报头中的紧急指针字段使用的。当URG标记标记为被设置为1时,说明就会有紧急数据,此时紧急指针也会被设置。注意,紧急指针是一个偏移量,标明的就是紧急数据相对其实数据的偏移量。紧急数据一般只能传递(或者说占用)一个字节,因为大多数情况下,报文都是按序到达然后被读取的,如果紧急数据太多,读取的时间太长,会破坏TCP按序到达的特性。 当然,如果是多个字节的话,具体的紧急数据范围是由使用TCP协议的应用程序或协议自行决定和定义的。
RST标记为使用的场景较为特殊,情况也相对复杂。RST标记的作用有以下几个方面:
- 异常情况处理:当发生错误或非法状态时,RST标记可以用于立即关闭连接。例如,当双方期望建立连接但未收到合适的响应,或者当连接被认为是非法的或不安全的时,可以发送带有RST标记的TCP报文来终止连接。
- 连接复位:在某些情况下,TCP协议会需要重置已经建立的连接以恢复到初始状态。这种情况下,发送带有RST标记的TCP报文可以迫使接收方释放已经建立的连接,并且重新开始建立新的连接。
- 防止连接延迟:如果某个TCP连接长时间没有活动,为了避免资源浪费,可能会选择发送带有RST标记的TCP报文来提前关闭连接。
以下是一个例子,说明RST标记的使用情景:
假设有两台计算机A和B正在进行TCP连接,其中A是客户端,B是服务器端。在正常传输数据的过程中,如果服务器B发现某些异常情况,例如接收到非法数据、资源不足等,B可以发送一个带有RST标记的TCP报文给A。客户端A收到带有RST标记的TCP报文后,会立即关闭连接,不再进行任何后续操作。
需要注意的是,正常情况下,应该使用正常的TCP释放过程来关闭连接,而不是简单地发送带有RST标记的TCP报文。只有在特殊情况下,如安全问题或连接异常时,才应该使用RST标记来强制关闭连接。
SYN 和 FIN 标记为将在TCP的三次握手和四次挥手环节详细讲解。
5. TCP三次握手四次挥手
6. TCP的缓冲区
我们在应用层向下交付数据的时候,并不是直接把数据放到了网络里面,而是放到了TCP的缓冲区当中!TCP是由发送缓冲区和接收缓冲区的!!!
TCP维护的缓冲区主要分为发送缓冲区和接收缓冲区:
发送缓冲区: 发送缓冲区位于发送方主机上,用于临时保存待发送的数据。当应用程序调用发送操作发送数据时,数据首先被拷贝到发送缓冲区。发送缓冲区通常由操作系统内核管理,并根据网络条件适时发送数据。
TCP发送缓冲区的主要作用是:
- 提供一个临时存储区,用于将应用程序产生的大量数据暂时保存,以便分段发送。
- 将数据进行分段并添加TCP头部等信息,使其符合TCP协议的要求。
- 通过流量控制和拥塞控制机制,动态地调整发送速率,以适应网络状况(后续会详解)。
接收缓冲区: 接收缓冲区位于接收方主机上,用于暂时存放接收到的数据。当TCP在网络上接收到数据时,数据被拷贝到接收缓冲区。接收缓冲区通常由操作系统内核管理,并提供给应用程序读取。
TCP接收缓冲区的主要作用是:
- 接收和存储网络传输过来的数据,以便应用程序从中读取。
- 对接收的数据进行重组,将分散的数据片段按序拼接成完整的消息或文件。
- 根据TCP协议规定,对接收到的数据进行校验和错误检测,以确保数据的完整性和正确性。
我们所用的write、read、recv、send等函数,本质上就是从TCP的缓冲区拷贝数据或者拿数据。具体如下图所示:TCP维护的缓冲区主要分为发送缓冲区和接收缓冲区,那么发送方和接收方不仅仅只限于发送和接受了!这样再次说明了TCP是可以支持全双工的。
我们可以理解TCP的接收和发送缓冲区为一个线性数组,具体如下图:
数组的下标就是序号。所以在收到数据时,就可以很好的去重。同时确认序号也可以很好的被设置,同时告诉发送端下次开始发送的位置。
那么这里又有一个问题了:既然数据是发送到TCP所维护的缓冲区,那能一直发吗?或者发大量的数据?答案是肯定不行的。这就涉及到了如何发送的问题。我们大概也能理解,发送不能发的太快,也不能发送的太慢。后文也会在流量控制和滑动窗口中详细讲解。
7. 流量控制
接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包问题,继而引起丢包重传等等一系列连锁反应。那么发送端既不能发送太快,也不能发送太慢,发送端发送数据的速度有什么来决定呢?目前我们就可以理解为是由接收端的窗口大小决定的,而接收端窗口大小不就表示的是接收端的接收缓冲区所剩余空间的大小吗!!!
接收端如何把窗口大小告诉发送端呢? 回忆我们的TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息。那么问题来了, 16位数字最大表示65535,那么TCP窗口最大就是65535字节么?实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移 M 位。
TCP支持根据接收端的处理能力,来决定发送端的发送速度。 这个机制就叫做流量控制(Flow Control)。我们再来看流量控制中的细节:
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段, 通过ACK端通知发送端本端的承载能力;
- 窗口大小字段越大,说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后,就会减慢自己的发送速度;
- 如果接收端缓冲区满了,就会将窗口置为0。这时发送方不再发送数据,,但是需要定期发送一个窗口探测数据段(不带数据),使接收端把窗口大小告诉发送端。如下:
在进行流量控制的时候,第一次我们如何得知时方的接受能力呢?如何在第一次传输数据时就得知对方的接受能力呢?
注意,第一次交换数据不等同于第一次交换报文!不要忘记了,在传输数据之前,一定是要先把连接建立好的。也就是有三次握手!在前两次握手时,就可以互相告诉双方自己的窗口大小,也就是本端的接受能力!
通过流量控制维护发送缓冲区和接收缓冲区,TCP可以实现可靠的数据传输。发送方将数据经过分段、封装和流量控制等处理后发送到网络中,接收方则负责接收并按序重组数据。这种缓冲区的机制不仅确保了数据的可靠性和有序性,还能适应不同的网络状况,并根据具体情况进行动态调整。
8. 滑动窗口
我们之前了解的是发送消息时,都是一条一条发送的。具体如下图:
对每一个发送的数据段,都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差,尤其是数据往返的时间较长的时候。
TCP要保证的不仅仅是可靠性,还要保证效率。
那么能不能一次发送多条数据呢,也就是一次发送多个报文呢?最明显的优势:在传统的单个报文传输中,每次传输都需要等待一个ACK响应的时间来获取确认,而在一次发送多个报文的情况下,ACK确认可以一次性返回,减少了等待时间,提高了数据传输的效率(其实是将多个段的等待时间重叠在一起了)。具体如下图:
答案是可以的。但是怎么控制一次到底能够发多少个报文过去?发过去的报文都有哪几个得到了响应? 滑动窗口就可以帮助我们很好的解决这些问题!
TCP中的滑动窗口是一种动态调整的机制,用于控制发送方将数据发送到接收方的速率,以确保网络上的稳定和可靠的数据传输。滑动窗口的基本思想是:接收方告诉发送方它有多少可用的缓冲区空间,发送方可以根据这个信息来确定发送多少数据。发现这与流量控制很相似!流量控制中引入了滑动窗口机制,也可以理解为流量控制就是基于滑动窗口来实现的。但是我们需要区分的是流量控制体现更多的是数据传输的可靠性,滑动窗口主要是传输数据速率的提升。通过下图我们再来理解一下滑动窗口:
- 已经发送并且已经收到 ACK 的数据:这些是发送给接收方的数据,并且接收方已经发送了确认应答。这部分数据可以从发送缓冲区中删除了。
- 已经发送但还没有收到 ACK 的数据:这些数据已经发送给接收方,但还没有收到确认应答。这部分数据仍然需要留在发送缓冲区中,因为发送方需要在超时或接收方重新请求时进行重传。
- 待发送的数据:发送方准备发送但还没有发送的数据。这些数据等待发送,直到发送窗口允许发送。
滑动窗口就是上图中红色框所维护的那一段。滑动窗口中的数据也是在自己的发送缓冲区当中,属于发送缓冲区的一部分!以下是TCP中滑动窗口理解的一些关键要点:
- 接收方窗口大小(Receiver Window Size):接收方在TCP报文中通过窗口大小字段告诉发送方它有多少可用的缓冲区空间。这个窗口大小是动态调整的,可以根据接收方的缓冲区情况和网络拥塞情况而变化。
- 发送方窗口大小(Sender Window Size):发送方也会维护一个窗口大小,表示它可以发送多少数据而不需要等待确认。发送方的窗口大小通常受到接收方窗口大小和网络拥塞的影响。
- 滑动窗口的操作:发送方发送数据,并等待接收方的确认。一旦接收方成功接收并确认数据,发送方的窗口会向前滑动,允许发送更多数据。这就是为什么它被称为"滑动"窗口。
- 流量控制和可靠性:滑动窗口机制有助于实现流量控制,防止发送方过多地发送数据,从而导致网络拥塞。它还增强了可靠性,因为接收方可以告知发送方哪些数据已经成功接收,哪些需要重新传输。
批量的发送一组数据,发送这一组数据的过程中就不需要等待ACK就直接往前发,此时就相当于使用等待一份ACK的时间去等待4个ACK(图表中是一下子发4个数据),窗口越大,批量发送的数据也就越多,效率越高:
通过对上述概念的理解后,我们再来理解一下滑动窗口的本质:发送方一次可向对方发送数据的上限,即无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000个字节(四个段)。滑动窗口的上限主要取决于对方的接受能力(目前认知)。
我们再来深入理解一下滑动窗口:
滑动窗口一定是向右移动吗?
- 答案是不一定的!当接收端确认了某些数据包的正确性后,它会向发送端发送一个确认响应(ACK),表示接收端期望接收的下一个数据包的序号(确认序号也表示发送端哪些数据已经被接收方成功处理)。发送端在收到这个ACK后,会将其滑动窗口的左边界向右移动,以释放已经确认的数据所占用的缓冲区空间。这样,发送端的窗口就可以继续容纳新的数据,以供后续的发送操作。 此时滑动窗口的左边界(start)向右移动(窗口在变小)!这种情况并不是窗口在整体移动。滑动窗口的更新:窗口左边界(start)= 收到的应答报文的确认序号,窗口的右边界(end)= 窗口左边界(start)+ 收到报文中的窗口大小。
滑动窗口大小可以为0吗?
- 答案是可以的。当接收方处理数据较慢,甚至不处理时,这时滑动窗口的大小会一直减小,且可以减到0。
如果没有收到开始的报文的应答,而是收到中间的可以吗?那么会有影响吗?答案是可以的且不影响。我们看如下两种情况:
- 数据报文已经抵达,ACK被丢了。但是这种情况下,部分ACK丢了并不要紧,因为可以通过后续的ACK进行确认。具体如下:1~1000、1001~2000、3001~4000已发送但ACK却丢了,但通过后续已被接收的确认应答就可以确定之前的序号报文已成功被接收。
- 如果中间数据报文就丢了,那么ACK中的确认序号会不会直接跳过丢失的数据报文的序号呢?答案是不会的!我们看如下情况:
当某一段报文段丢失之后,发送端会一直收到1001这样的ACK,就像是在提醒发送端"我想要的是1001"一样。如果发送端主机连续三次收到了同样一个"1001"这样的应答,就会将对应的数据1001 - 2000重新发送。这个时候接收端收到了1001之后,再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中。
到这里我们再来理解一下超时重传。超时重传背后的含义:就是没有收到应答的时候,数据必须被暂时保存起来!保存的位置就是在滑动窗口当中!
滑动窗口如果一直向右滑动,会不会存在越界问题?答案是不存在的。我们可以吧滑动窗口看成一个环状结构的。具体如下图:
(1)快重传(高速重发控制)
快重传(Fast Retransmit)机制是指当发送方连续收到3个重复确认(ACK)时,就认为这个数据包之前发送的数据包已经丢失,会立即进行重传,而不等待超时时间到达。这样可以避免等待较长的超时时间,从而提高数据传输的效率。
我们对比一下超时重传:超时重传指当发送方发送一个数据包后,会设置一个定时器,如果在超时时间内没有收到对应的确认(ACK),则认为这个数据包丢失了,触发重传。
两者的区别主要集中在触发重传的条件和时机上:
- 快重传是在发送方收到重复的确认时立即进行重传,无需等待超时时间。
- 超时重传是等待一个合适的超时时间后,如果没有收到确认,则进行重传。
举例来说,假设TCP连接中有4个数据包需要传输,数据包2丢失了,而数据包1、3、4正常送达。以下是具体的过程:
- 发送方发送数据包1、2、3。
- 接收方正常接收到数据包1、3、4,并回传确认的是ACK2、ACK2、ACK2。
- 发送方在发送数据包后设置定时器开始计时。
- 发送方收到突然收到三个ACK2(重复的确认)。
- 发送方立即意识到数据包2丢失,进行快重传,重传数据包2。
- 发送方继续发送数据包5。
通过快重传,发送方可以快速发现丢失的数据包并立即进行重传,而不需要等待整个超时时间。这可以避免了长时间的等待,提高了传输效率。快重传的速率比超时重传快,为什么不全都用快重传呢?原因是快重传是有条件的,即发送方收到三个连续的对同一个数据包的ACK确认。
需要注意的是,快重传和超时重传是TCP协议中的两种处理丢包的机制,它们可以相互配合使用以提高数据传输的可靠性和效率。
9. 拥塞控制
前面我们了解到丢包时,会进行超时重传。这里的丢包是指少量的丢包。但是如果出现大量的丢包情况呢?一旦出现大量的丢包,我们就认为是网络出现了问题,一般情况下就是网络拥塞了!
注意,服务器不只是在给你一台主机提供服务,可能会同时给大量主机提供服务。可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。于是这里就引入了拥塞控制机制。
TCP拥塞控制是一种反馈机制,通过监测网络的拥塞情况并采取适当的措施来防止或减轻拥塞。以下是TCP拥塞控制的关键要点:
- 拥塞窗口(Congestion Window):发送方维护一个拥塞窗口,该窗口表示可以发送到网络的数据量。初始时,拥塞窗口很小,然后随着时间的推移逐渐增大。
- 拥塞信号:当网络出现拥塞时,路由器或交换机可以发送拥塞信号给发送方,通知其减少发送数据的速率。这可以通过发送TCP拥塞通知(TCP Congestion Notification)或丢弃数据包来实现。
- 慢启动(Slow Start):在连接刚开始时,拥塞窗口是以指数级增长,以探测网络的容量。当拥塞窗口大小达到某个阈值时,它将进入拥塞避免阶段。
- 拥塞避免(Congestion Avoidance):在拥塞避免阶段,拥塞窗口是以线性增长的方式增加,以更稳定的速率发送数据。
- 拥塞控制算法:TCP使用不同的拥塞控制算法,如TCP Reno、TCP NewReno、TCP Cubic等,以适应不同网络条件和用例。
一但收到网络拥塞信号,就会进行慢启动。为什么要慢启动呢?慢启动会一直很慢吗?
- 首先当网络拥塞时,肯定是不能在发送过多数据了。这时会发送少量数据,看看网络是否拥塞。不只是我们自己的主机发送少量数据,是触发到网络拥塞信号的主机都会发送少量数据,这样就可以是网络在很大程度上得到缓解。
- 像上面这样的拥塞窗口增长速度,是指数级别的。"慢启动"只是指初使时慢,但是增长速度非常快,以便恢复传输速率。但是,为了到后面不增长的那么快,因此不能使拥塞窗口单纯的加倍。此处引入一个叫做慢启动的阈值(ssthresh)。当拥塞窗口超过这个阈值的时候,不再按照指数方式增长, 而是按照线性方式增长(拥塞避免)。
- 慢启动不仅可以缓解网络拥塞的问题,同时还可以在中后期,网络恢复了之后,尽快恢复通信的速率。
那么现在发送数据,不只是要考虑对方的就收能力了,还要考虑网络的情况。 拥塞窗口本质是单台主机一次向网络中发送大量数据时,可能会引发网络拥塞的上限值!所以现在再看滑动窗口的大小 = min(拥塞窗口,对方窗口大小[接受能力] )。
10. 延迟应答
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。这时引入延迟应答机制。延迟应答机制的工作原理:
- 当接收方接收到数据后,不是立即发送ACK确认应答,而是等待一段时间。
- 在这段延迟的时间里,接收方的应用程序有足够的时间从接收缓冲区中取走数据,从而释放更多的缓冲区空间。
- 由于接收缓冲区空间变大,发送方的滑动窗口也可以相应地扩大。更大的窗口意味着发送方可以在不等待确认的情况下发送更多的数据。
- 从而提高了数据传输的效率。
示例说明:
- 假设接收端缓冲区为1M。一次收到了500K的数据;如果立刻应答,返回的窗口就是500K。
- 但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了;
- 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来。
- 如果接收端稍微等一会再应答, 比如等待20ms再应答,那么这个时候返回的窗口大小就是1M。
窗口越大,网络吞吐量就越大,传输效率就越高。 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。当我们进行延迟应答时,就有可能允许发送方一次发送更多的数据,这样是不是就提高了单次IO的速率。
注意,我们上面所说的是有可能。因为发送方单次发送数据的上限是取决于网络状态(拥塞窗口)和对方的接受能力(对方的窗口)。
那么所有的包都可以延迟应答么? 肯定也不是。具体如下:
- 数量限制: 每隔N个包就应答一次;
- 时间限制: 超过最大延迟时间就应答一次;
具体的数量和超时时间,依操作系统不同也有差异。一般N取2,超时时间取200ms。
11. 捎带应答
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是"一发一收"的。意味着客户端给服务器说了"How are you",服务器也会给客户端回一个"Fine, thank you"(数据)。
那么这个时候再加一句 "And you?" (ACK),就可以搭顺风车,和服务器回应的"Fine, thank you"一起回给客户端。
捎带应答是TCP协议中的一种效率机制,允许在同一个TCP包中既发送数据又发送确认应答。
捎带应答是在延迟应答的基础上进行的。延迟应答是为了保证网络不拥塞的情况下尽量提高传输效率,通过延迟发送ACK确认应答来让窗口变得更大,从而增加网络吞吐量。而捎带应答则是在延迟应答的基础上,进一步通过合并数据包和确认应答来减少传输开销。
TCP协议的捎带应答机制(Nagle's Algorithm)是一种用于优化网络通信性能的策略,它有助于减少小数据包的发送,从而降低网络流量和提高效率。该机制的核心思想是合并多个小数据包,将它们一起发送,以减少网络上的包头开销。
四、TCP 总结
(一)面向字节流
TCP的面向字节流是指它将数据视为连续的字节流,而不是将数据划分为离散的消息或数据包。这意味着在TCP连接中,数据被视为一系列无结构的字节,发送方和接收方之间没有明确的消息边界。这与UDP不同,因为UDP是面向数据报的,每个数据包都是独立的单元,有明确的边界。
以下是TCP的面向字节流特性的一些关键点与UDP进行对比:
数据连续性:
- TCP:数据在TCP连接中被视为一个无间断的字节流。发送的数据可以被拆分成多个小块,然后在接收端重新组装,但这一切都在传输层进行,上层应用程序不需要关心数据的分段和重组。
- UDP:UDP以数据报的形式传输数据,每个数据报都是独立的消息。应用程序必须自行处理消息的边界和重组。
消息边界:
- TCP:没有明确的消息边界,因此多个write()操作的输出可能会在接收端被组合成一个大的数据块,或者一个write()操作的数据可能会被拆分成多个小块。
- UDP:每个UDP数据包都有自己的消息边界,接收方可以清楚地知道每个数据包的开始和结束。
数据的完整性和顺序:
- TCP:TCP确保数据的完整性和顺序交付。它使用序列号和确认机制来确保数据不会丢失或乱序传输。
- UDP:UDP不提供数据的完整性和顺序保证,数据可能会在传输过程中丢失或以不同顺序到达。
总之,TCP的面向字节流特性使其适用于需要可靠数据传输和保持数据顺序的应用,如文件传输和网页访问。而UDP的面向数据报特性更适合对实时性要求较高,可以容忍少量数据丢失的应用,如实时游戏和实时音视频传输。选择使用哪种协议应根据应用的需求和性质来决定。
(二)粘包问题
TCP的粘包问题是指在数据传输过程中,多个小数据包被合并成一个大数据包或者一个大数据包被拆分成多个小数据包,导致数据的接收端难以正确解析和处理。这种情况通常发生在TCP协议的数据传输过程中,因为TCP是面向字节流的协议,不像UDP那样具有消息边界,所以数据包的界限不明确。
粘包问题可能会导致数据的混乱和错误解析,特别是在需要按照消息边界来解析数据的应用中,如网络通信协议或文件传输。解决粘包问题可以采取以下一些方法:
- 消息分隔符:在数据包中添加特殊字符或标记作为消息的分隔符,接收端可以根据这些分隔符来将接收到的数据拆分成多个消息。
- 消息长度前缀:在每个数据包的开头添加一个表示消息长度的字段,接收端首先读取这个长度字段,然后再根据消息长度来读取相应长度的数据。
- 使用缓冲区:接收端可以使用缓冲区来暂时存储接收到的数据,然后根据消息边界来从缓冲区中提取完整的消息。
- 协议设计:在设计通信协议时,考虑消息边界和数据包的格式,以尽量减少粘包问题的发生。
在TCP的协议报头中,没有如同UDP一样的 "报文长度" 这样的字段,但是有一个序号这样的字段。TCP使用序列号来标识每个数据段的顺序。发送端在每个数据段中添加一个序列号,接收端根据序列号来确定数据的正确顺序。
选择哪种解决方案取决于具体的应用场景和需求。通常,消息分隔符和消息长度前缀是比较常见的解决方法,但在一些高性能的应用中,可能需要结合多种方法来解决TCP粘包问题。
(三)TCP协议中的机制总结
TCP(传输控制协议)是一种面向连接的、可靠的传输层协议,用于在网络中传输数据。以下是TCP协议中的主要机制:
- 确认应答:TCP通过ACK回应,来告诉发送方收收到了报文数据。以避免数据在丢失的情况下,依然在传送数据。
- 流量控制:TCP使用滑动窗口机制来控制发送方和接收方之间的数据流量,以避免发送方发送过多数据导致接收方无法处理。
- 拥塞控制:TCP通过使用拥塞窗口和拥塞避免算法来控制网络中的拥塞情况,以避免网络过载导致的性能下降。
- 三次握手和四次挥手:TCP在建立连接时使用三次握手,确保双方都准备好进行通信。在关闭连接时,使用四次挥手来优雅地关闭连接。
- 超时与重传:TCP通过设置超时时间来检测丢失的数据包,并在超时后重新发送未收到确认的数据。
- 序号和确认号:TCP使用序号来标识发送的数据段,使用确认号来确认已经接收到的数据段。
- 滑动窗口:TCP使用滑动窗口机制来动态调整发送方发送数据的速率,以适应网络的状况。
- MSS(最大报文段长度):MSS是TCP数据段的最大长度,通常根据网络的MTU(最大传输单元)来确定,以避免分段和重新组装的开销。
- 延迟确认:TCP使用延迟确认机制来减少确认消息的发送次数,以提高网络效率。
- 携带应答:在给发送方回应时,也会携带上部分有效数据,提高传输效率。
请注意,以上是TCP协议中的一些重要机制,它们共同确保了数据的可靠传输和网络的稳定性。
学完TCP协议,发现TCP协议很复杂。为什么TCP这么复杂?因为要保证可靠性,同时又尽可能的提高性能。其中保证可靠性的有:
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能的有:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
其他:
- 定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等
(四)用UDP实现可靠传输(经典面试问题)
UDP(User Datagram Protocol)是一种无连接、不可靠的传输协议,通常用于快速传输数据,但不提供可靠性保证。要实现可靠传输,通常需要再一些额外的机制,因为UDP本身并不提供这些功能。
以下是一种可能的方法,详细解释如何使用UDP实现可靠传输:
- 确认和重传机制:在发送端,将每个UDP数据包都分配一个唯一的序列号,并设置一个超时时间。一旦发送UDP数据包,就等待接收端的确认。如果在超时时间内没有收到确认,发送端将重传该数据包。这个确认和重传机制确保数据的可靠性。
- 流控制:在发送端和接收端之间实现流控制,以确保发送速率不会超过接收端的处理能力。这可以通过维护发送窗口和接收窗口来实现。发送窗口定义了可以发送的未确认数据包数量,而接收窗口定义了接收端可以接受的数据包数量。
- 数据校验:UDP本身不提供数据校验,但你可以在应用层添加校验和,以检测数据包是否被损坏。常用的校验和算法包括CRC(循环冗余校验)和MD5等。
- 拥塞控制:实现拥塞控制以避免网络拥塞和数据丢失。这可以通过动态调整发送速率和发送窗口来实现,以确保网络不会过载。
- 超时处理:合理设置超时时间,以便在发生数据包丢失或延迟时及时重传数据包。
- 缓冲区管理:在发送端和接收端维护适当的缓冲区,以处理数据包的排队和处理。
- 错误处理:处理各种错误情况,例如重传次数超过阈值时的放弃,以及接收到重复数据包时的处理。
我们知道TCP是可靠的,为了使UDP可靠,我们可以结合TCP中的机制来实现UDP的安全可靠!