网络层
传输层
负责主机间进程间的通信,即端到端通信,流量控制,差错控制,复用分用(进程间根据端口号),主要使用TCP和UDP协议。
Socket套接字
由主机IP地址和端口号组成,唯一标识了某台主机的某个应用
数据链路层的SAP是MAC地址,网络层的SAP是IP地址,传输层的SAP是端口号
复用和分用
复用:应用层所有应用都可以通过传输层将数据传送网络层
分用:传输层将从网络层接收的数据根据端口号交给不同的应用。
UDP
用户数据报协议:一种面向事务的,无需建立连接的,简单不可靠的传输层通信协议。
特点:
- 无需建立连接
- 不可靠交付,尽最大努力交付
- 分组首部开销小(8B)
- 面向报文,适合一次性传输少量数据的网络应用
- 计算校验和需要加入12B伪首部,出错直接丢弃数据报,然后向上层发送错误报告。
- 用于DNS、SNMP,TFTP,RTP(实时传输协议)
UDP首部格式

Source Port (16 bits): 源端口,标识发送方的应用程序端口号。
Destination Port (16 bits): 目标端口,标识接收方的应用程序端口号。
Length (16 bits): 长度字段,表示UDP报文的总长度(包括首部和数据),以字节为单位。
Checksum (16 bits): 校验和,用于检测UDP首部和数据的错误。可以为全0,表示不使用校验和。
Data (variable): 数据字段,包含UDP报文的有效载荷。UDP报文的数据部分长度可以为0。
使用UDP传输可能遇到的问题
丢包:UDP不保证数据的可靠传输,因此可能会发生丢包现象。丢包可能由网络拥塞、传输错误、网络故障,或者缓冲区溢出等原因引起,导致部分数据包在传输过程中丢失。
解决方法:尽管UDP本身不提供重传机制,但是在应用层可以实现简单的重传机制。例如,发送端可以周期性地重发丢失的数据包,或者通过应答机制来确认接收端已经正确接收到数据包。
乱序:UDP不保证数据包的顺序传输,因此数据包在传输过程中可能会发生乱序现象。乱序可能会影响数据的解析和处理,需要在应用层进行适当的处理和重组。
解决方法:对千乱序的解决方法可以采用发送端在数据段中加入数据报序号的方法,这样接收端对接收到数据的头端进行简单地处理就可以重新获得原始顺序的数据
无流量控制和拥塞避免导致缓冲区溢出:UDP不提供流量控制机制,因此发送端可能会以较高的速率向网络发送数据,导致网络拥塞。这可能会影响其他应用的性能,或者导致数据包丢失。
解决方法:
发送速率限制:应用程序可以自行限制UDP数据包的发送速率,控制发送端的数据发送量。通过设置发送速率的阈值或者采用滑动窗口等机制,使得发送端在发送数据时不会过快,避免导致网络拥塞。
增大缓冲区大小:可以通过操作系统或者编程语言提供的接口来增大UDP服务端的接收缓冲区大小,以容纳更多的数据。较大的缓冲区可以在一定程度上减少缓冲区溢出的风险。
UDP的冗余传输方案:在外网通信链路不稳定的情况下,有什么办法可以降低UDP的丢包率呢?一个简单的办法来采用冗余传输的方式。如下图,一般采用较多的是延时双发,双发指的是将原本单发的前后连续的两个包合并成一个大包发送,这样发送的数据量是原来的两倍。这种方式提高丢包率的原理比较简单,例如本例的冗余发包方式,在偶数包全丢的情况下,依然能够还原出完整的数据,也就是在这种情况下,50%的丢包率,依然能够达到100%的数据接收。

TCP
传输控制协议,一种基于字节流,面向连接的,可靠的传输层通信协议。保证传输无差错,不丢失,不重复,按序到达。
特点:
- 面向连接,三次握手
- 全双工通信,有发送缓存,接收缓存,
- 可靠的逻辑信道,无差错,不丢失,不重复,按序到达
- 面向字节流(报文段segment),把应用程序交下来的数据看成一连串的无结构的字节流。
- 用于FTP,HTTP,TELNET等可靠性要求较高的应用程序
TCP报文首部格式
TCP报文首部的长度为20字节(不包括选项和填充),但可选字段的存在可能使首部长度增加。
- Source Port (16 bits): 源端口,标识发送方的应用程序端口号。
- Destination Port (16 bits): 目标端口,标识接收方的应用程序端口号。
- Sequence Number (32 bits): 顺序号,标识本次TCP报文中第一个数据字节的序列号。用于实现可靠的数据传输。
- Acknowledgment Number (32 bits): 确认号,标识期望接收的下一个数据字节的序列号。仅在ACK标志置位时有效。
- Data Offset (4 bits): 数据偏移,表示TCP首部的长度,以32位字为单位。该字段用于指示TCP报文的首部长度,最大值为15。
- Reserved (3 bits): 保留字段,必须设置为0。
- Control Flags (9 bits): TCP控制标志,包括 URG、ACK、PSH、RST、SYN 和 FIN 等标志。
- URG (1 bit): 紧急指针(Urgent Pointer)标志。
- ACK (1 bit): 确认标志,表示确认号字段有效。
- PSH (1 bit): 推送标志,表示接收方应该尽快交付数据给应用层。
- RST (1 bit): 复位标志,用于中断连接。
- SYN (1 bit): 同步标志,用于建立连接。
- FIN (1 bit): 结束标志,用于释放连接。
- Window (16 bits): 窗口大小,表示接收方能够接收的最大数据量,用于流量控制。
- Checksum (16 bits): 校验和,用于检测TCP首部和数据的错误。
- Urgent Pointer (16 bits): 紧急指针,当URG标志置位时,该字段表示紧急数据的最后一个字节在数据中的偏移。
- Options (variable): 可选字段,用于包含一些可选的控制信息,如最大报文段长度、窗口扩大因子等。
- Padding (variable): 填充字段,用于将首部扩展至32位的倍数。
TCP与UDP的区别
是否面向连接:UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。
是否是可靠传输:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
是否有状态:这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了。
传输效率:由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。
传输形式:TCP 是面向字节流的,UDP 是面向报文的。
首部开销:TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。
是否提供广播或多播服务:TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多;
综上所述,UDP适用于实时性要求高、数据量小且频繁发送的简单应用场景,而TCP适用于可靠性要求高、顺序传输要求严格的复杂应用场景。
TCP连接(三次握手)
①客户端向客户端发送请求连接报文,并进入SYN_SENT状态,等待服务端确认(SYN=1 seq=x)
②服务端收到请求连接报文后,向客户端返回确认报文,允许连接,并进入SYN_RECV状态(SYN=1,ACK=1,ack=x+1,seq=y)
③客户端收到服务器端的确认报文后,向服务端发送确认报文,客户端完成TCP连接,服务端收到确认报文后也完成TCP连接,完成连接后都进入ESTABLISHD状态,第三次握手客户端可携带数据(SYN=1,ACK=1,seq=x+1,ack=y+1)
seq序列号的作用
- 数据段排序和组装:每个TCP数据段都包含一个seq位,用于标识该数据段中首字节的序列号。接收方通过检查seq位的值可以确定数据段的顺序,并将接收到的数据按正确的顺序进行组装。TCP协议确保按照seq位的值将接收到的数据进行正确的排序和组装,以保证数据的完整性和正确性。
- 确认收到的数据:接收方在向发送方发送确认(ACK)时,将确认号(ack位)设置为已成功接收的数据段中下一个期望接收的字节的序列号。发送方通过检查接收到的ACK确认号,可以确定哪些数据已经被成功接收,从而可以进行相应的重传或发送下一批数据。
- 滑动窗口控制:TCP使用滑动窗口机制来控制发送方发送数据的速率。seq位和窗口大小一起用于指示发送方可以发送的数据量。发送方根据接收方发送的ACK确认号和窗口大小来确定可以发送的数据段的范围,并设置相应的seq位。
- 重传机制:TCP使用序列号来进行数据的重传。当发送方发送的数据段未收到确认或发生丢失时,发送方可以根据已发送的数据段的序列号进行重传,以确保数据的可靠传输。
seq的初始化
初始seq值的选择是由TCP实现决定的,并且在建立连接时由双方协商确定。具体的算法和策略可能因TCP实现的不同而有所差异。
防止序列号的预测:为了增强安全性,初始seq值应该难以被猜测。如果序列号可以被轻易猜测到,攻击者可能能够预测TCP连接中的数据包序列,并进行恶意操作。因此,选择一个随机或伪随机的初始seq值可以增加安全性。
三次握手原因
第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
- 确保双方都能够发送和接收数据: 通过三次握手,可以确保客户端和服务器都能够正常地发送和接收数据。在握手的过程中,双方都确认了对方的能力,确保连接的双向通信能够正常建立。
- 防止已失效的连接请求触发建立连接: 如果只有两次握手,那么在网络中可能存在已失效的连接请求。例如,客户端发送连接请求,但由于网络问题导致请求被延迟到达服务器,而此时客户端已经放弃了连接。如果只有两次握手,服务器会错误地认为客户端要建立连接,但实际上客户端并不需要。通过三次握手,可以降低这种情况的发生。
- 防止重复连接建立: 在网络中,可能存在延迟、拥塞等问题,导致客户端发送的连接请求在一定时间内无法到达服务器。如果没有第三次握手,而只有两次握手,那么在一定条件下,服务器可能会收到客户端的重复连接请求,并误认为是不同的连接。通过第三次握手,可以减少这种情况的发生。
TCP连接的释放(四次挥手)
①客户端向服务端发送连接释放报文(FIN),停止发送数据,进入FIN_WAIT_1状态(FIN=1,seq=x)
②服务端收到客户端的报文后回送一个确认报文,进入CLOSE_WAIT状态,此时客户端到服务器这个方向的连接就释放了,但服务器还可以继续发送数据(ACK=1, seq=y,ack=x+1)
③服务端发送连接释放报文(FIN),停止服务端到客户端的数据传送,进入LAST_ACK状态(FIN=1,ACK=1,seq=z,ack=x+1)
④客户端收到FIN报文后,回送一个确认报文,进入TIME-WAIT状态,等待2MSL后,若无收到服务端重发的FIN报文,则进入CLOSE状态,连接彻底关闭(ACK=1,seq=x+1,ack=z+1)
四次挥手的原因
TCP是全双工通信的协议,每个方向的数据流都需要单独进行关闭。因为服务器收到客户端断开连接的请求时,可能还有一些数据没有发完,这时先回复 ACK,表示接收到了断开连接的请求。等到数据发完之后再发 FIN,断开服务器到客户端的数据传送。
为什么会有TIME_WAIT状态(等待2MSL)
若客户端发送的最后一个ACK报文丢失,则服务器再经过MSL后会重发的FIN报文。
如果客户端已经关闭,则无法对重发的FIN报文进行确认,服务端无法关闭连接。2MSL为2倍的TCP报文的最长寿命,若等待2MSL后没有收到服务端重发的FIN报文,说明服务端已正确接收ACK报文并关闭连接。
TCP的短连接和长连接的区别
TCP短连接流程和实现:
- 客户端创建一个TCP套接字(Socket)并向服务器发起连接请求。
- 服务器接受连接请求,创建一个新的套接字,用于与客户端进行通信。
- 客户端和服务器之间进行数据交换,包括请求和响应等。
- 数据交换完成后,要么客户端要求关闭连接,要么服务器主动关闭连接。
- 关闭连接后,客户端和服务器的套接字被释放,连接断开。
TCP长连接流程和实现:
- 客户端创建一个TCP套接字并向服务器发起连接请求。
- 服务器接受连接请求,创建一个新的套接字,用于与客户端进行通信。
- 客户端和服务器之间进行数据交换,包括请求和响应等。
- 数据交换完成后,双方保持连接处于活动状态,并继续可以进行数据交换。
- 当一方决定关闭连接时,发送关闭连接的请求。
- 接收到关闭连接的请求后,双方都关闭连接,并释放相应的套接字。
- 实现TCP长连接的关键是保持连接处于活动状态,直到其中一方决定关闭连接。
这可以通过以下方式实现:
- 客户端和服务器需要使用套接字编程来创建和管理TCP连接。
- 客户端和服务器可以使用操作系统提供的套接字库,如Socket API。
- 在客户端和服务器之间进行数据交换时,双方可以通过发送心跳包(keep-alive)来维持连接的活跃状态。心跳包是一个空的TCP消息,用于告知对方连接仍然有效。
- 可以在TCP套接字上设置连接超时时间,以确保及时检测到连接的失效。
- 客户端和服务器应该处理异常情况,如网络故障、连接中断等,并进行相应的错误处理和重连机制。
TCP如何保证可靠性?
校验和
TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
编号,确认,重传
编号:每个字节对应一个编号,只能按序接收
确认:使用累计确认或捎带确认机制,正确接收但失序的报文先缓存
重传:超时重传:发送方如果在规定时间内没有收到确认就要重传已发送的报文段
冗余ACK:收到3个重复的ACK报文立即重传
流量控制
- 窗口大小: 接收方通过在TCP头中的窗口大小字段(Window Size)告诉发送方它目前能够接收的数据量。这个窗口大小是动态调整的。
- 滑动窗口: 发送方维护一个发送窗口,其大小不能超过接收方通告的窗口大小。发送方只能发送位于发送窗口内的数据。
- 动态调整: 随着接收方不断处理数据,它会通告更大的窗口大小。发送方会根据接收方通告的窗口大小动态调整自己的发送窗口,以提高传输效率。

TCP拥塞控制
发送窗口swnd = min(接收窗口rwnd,拥塞窗口cwnd)
接收窗口:接受发根据缓存容量设置的值,反应接收方容量
拥塞窗口:发送方根据估算的网络拥塞成都设置的值,反映网络当前容量
慢开始:拥塞窗口cwnd指数式增长,每经过一个RTT乘2,直到达到阈值ssthresh,改用拥塞避免算法
拥塞避免:拥塞窗口cwnd“加法增大”,每经过一个RTT加1。
无论在慢开始阶段还是拥塞避免阶段,只要出现网络拥塞,就把慢开始阈值ssthresh减少为出现拥塞时发送方cwnd值的一半,然后把cwnd设置为1,重新执行慢开始算法。

快重传:当发送方连续收到三个重复的ACK报文时,直接重传对方未收到报文,不必等待报文设置的计时器超时重传
快恢复:收到三个重复的ACK报文时,将慢开始阈值和拥塞窗口都减少为当前拥塞窗口的一半,直接执行拥塞避免算法“加法增大”

拥塞控制的好处
避免网络拥塞:TCP拥塞控制机制可以监测网络中的拥塞情况,并相应地调整发送数据的速率,以避免过多的数据包在网络中造成拥塞。它通过动态调整发送速率和控制窗口大小,以适应网络的负载情况,从而避免了网络拥塞的发生。
当网络发生拥塞时,如果没有拥塞控制机制,大量的数据包会导致网络资源耗尽,甚至崩溃。TCP拥塞控制的作用是在网络出现拥塞时主动控制发送速率,防止过多的数据包进入网络,从而避免了网络的崩溃和系统的不可用。
解释一下TCP的粘包问题
TCP的粘包问题是指在数据传输过程中,多个发送的数据包被合并成一个或者少于预期的数据包的现象。这种情况可能导致接收端无法正确解析数据,造成数据解析错误或者数据丢失。
TCP的粘包问题主要有两种情况:
多个数据包合并成一个
- 发送端连续发送多个小数据包,但是由于网络传输的不确定性,这些数据包在传输过程中可能被合并成一个大的数据包。接收端接收到的数据包可能会包含多个消息,导致消息边界模糊,无法正确解析。
一个数据包被拆分成多个
- 发送端发送一个大的数据包,但是由于网络传输的MTU(最大传输单元)限制或者其他网络因素,这个数据包在传输过程中被拆分成多个小的数据包。接收端接收到的数据包可能只包含部分消息,导致消息不完整或者丢失。

造成TCP粘包问题的主要原因是TCP协议的特性,TCP是一种面向流的协议,数据被视为一个连续的字节流进行传输,而不是一个个独立的消息。因此,在数据传输过程中,TCP并不会考虑消息的边界,而是根据缓冲区的大小和网络状况来进行数据分段和合并,导致出现粘包问题。
为了解决TCP的粘包问题,可以采取以下一些常用的方法:
消息边界标识
在消息中添加特定的边界标识符(如换行符、特殊字符等),接收端根据边界标识符来识别消息的边界,从而正确解析数据。使用像HTTP、SMTP等协议这样的消息边界协议,它们都有自己的消息边界标识规范,可以有效地避免粘包问题。如HTTP协议中的content-length
消息长度字段
- 在消息头部添加一个表示消息长度的字段,接收端首先读取消息长度,然后根据消息长度来读取完整的消息内容,从而避免粘包问题。
固定长度消息
- 将消息固定成固定长度的数据包,不足部分补充填充,接收端根据固定长度来解析数据,保证每个数据包都是完整的消息。
为什么UDP不会产生粘包问题
在报头中有16bit
用于指示 UDP 数据报文的长度,假设这个长度是 n ,以此作为数据边界。因此在接收端的应用层能清晰地将不同的数据报文区分开,从报头开始取 n 位,就是一个完整的数据报,从而避免粘包和拆包的问题。
当然,就算没有这个位(16位 UDP 长度),因为 IP 的头部已经包含了数据的总长度信息,此时如果 IP 包(网络层)里放的数据使用的协议是 UDP(传输层),那么这个总长度其实就包含了 UDP 的头部和 UDP 的数据。
因为 UDP 的头部长度固定为 8 字节( 1 字节= 8 位,8 字节= 64 位,上图中除了数据和选项
以外的部分),那么这样就很容易的算出 UDP 的数据的长度了。因此说 UDP 的长度信息其实是冗余的。
跟 UDP 不同在于,TCP 发送端在发的时候就不保证发的是一个完整的数据报,仅仅看成一连串无结构的字节流,这串字节流在接收端收到时哪怕知道长度也没用,因为它很可能只是某个完整消息的一部分。
为什么长度字段冗余还要加到 UDP 首部中
关于这一点,查了很多资料,《 TCP-IP 详解(卷2)》里说可能是因为要用于计算校验和。也有的说是因为UDP底层使用的可以不是IP协议,毕竟 IP 头里带了总长度,正好可以用于计算 UDP 数据的长度,万一 UDP 的底层不是IP层协议,而是其他网络层协议,就不能继续这么计算了。
但我觉得,最重要的原因是,IP 层是网络层的,而 UDP 是传输层的,到了传输层,数据包就已经不存在IP头信息了,那么此时的UDP数据会被放在 UDP 的 Socket Buffer 中。当应用层来不及取这个 UDP 数据报,那么两个数据报在数据层面其实都是一堆 01 串。此时读取第一个数据报的时候,会先读取到 UDP 头部,如果这时候 UDP 头不含 UDP 长度信息,那么应用层应该取多少数据才算完整的一个数据报呢?
因此 UDP 头的这个长度其实跟 TCP 为了防止粘包而在消息体里加入的边界信息是起一样的作用的