OSI(Open Systems Interconnection)模型
- 应用层
应用层最接近终端用户。大多数应用程序都位于这一层。我们从后端服务器请求数据,无需了解数据传输的具体细节。这一层的协议包括 HTTP、SMTP、FTP、DNS 等。 - 表现层
这一层处理数据编码、加密和压缩,为应用层准备数据。例如,HTTPS 利用 TLS(Transport Layer Security)实现客户端与服务器之间的安全通信。 - 会话层
该层用于打开和关闭两个设备之间的通信。如果数据量较大,会话层就会设置检查点,避免从头开始重新发送。 - 传输层
该层处理两个设备之间的端到端的通信。它在发送方将数据分解成段,然后在接收方重新组装。这一层有流量控制,以防止拥塞。这一层的主要协议是 TCP 和 UDP。 - 网络层
这一层实现不同网络之间的数据传输。它进一步将网段或数据报分解成更小的数据包,并使用 IP 地址找到通往最终目的地的最佳路由。 - 数据链路层
这一层允许在同一网络的设备之间传输数据。数据包被分解成帧,这些帧被限制在局域网内。 - 物理层
这一层通过电缆和交换机发送比特流,因此与设备之间的物理连接密切相关。
与OSI模型相比,TCP/IP 模型只有4层。在讨论网络协议的层次时,必须明确上下文。
OSI 封装
每一层都使用报头来处理指令,而不需要解封上一层的数据。
- 步骤 1:
当设备 A 使用 HTTP 通过网络向设备 B 发送数据时,最初会在应用层添加一个 HTTP 报头。 - 步骤 2:
在数据中添加 TCP 或 UDP 报头。它在传输层被封装成 TCP 段。报头包含源端口、目的端口和序列号。 - 步骤 3:
然后在网络层用 IP 报头对这些段落进行封装。IP 报头包含源 IP 地址和目的 IP 地址。 - 步骤 4:
在数据链路层为 IP 数据报添加 MAC 报头,其中包含源 MAC 地址和目的 MAC 地址。 - 步骤 5:
封装帧被发送到物理层,并作为比特流在网络上发送。 - 步骤 6-10:
设备 B 从网络接收到比特流后,会启动去封装过程,这与封装过程相反。报头逐层去除,直到设备 B 可以访问原始数据。
TCP/IP协议栈
TCP/IP协议栈是一组用于网络通信的协议,它包括物理层、数据链路层、网络层、传输层和应用层。每个层次的协议都提供了不同的功能,例如,网络层协议IP负责将数据包从一个主机传输到另一个主机,而传输层协议TCP提供可靠的数据传输。
IP数据报的报头
IP数据报的报头包含以下字段:
- 版本(Version):指定IP协议的版本,通常为IPv4或IPv6。
- 首部长度(Header Length):指定IP报头的长度,以32位字(4字节)为单位。
- 服务类型(Type of Service):用于指定数据报的服务质量要求,如优先级、延迟、吞吐量等。
- 总长度(Total Length):指定整个IP数据报的长度,包括报头和数据部分。
- 标识(Identification):用于唯一标识一个IP数据报,通常由发送方设置,接收方用于重组分片。
- 标志(Flags):包含3个标志位,分别是DF(Don’t Fragment,不分片)、MF(More Fragments,更多分片)、和保留位。
- 分片偏移(Fragment Offset):用于指示当前分片相对于原始数据报的偏移量,以8字节为单位。
- 生存时间(Time to Live):指定数据报在网络中可以经过的最大路由器跳数,每经过一个路由器,该值减1,为0时数据报被丢弃。
- 协议(Protocol):指定IP数据报中承载的上层协议,如TCP、UDP、ICMP等。
- 头部校验和(Header Checksum):用于检验IP报头的完整性,接收方使用该字段来验证报头是否正确。
- 源IP地址(Source IP Address):指定发送方的IP地址。
- 目标IP地址(Destination IP Address):指定接收方的IP地址。
TCP头格式
- 序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
- 确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。
- 控制位:
- ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。
- RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
- SYN:该位为 1 时,表示希望建立连接,并在其「序列号」的字段进行序列号初始值的设定。
- FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。
IP 层是“不可靠”的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。源地址和目的地址的字段(32 位)是在 IP 头部中。
TCP 是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。总之,TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
- 面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
- 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
- 字节流:用户消息通过 TCP 协议传输时,消息可能会被操作系统「分组」成多个的 TCP 报文,如果接收方的程序如果不知道「消息的边界」,是无法读出一个有效的用户消息的。并且 TCP 报文是「有序的」,当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给应用层去处理,同时对「重复」的 TCP 报文会自动丢弃。
建立一个 TCP 连接是需要客户端与服务端达成上述三个信息的共识。
- Socket:由 IP 地址和端口号组成
- 序列号:用来解决乱序问题等
- 窗口大小:用来做流量控制
TCP 四元组可以唯一的确定一个连接
- 源地址
- 源端口
- 目的地址
- 目的端口
UDP头格式
UDP 不提供复杂的控制机制,利用 IP 提供面向「无连接」的通信服务。UDP 协议头部只有 8 个字节(64 位),UDP 的头部格式如下:
- 目标和源端口:主要是告诉UDP协议应该把报文发给哪个进程。
- 包长度:该字段保存了UDP首部的长度跟数据的长度之和。
- 校验和:校验和是为了提供可靠的UDP首部和数据而设计,防止收到在网络传输中受损的UDP包。
TCP (3-way shake)三次握手建立连接:
- 一开始,客户端和服务端都处于CLOSE状态。先是服务端主动监听某个端口,处于LISTEN状态.
-
客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的 “序列号” 字段中,同时把 SYN 标志位置为 1,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 SYN-SENT 状态。
-
服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的 “序列号” 字段中,其次把 TCP 首部的 “确认应答号” 字段填入 client_isn + 1, 接着把 SYN 和 ACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。
- 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次 “确认应答号” 字段填入 server_isn + 1 ,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于 ESTABLISHED 状态。服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态。
为什么三次握手才可以初始化 Socket、序列号和窗口大小并建立 TCP 连接。
-
首先是为了防止旧的重复连接初始化造成混乱(避免历史连接)。
考虑一个场景,客户端先发送了 SYN(seq = 90)报文,然后客户端宕机了,而且这个 SYN 报文还被网络阻塞了,服务端并没有收到,接着客户端重启后,又重新向服务端建立连接,发送了 SYN(seq = 100)报文(注意!不是重传 SYN,重传的 SYN 的序列号是一样的)。
客户端连续发送多次 SYN(都是同一个四元组)建立连接的报文,在网络拥堵情况下:- 一个 “旧 SYN 报文(seq = 90)”比 “最新的 SYN(seq = 100)” 报文早到达了服务端,那么此时服务端就会回一个 SYN + ACK 报文给客户端,此报文中的确认号是 91(90+1)。
- 客户端收到后,发现自己期望收到的确认号应该是 100 + 1,而不是 90 + 1,于是就会回 RST 报文。
- 服务端收到 RST 报文后,就会释放连接。
- 后面 “最新的 SYN ” 抵达了服务端后,客户端与服务端就可以正常的完成三次握手了。
上述中的 “旧 SYN 报文” 称为历史连接,TCP 使用三次握手建立连接的最主要原因就是防止 “历史连接" 初始化了连接。
如服务端收到客户端报文的顺序是:“旧 SYN 报文” ->“新 SYN 报文”,此时会发生什么:
- 当服务端第一次收到 SYN 报文,也就是收到 “旧 SYN 报文” 时,就会回复 SYN + ACK 报文给客户端,此报文中的确认号是 91(90+1)。
- 然后这时再收到 “新 SYN 报文” 时,就会回 Challenge Ack报文给客户端,这个 ack 报文并不是确认收到 “新 SYN 报文” 的,而是上一次的 ack 确认号,也就是91(90+1)。所以客户端收到此 ACK 报文时,发现自己期望收到的确认号应该是 101,而不是 91,于是就会回 RST 报文。
在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。
-
其次为了同步双方初始序列号
TCP协议的通信双方, 都必须维护一个 “序列号” , 序列号是可靠传输的一个关键因素,作用:- 接收方可以去除重复的数据;
- 接收方可以根据数据包的序列号按序接收;
- 可以标识发送出去的数据包中, 哪些是已经被对方收到的(通过 ACK 报文中的序列号知道);
可见,序列号在TCP连接中占据着非常重要的作用,所以当客户端发送携带 “初始序列号” 的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送 “初始序列号” 给客户端的时候,依然也要得到客户端的应答回应。这样一来一回,才能确保双方的初始序列号能被可靠的同步。
-
避免资源浪费
如果只有 “两次握手”,当客户端发生的 SYN 报文在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的 ACK 报文,所以服务端每收到一个 SYN 就只能先主动建立一个连接。
如果客户端发送的 SYN 报文在网络中阻塞了,重复发送多次 SYN 报文,那么服务端在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
每次建立TCP连接时,初始化的序列号都要求不一样
主要原因有两个方面:
- 主要是为了防止历史报文被下一个相同四元组的连接接收。
- 其次为了安全性,防止黑客伪造的相同序列号的TCP报文被对方接收。
接收历史相同四元组的过程如下:
- 客户端和服务端建立一个TCP连接,在客户端发送数据包被网络阻塞了,然后超时重传了这个数据包,而此时服务端设备断电重启了,之前与客户端建立的连接就消失了,于是在收到客户端的数据包的时候就会发送RST报文。
- 紧接着,客户端又与服务端建立了与上一个连接相同四元组的连接;
- 在新连接建立完成后,上一个连接中被网络阻塞的数据包正好抵达了服务端,刚好该数据包的序列号正好是在服务端的接收窗口内,所以该数据包会被服务端正常接收,就会造成数据错乱。
初始序列号ISN是如何随机产生的?
- 起始ISN是基于时钟的,每4微秒+1,转一圈要4.55个小时。
大量SYN包发送给服务端服务端会怎样
可能会导致TCP半连接队列满,如TCP半连接队列满了,后续再在收到SYN报文就会丢弃,导致客户端无法和服务端建立连接。避免SYN攻击方式,可以有以下四种方法:
- 调大netdev_max_backlog;
- 增大TCP半连接队列;
- 开启tcp_syncookies;
- 减少SYN+ACK重传次数
TCP四次挥手断开
- 客户端主动调用关闭连接的函数,于是就会发送 FIN 报文,这个 FIN 报文代表客户端不会再发送数据了,进入 FIN_WAIT_1 状态;
- 服务端收到了 FIN 报文,然后马上回复一个 ACK 确认报文,此时服务端进入 CLOSE_WAIT 状态。在收到 FIN 报文的时候,TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,服务端应用程序可以通过 read 调用来感知这个 FIN 包,这个 EOF 会被放在已排队等候的其他已接收的数据之后,所以必须要得继续 read 接收缓冲区已接收的数据;
- 接着,当服务端在 read 数据的时候,最后自然就会读到 EOF,接着 read() 就会返回 0,这时服务端应用程序如果有数据要发送的话,就发完数据后才调用关闭连接的函数,如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,这时服务端就会发一个 FIN 包,这个 FIN 报文代表服务端不会再发送数据了,之后处于 LAST_ACK 状态;
- 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
- 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态;
- 客户端经过 2MSL 时间之后,也进入 CLOSE 状态;
每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。
- 第二次挥手丢失
客户端会触发超时重传 FIN 报文,达到最大的重传次数,再等待一段时间,客户端就会断开连接。 - 第三次挥手丢失
服务端重传第三次挥手报文达到最大值,再等待一段时间,服务端就会断开连接。客户端如tcp_fin_timeout 时间内还是没能收到服务端的第三次挥手(FIN 报文),那么客户端就会断开连接。 - 第四次挥手丢失
当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态。持续 2MSL 后才会进入关闭状态。
为什么 TIME_WAIT 等待的时间是 2MSL(MSL 是 Maximum Segment Lifetime,报文最大生存时间)
-
防止历史连接中的数据,被后面相同四元组的连接错误的接收
为了防止历史连接中的数据,被后面相同四元组的连接错误的接收,因此 TCP 设计了 TIME_WAIT 状态,状态会持续 2MSL 时长,这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。 -
保证 “被动关闭连接” 的一方,能被正确的关闭
TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。如果客户端(主动关闭方)最后一次 ACK 报文(第四次挥手)在网络中丢失了,那么按照 TCP 可靠性原则,服务端(被动关闭方)会重发 FIN 报文。
假设客户端没有 TIME_WAIT 状态,而是在发完最后一次回 ACK 报文就直接进入 CLOSE 状态,如果该 ACK 报文丢失了,服务端则重传的 FIN 报文,而这时客户端已经进入到关闭状态了,在收到服务端重传的 FIN 报文后,就会回 RST 报文。服务端收到这个 RST 并将其解释为一个错误(Connection reset by peer),这对于一个可靠的协议来说不是一个优雅的终止方式。
当存在大量close_wait的连接时怎么处理
-
CLOSE_WAIT 状态是(被动关闭方)才会有的状态,而且如果(被动关闭方)没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。
所以,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。
大量TIME_WAIT什么原因
- TIME_WAIT 状态是主动关闭连接方才会出现的状态,所以如果服务器出现大量的 TIME_WAIT 状态的 TCP 连接,就是说明服务器主动断开了很多 TCP 连接。
TCP连接中,客户端和服务器之间操作
- 握手阶段:客户端向服务器发送SYN包(同步包),请求建立连接。服务器收到SYN包后,向客户端发送SYN+ACK包(同步确认包),表示可以建立连接。客户端收到SYN+ACK包后,再向服务器发送ACK包(确认包),表示连接建立成功。
- 数据传输阶段:连接建立成功后,客户端和服务器之间可以进行数据的传输。客户端向服务器发送数据包,服务器接收数据包并进行处理,然后向客户端发送响应包。客户端收到响应包后,可以再次向服务器发送数据包,以此类推。
- 断开连接阶段:当客户端或服务器不再需要连接时,可以发送FIN包(结束包)来请求断开连接。对方收到FIN包后,也发送FIN包进行响应,表示同意断开连接。当两端都收到对方的FIN包后,连接才真正关闭。
TCP如何实现可靠传输
- 序列号与确认应答:TCP将每个发送的数据包进行编号(序列号),接收方通过发送确认应答(ACK)来告知发送方已成功接收到数据。如果发送方在一定时间内未收到确认应答,会进行超时重传。
- 数据校验:TCP使用校验和来验证数据在传输过程中是否发生了损坏。接收方会计算校验和并与发送方发送的校验和进行比较,如果不一致,则说明数据包发生了损坏,需要重新发送。
- 滑动窗口控制:TCP使用滑动窗口机制来控制发送方和接收方之间的数据流量。发送方根据接收方的处理能力和网络状况来调整发送的数据量,接收方则通过窗口大小来告知发送方可以接收的数据量。
- 重传机制:如果发送方未收到确认应答或接收方检测到数据错误,TCP会进行重传。发送方会根据超时时间或接收方的冗余确认来触发重传,以确保数据的可靠传输。
- 拥塞控制:TCP使用拥塞控制算法来避免网络拥塞。通过动态调整发送速率和窗口大小,TCP可以根据网络的拥塞程度来进行适当的调整,以提高网络的利用率和稳定性。
TCP 流量控制、拥塞控制
发送方发数据给接收方,要考虑接收方处理能力,如果接收方对方处理不过来,那么就会导致触发重发机制,从而导致网络流量的无端的浪费。为了解决这种现象发生,
TCP 提供一种机制可以让(发送方)根据(接收方)的实际接收能力控制发送的数据量,这就是所谓的流量控制。
在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环。当网络发送拥塞时,TCP会自我牺牲,降低发送的数据量。于是,就有了拥塞控制,目的就是避免(发送方)的数据填满整个网络。
TCP传输协议中,流量控制是使用**滑动窗口(Sliding Window)**来实现的。滑动窗口是一种基于数据流的、动态调整的、可变大小的窗口,它通过协商双方的接收窗口和发送窗口大小,控制数据的传输速率。
在TCP协议中,每个数据包都有一个序号,接收方通过序号来确认是否收到了正确的数据包。发送方将数据分成若干个数据段,每个数据段的大小不超过发送窗口的大小,然后将这些数据段发送给接收方。接收方会确认已经收到的数据,同时告诉发送方自己的接收窗口大小。发送方根据接收方的窗口大小,动态调整自己的发送窗口大小,从而控制数据的传输速率。
滑动窗口的大小是可以动态调整的,它可以根据网络状况和双方的能力来自适应地调整,从而实现流量控制的功能。如果接收方的接收窗口变小,发送方会相应地减小自己的发送窗口,以避免过多的数据堆积在网络中导致拥塞。如果接收方 的接收窗口变大,发送方会相应地增加自己的发送窗口,以提高数据传输速率。
TCP和UDP的区别
- 连接。TCP 是面向连接的传输层协议,传输数据前先要建立连接。UDP是不需要连接,即刻传输数据。TCP建立和释放连接需要进行三次握手和四次挥手。UDP无需进行三次握手和四次挥手。说明UDP比TCP实时性更强。
- 首部开销。TCP 首部长度较长,会有一定的开销,首部在没有使用(选项)字段时是 20 个字节,如果使用了(选项)字段则会变长的。UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
- 服务对象。TCP 是一对一的两点服务,即一条连接只有两个端点。UDP 支持一对一、一对多、多对多的交互通信
- 可靠性。TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。UDP 是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议,比如 QUIC 协议。
- 拥塞控制、流量控制。TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
- 传输方式。TCP 是流式传输,没有边界,但保证顺序和可靠。UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
- 分片不同。TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。
- 应用场景不同。
TCP是面向连接,能保证数据的可靠性交付,因此经常用于:FTP文件传输;HTTP/HTTPS(网页)、电子邮件、远程登录连接;
UDP面向无连接,随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于包总量较少的通信,如DNS、SNMP等、视频、音频等多媒体通信、广播通信。
为什么UDP头部没有 “首部长度” 字段,而TCP头部有 “首部长度” 字段
- TCP有可变长的 “选项” 字段,而UDP头部长度则是不会变化的,无需多一个字段去记录UDP的首部长度。
为什么UDP头部有 “包长度” 字段,而TCP头部则没有 “包长度” 字段
- TCP的负载数据长度, 可以根据IP总长度、IP首部长度(IP首部格式是已知的)、TCP首部长度求得TCP数据的长度。
TCP粘包问题
TCP的粘包问题是指当发送方将多个数据包连续发送到接收方时,接收方可能将它们看作单个大的数据包。这种问题可能会导致数据丢失或数据不完整。为了避免TCP的粘包问题,可以使用一些技术,如消息边界标记、消息长度标记和消息头标记。
- 固定长度的消息
这种是最简单方法,即每个用户消息都是固定长度的,比如规定一个消息的长度是 64 个字节,当接收方接满 64 个字节,就认为这个内容是一个完整且有效的消息。
但是这种方式灵活性不高,实际中很少用。 - 特殊字符作为边界
我们可以在两个用户消息之间插入一个特殊的字符串,这样接收方在接收数据时,读到了这个特殊字符,就把认为已经读完一个完整的消息。 - 自定义消息结构
我们可以自定义一个消息结构,由包头和数据组成,其中包头包是固定大小的,而且包头里有一个字段来说明紧随其后的数据有多大。
UDP的粘包问题是指当发送方将多个数据包连续发送到接收方时,接收方可能将它们看作单个大的数据包。这种问题可能会导致数据丢失或数据不完整。UDP协议本身并没有提供解决粘包问题的机制,因此应用程序需要自己解决这个问题。解决粘包问题的方法包括使用消息边界标记、消息长度标记和消息头标记等。
TCP重置技术
“伪造RST报文来关闭TCP连接” 的方式叫做TCP重置攻击,伪造RST报文时,要保证伪造的RST报文的序列号能被对方接受。
-
tcpkill工具是在双方进行TCP通信时,拿到对方下一次期望收到的序列号,然后将序列号填充到伪造的RST报文,并将其发送给对方,达到关闭TCP连接的效果。
tcpkill工具属于被动获取,就是在双方进行TCP通信的时候,才能获取到正确的序列号,很显然这种方式无法关闭非活跃的TCP连接,只能用于关闭活跃的TCP连接。因为如果这条TCP连接一直没有任何数据传输,则就永远获取不到正确的序列号。
-
killcx工具是主动发送一个SYN报文,对方收到后会回复一个携带了正确序列号和确认号的ACK报文,这个ACK被称之为ChallengeACK,这时就可以拿到对方下一次期望收到的序列号,然后将序列号填充到伪造的RST报文,并将其发送给对方,达到关闭TCP连接的效果。
killcx工具则是属于主动获取,它是主动发送一个SYN报文,通过对方回复的ChallengeACK来获取正确的序列号,所以这种方式无论TCP连接是否活跃,都可以关闭。
TCP Socket编程
- 服务端和客户端初始化socket,得到文件描述符;
- 服务端调用bind,将socket绑定在指定的IP地址和端口;
- 服务端调用listen,进行监听;
- 服务端调用accept,等待客户端连接;
- 客户端调用connect,向服务端的地址和端口发起连接请求;
- 服务端accept返回用于传输的socket的文件描述符;
- 客户端调用write写入数据;服务端调用read读取数据;
- 客户端断开连接时,会调用close,那么服务端read读取数据的时候,就会读取到了EOF,待处理完数据后,服务端调用close,表示连接关闭。
连接的主要实现步骤:
- 服务器端:socker()建立套接字,绑定(bind)并监听(listen),用accept()
等待客户端连接。 - 客户端:socker()建立套接字,连接(connect)服务器,连接上后使用send()和recv(
),在套接字上写读数据,直至数据交换完毕,close socket()关闭套接字。 - 服务器端:accept()发现有客户端连接,建立一个新的套接字,自身重新开始等待连
接。该新产生的套接字使用send()和recv()写读数据,直至数据交换完毕,closesock
et()关闭套接字。
IP地址及端口问题
-
客户端连接一个不存在的IP地址会怎样
- 目标IP地址和客户端的IP地址是同一个局域网(网络号相同)的情况。
- 客户端无法发出SYN报文,主要卡在数据链路层。
- 因为目标地址不存在IP地址,客户端的内核在发arp请求的时候,广播询问这个目标IP地址是谁的,由于网络中不存在该目标IP地址,所以没有设备应答客户端的arp请求。
- 由于客户端无法拿到目标设备的MAC,这样就没办法组装MAC头的信息,所以SYN报文无法发送出去。
- 目标IP地址和客户端的IP地址不在同一个局域网(网络号不同)情况。
- 客户端会先将SYN报文发给路由器,然后路由器会继续转发。
- 由于目标IP地址是不存在的,该SYN报文会在网络中消亡,因此客户端是不会收到对SYN报文的确认报文的,接着客户端会触发超时重传,重传SYN报文,直到重传的次数达到最大次数后,客户端的连接就会被释放。
- 目标IP地址和客户端的IP地址是同一个局域网(网络号相同)的情况。
-
客户端连接一个存在的IP地址但是端口不存在,会怎样
客户端连接的目标IP地址是存在的,那么SYN报文就能正确的抵达到目标设备。目标设备收到SYN报文后,发现端口号并没有被进程监听,这时候目标设备的内核就会回RST报文。
-
一个IP的服务端监听了一个端口,它的TCP的最大连接数是多少?
服务端通常固定在某个本地端口上监听,等待客户端的连接请求。因此,客户端IP和端口是可变的,其理论值计算公式如下:客户端IP数 * 客户端端口数。
服务端最大并发TCP连接数远不能达到理论上限,会受以下因素影响:
- 文件描述符限制,每个TCP连接都是一个文件,如果文件描述符被占满了,会发生Too many open files。Linux对可打开的文件描述符的数量分别作了三个方面的限制:
系统级:当前系统可打开的最大数量,通过 cat /proc/sys/fs/file-max 查看;
用户级:指定用户可打开的最大数量,通过 cat /etc/security/limits.conf 查看;
进程级:单个进程可打开的最大数量,通过 cat /proc/sys/fs/nr_open 查看; - 内存限制,每个TCP连接都要占用一定内存,操作系统的内存是有限的,如果内存资源被占满后,会发生OOM。
- 文件描述符限制,每个TCP连接都是一个文件,如果文件描述符被占满了,会发生Too many open files。Linux对可打开的文件描述符的数量分别作了三个方面的限制:
-
TCP和UDP可以使用同一个端口吗
在数据链路层中,通过MAC地址来寻找局域网中的主机。
在网际层中,通过IP地址来寻找网络中互连的主机或路由器。
在传输层中,需要通过端口进行寻址,来识别同一计算机中同时通信的不同应用程序。
所以,传输层的「端口号」的作用,是为了区分同一个主机上不同应用程序的数据包。传输层有两个传输协议分别是TCP和UDP,在内核中是两个完全独立的软件模块。
当主机收到数据包后,可以在IP包头的 “协议号” 字段知道该数据包是TCP/UDP,所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理,送给TCP/UDP模块的报文根据 “端口号” 确定送给哪个应用程序处理。
因此,TCP/UDP各自的端口号也相互独立,如TCP有一个80号端口,UDP也可以有一个80号端口,二者并不冲突。 -
客户端的端口可以重复使用吗?
只要客户端连接的不是相同的服务器,内核是允许端口重复使用的。TCP连接由四元组(源IP地址,源端口,目的IP地址,目的端口)唯一确认的,四元组其中任何一个元素改变,就表示不同的TCP连接。
假如客户使用端口1与服务器A建立了连接,客户端也可以使用端口1与服务器B建立连接,即使客户端的端口号相同,但因四元组信息发生变化,并不会导致连接冲突。
-
多个TCP服务进程可以绑定同一个端口吗?
若多个TCP服务进程同时绑定相同的IP地址和端口,那么执行bind()时候就会报错“Addressalreadyinuse”;若TCP服务进程只是绑定相同的端口,但绑定的IP地址不同,那么则不会报错。
IP层会分片,为什么TCP层还需要MSS呢
MTU:一个网络包的最大长度,以太网中一般为1500字节;
MSS:除去IP和TCP头部之后,一个网络包所能容纳的TCP数据的最大长度;
如果在TCP的整个报文(头部+数据)交给IP层进行分片,而IP层本身没有超时重传机制,它由传输层的TCP来负责超时和重传。
当某一个IP分片丢失后,接收方的IP层就无法组装成一个完整的TCP报文(头部+数据),也就无法将数据报文送到TCP层,所以接收方不会响应ACK给发送方,因为发送方迟迟收不到ACK确认报文,所以会触发超时重传,就会重发 “整个TCP报文(头部+数据)” 。因此,由IP层进行分片传输,是非常没有效率的。
所以,为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,当TCP层发现数据超过MSS时,则就先会进行分片,当然由它形成的IP包的长度也就不会大于MTU,自然也就不用IP分片了。
经过TCP层分片后,如果一个TCP分片丢失后,进行重发时也是以MSS为单位,而不用重传所有的分片,大大增加了重传的效率。
HTTP3放弃TCP:
- TCP存在队头阻塞问题———TCP发送出去的数据,都是需要按序确认的,只有在数据都被按顺序确认完后,发送窗口才会往前滑动。如果某个数据报文丢失或者其对应的ACK报文在网络中丢失,会导致发送方无法移动发送窗口,这时就无法再发送新的数据。如客户端发送了第5~9字节的数据,但是第5字节的ACK确认报文在网络中丢失了,那么即使客户端收到第6~9字节的ACK确认报文,发送窗口也不会往前移动。此时的第5字节相当于 “队头” ,因为没有收到 “队头” 的ACK确认报文,导致发送窗口无法往前移动,此时发送方就无法继续发送后面的数据,相当于按下了发送行为的暂停键,这就是发送窗口的队头阻塞问题。
- 接收窗口的队头阻塞————接收窗口收到的数据不是有序的,比如收到第33~40字节的数据,由于第32字节数据没有收到,接收窗口无法向前滑动,那么即使先收到第33~40字节的数据,这些数据也无法被应用层读取的。
两个问题的原因都是因为TCP必须按序处理数据,也就是TCP层为了保证数据的有序性,只有在处理完有序的数据后,滑动窗口才能往前滑动,否则就停留。
NAPT(Network Address Port Transfer , 网络地址端口转换 )工作原理。
-
发送流程
发送端IP地址填的就是192.168.30.5,接收端IP地址就是30.30.30.30。将数据包发到NAT路由器中。此时NAT路由器会将IP数据包里的源IP地址和端口号修改一下,从192.168.30.5:5000改写成20.20.20.20:6000。并且还会在NAT路由器内部留下一条192.168.30.5:5000->20.20.20.20:6000的映射记录。之后数据包经过公网里各个路由器的转发,发到了接收端30.30.30.30:3000,到这里发送流程结束。
-
接收流程
接收端响应时,就会在数据包里填入发送端地址是30.30.30.30:3000,将接收端是20.20.20.20:6000,发往NAT路由器。NAT路由器发现下自己之前留下过这么一条192.168.30.5:5000->20.20.20.20:6000的记录,就会将这个数据包的目的IP地址和端口修改一下,变回原来的192.168.30.5:5000。之后将其转发给你的电脑上。
ping基于ICMP协议,ICMP协议报文里并不带端口信息
- 事实上针对ICMP协议,NAT路由器做了特殊处理。ping报文头里有个Identifier的信息,它其实指的是放出ping命令的进程id。
- 对NAT路由器来说,这个Identifier的作用就跟端口一样,所以可以ping通公网机器并收到回包。
内网穿透
使用了NAT上网的话,前提得内网机器主动请求公网IP,这样NAT才能将内网的IP端口转成外网IP端口。
反过来公网的机器想主动请求内网机器,就会被拦在NAT路由器上,此时由于NAT路由器并没有任何相关的IP端口的映射记录,因此也就不会转发数据给内网里的任何一台机器。
我们就在公网上加一台服务器x,并暴露一个访问域名,再让内网的服务主动连接服务器x,这样NAT路由器上就有对应的映射关系。接着,所有人都去访问服务器x,服务器x将数据转发给内网机器,再原路返回响应,这样数据就都通了。这就是所谓的内网穿透。
上面提到的服务器x,也不需要自己去搭,已经有很多现成的方案,比如花某壳。
常用网络命令
-
route
-
nc 工具
-
netstat 命令
-
IP分配
- 手动在电脑里配IP地址还需要配上子网掩码和路由器的地址。
- DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)。在联网之后可以自动获取到本机需要的IP地址,子网掩码还有路由器地址。
# 在命令行里执行下面的命令,可以强行让电脑的en0网卡重新走一遍DHCP流程。
sudo ipconfig set en0 DHCP
# 在 Linux下,TCP 的连接状态可以通过 netstat -napt 命令查看。
netstat -tunlp # 用于显示所有TCP和UDP端口的监听状态。
lsof -i :端口号 # 用于显示指定端口的相关信息。
# 查看哪个端口被哪个进程占用,可以通过 lsof 或者 netstate 命令查看,比如查看 80 端口。
lsof -i :80
netstat -napt | grep 80
# 查看close_wait状态的连接
netstat -napt | grep close_wait
# sort -k3 -nr用于按第三列(请求耗时)进行倒序排序。
# -k3表示按第三列排序,-n表示按数字排序,-r表示倒序排序。
# 然后,使用head -n 10来获取排序后的前10行,即耗时最高的10条记录。
sort -k3 -nr 日志文件 | head -n 10
# telnet命令用于建立与远程主机的Telnet连接,并可以使用telnet命令测试特定端口的可访问性。
telnet IP地址 端口号 # 用于测试指定IP地址上的指定端口是否可访问。
# 如果能够建立连接,则表示端口通畅;如果连接失败或超时,则表示端口不可访问。
# nc命令(也称为netcat)是一个网络工具,可以用于创建各种类型的网络连接,包括测试端口的可访问性。
nc -zv IP地址 端口号 # 用于测试指定IP地址上的指定端口是否可访问。
#如果能够成功连接,则表示端口通畅;如果连接失败或拒绝,则表示端口不可访问。
常用协议
ARP (Address Resolution Protocol) - Internet物理地址和IP地址转换
ICMP - Internet控制报文协议,处于网络层(IP层)
ARQ - 自动重传请求(Automatic Repeat-reQuest),是 OSI 模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。
ICMP - Internet Control Message Protocol - Internet控制消息协议。文章来源:https://www.toymoban.com/news/detail-840924.html
墙裂推荐网络学习参考
图解网络介绍文章来源地址https://www.toymoban.com/news/detail-840924.html
到了这里,关于网络协议常见问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!