IP层不可靠,不保证网络包的交付,不保证网络包的按序交付,也不保证网络包中的数据的完整性。因为 TCP 是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
建立一个TCP连接是需要客户端与服务端达成上述三个信息共识
TCP连接(Transmission Control Protocol connection)是计算机网络中的一种通信方式,它建立在传输控制协议(TCP)之上。TCP连接是一种面向连接的通信方式,意味着在数据传输之前,通信的两端(通常是客户端和服务器)需要通过一系列握手来建立一个可靠的连接。
源地址、源端口、目的地址、目的端口(地址32位在IP头部,端口16位在TCP头部)
TCP最大连接数=客户端IP数*客户端端口数
TCP(传输控制协议)和UDP(用户数据报协议)是两种常见的传输层协议,它们在网络通信中有着不同的特点和应用场景。
TCP的特点和应用场景:
UDP的特点和应用场景:
分片不同
综上所述,TCP和UDP各有优势,应根据具体应用场景的需求来选择合适的协议。对于重要数据的传输和确保数据可靠性的应用,可以选择TCP。而对于实时性要求较高的应用,可以选择UDP。
答案:可以
**简介回答:**端口号的目的主要是为了区分同一个主机不同应用程序的数据包,传输层有两个传输协议分别是 TCP 和 UDP,在内核中是两个完全独立的软件模块,当主机收到数据包后,可以在 IP 包头的「协议号」字段知道该数据包是 TCP/UDP,所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理,送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理。
**详细回答:**在一般情况下,TCP和UDP是不能共用一个端口的。TCP和UDP是两种不同的传输层协议,它们使用不同的端口来进行通信。每个网络通信应用都使用特定的端口号来与其他应用进行交流。TCP和UDP各自有独立的端口号空间,这意味着一个特定的端口号在TCP和UDP之间可以同时存在,但它们对应的是不同的应用或服务。例如,HTTP通常使用TCP协议在80端口进行通信,DNS通常使用UDP协议在53端口进行通信。这意味着在同一时间,80端口可能被TCP协议的HTTP使用,而53端口可能被UDP协议的DNS使用,二者不会相互干扰。然而,也有一些特殊情况下,某些应用或协议可以同时使用TCP和UDP共享同一个端口。这被称为"TCP/UDP多路复用"或"TCP/UDP Port Sharing"。这种情况下,应用程序需要负责处理接收到的数据,并根据数据包的内容区分使用TCP还是UDP来处理数据。这样的机制通常由应用层协议自行实现,而不是由TCP和UDP协议栈提供的默认功能。总的来说,大多数情况下,TCP和UDP使用独立的端口来避免混淆和冲突。但特定的应用场景下,TCP/UDP多路复用可以实现在同一个端口上同时使用TCP和UDP。
TCP三次握手是建立TCP连接的过程,它用于确保客户端和服务器之间建立可靠的连接。以下是TCP三次握手的过程:
第一次握手(SYN):
第二次握手(SYN+ACK):
第三次握手(ACK):
一旦服务器收到客户端的确认报文段,TCP连接就建立成功,可以开始进行数据传输。在这个过程中,客户端和服务器通过交换序列号和确认号来确保彼此能够正确接收数据,从而建立可靠的连接。第三次握手是可以携带数据的,前两次握手是不可以携带数据的
增强安全性: 选择不同的初始序列号可以增强安全性。如果每次建立连接时初始序列号都是相同的,那么攻击者可以利用这个预测性来发动网络攻击,例如TCP序列号预测攻击。通过选择不同的初始序列号,可以增加攻击者猜测序列号的难度,提高连接的安全性。
防止旧连接的混淆: TCP协议要求,当处于TIME_WAIT状态的连接结束后,必须经过一段时间的等待才能确保网络上的所有数据包都被丢弃。在此期间,如果有新的连接建立并选择了与已结束的旧连接相同的初始序列号,可能会导致新连接收到旧连接的数据包,从而造成混淆。通过选择不同的初始序列号,可以避免这种情况的发生。
起始 ISN
是基于时钟的,每 4 微秒 + 1,转一圈要 4.55 个小时,RFC793 提到初始化序列号 ISN 随机生成算法:ISN = M + F(localhost, localport, remotehost, remoteport)。
M
是一个计时器,这个计时器每隔 4 微秒加 1。F
是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择。当一个TCP分片丢失,重发时以MSS
虽然IP层可以对大于MTU的数据包进行分片,但TCP层的MSS(最大分段大小)参数仍然非常重要,因为TCP层的MSS决定了在TCP连接中每个数据包的最大有效载荷大小,而这对于TCP连接的性能和效率至关重要。下面解释为什么TCP层需要MSS:
1. 避免不必要的分片: TCP层的MSS参数允许TCP在发送数据之前,根据网络的MTU,选择一个适当的最大有效载荷大小。如果TCP层的数据包大小超过了MTU,IP层会对数据包进行分片。分片增加了网络传输的复杂性,也可能导致丢失或乱序的问题。通过设置适当的MSS,TCP可以尽量避免数据包被分片,提高数据传输的效率和可靠性。
2. 控制拥塞窗口: TCP使用拥塞控制算法来避免网络拥塞。MSS是拥塞窗口的一个重要参数。拥塞窗口决定了TCP可以发送的未确认数据量,通过调整MSS,可以控制拥塞窗口的大小,从而适应网络的拥塞状况,防止过多的数据注入导致网络拥塞。
3. 保持连接的可靠性: TCP层的MSS也与TCP连接的可靠性密切相关。较大的MSS可以减少连接的握手次数和数据包数量,从而降低连接建立和维护的开销,提高连接的可靠性。
总的来说,虽然IP层可以进行分片,但TCP层的MSS仍然非常重要,它可以避免不必要的分片,控制拥塞窗口,提高连接的可靠性,从而优化TCP连接的性能和效率。通过合理设置MSS,TCP可以更好地适应不同网络环境,实现高效、可靠的数据传输。
客户端就会触发「超时重传」机制,重传 SYN 报文,而且重传的 SYN 报文的序列号都是一样的。不同操作系统超时时间不同,有的1秒,有的3秒,Linux里客户端SYN报文最大重传次数由(tcp_syn_retries)参数控制,默认值:5。每次超时的时间是上一次的 2 倍
客户端就会触发超时重传机制,重传 SYN 报文。服务端这边会触发超时重传机制,重传 SYN-ACK 报文。在 Linux 下,SYN-ACK 报文的最大重传次数由 tcp_synack_retries
内核参数决定,默认值是 5。
服务端就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。
正常流程:
accpet()
socket 接口,从「 Accept 队列」取出连接对象。不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,默认情况都会丢弃报文。
SYN 攻击方式最直接的表现就会把 TCP 半连接队列打满,这样当 TCP 半连接队列满了,后续再在收到 SYN 报文就会丢弃,导致客户端无法和服务端建立连接。
可以有以下四种方法:
TCP四次挥手是终止TCP连接的过程,用于断开客户端和服务器之间的连接。以下是TCP四次挥手的过程:
第一次挥手(FIN):
第二次挥手(ACK):
第三次挥手(FIN):
第四次挥手(ACK):
注意:在进行四次挥手过程中,客户端和服务器都可以在最后一次挥手前发送数据,因为FIN标志位只表示自己不再发送数据,而并不表示对方不再发送数据。因此,四次挥手可能是"FIN-WAIT-1",“FIN-WAIT-2”,"TIME-WAIT"等状态之间的过程。等待一段时间后,处于TIME-WAIT状态的一方会关闭连接,完成整个四次挥手过程。
FIN
时,仅仅表示客户端不再发送数据了但是还能接收数据。FIN
报文时,先回一个 ACK
应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN
报文给客户端来表示同意现在关闭连接。从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK
和 FIN
一般都会分开发送,因此是需要四次挥手。特定情况下,四次挥手是可以变成三次挥手的
会触发超时重传机制,客户端重传 FIN 报文,重发次数由 tcp_orphan_retries
参数控制。
客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数
服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retrie
s 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的,FIN_WAIT_2状态,默认60秒,有tcp_fin_timeout参数控制
如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries
参数控制
MSL:报文最大生存时间
TTL:经过路由跳数
TTL 的值一般是 64,Linux 将 MSL 设置为 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。
网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间TIME_WAIT等待时间为2倍的MSL(Maximum Segment Lifetime),这个值是为了确保在网络中所有可能的冗余数据包都已经被丢弃,从而保证TCP连接的可靠关闭。
SEQ = 301
报文,被网络延迟了。SEQ = 301
这时抵达了客户端,而且该数据报文的序列号刚好在客户端接收窗口内,因此客户端会正常接收这个数据报文,但是这个数据报文是上一个连接残留下来的,这样就产生数据错乱等严重的问题。32768~61000
,也可以通过 net.ipv4.ip_local_port_range
参数指定范围。打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项;,tcp_tw_reuse 功能只能用客户端(连接发起方),因为开启了该功能,在调用 connect() 函数时,内核会随机找一个 time_wait 状态超过 1 秒的连接给新的连接复用,使用这个选项,还有一个前提,需要打开对 TCP 时间戳的支持
net.ipv4.tcp_max_tw_buckets:这个值默认为 18000,当系统中处于 TIME_WAIT 的连接一旦超过这个值时,系统就会将后面的 TIME_WAIT 连接状态重置,这个方法比较暴力
程序中使用 SO_LINGER ,应用强制使用 RST 关闭。我们可以通过设置 socket 选项,来设置调用 close 关闭连接行为
优化TIME_WAIT状态主要是通过调整TCP连接的参数来减少或优化TIME_WAIT等待时间,以减少资源占用和提高系统性能。以下是一些常见的优化方法:
减少TIME_WAIT等待时间: 在Linux系统中,可以通过修改系统的TCP参数来减少TIME_WAIT等待时间。例如,可以通过修改tcp_fin_timeout
参数来减少TIME_WAIT状态的持续时间。这个参数控制在TCP连接关闭后,套接字保持在TIME_WAIT状态的时间,默认值是60秒。可以根据实际情况适当降低这个值,例如设置为30秒。
重用端口: 可以通过设置SO_REUSEADDR
套接字选项来允许重用处于TIME_WAIT状态的端口。这样可以避免新的连接被拒绝,从而提高系统的连接处理能力。在Go语言中,可以通过设置ReuseAddr
字段为true
来实现端口重用:
listener, err := net.Listen("tcp", "localhost:8080")
if err != nil {
// 错误处理
}
listener.(*net.TCPListener).SetReuseAddr(true)
tcp_tw_recycle
参数来开启快速回收模式。快速回收模式允许将处于TIME_WAIT状态的套接字快速回收,从而释放系统资源。但需要注意的是,快速回收模式可能会导致一些问题,因此在开启前需要仔细评估网络环境和应用需求。# 设置快速回收模式
sudo sysctl -w net.ipv4.tcp_tw_recycle=1
需要注意的是,优化TIME_WAIT状态需要根据具体的系统和网络环境进行调整,并且某些优化方法可能会带来潜在的风险。因此,在进行优化前应该仔细评估系统的性能和稳定性需求,并根据实际情况进行调整。
服务器出现大量TIME_WAIT状态的主要原因是短暂连接的频繁建立和关闭,通常在高并发的网络环境中或者存在大量短连接的场景下会出现这种情况。以下是一些导致服务器大量TIME_WAIT状态的常见原因:
高并发连接: 当服务器面对大量并发连接请求时,每个连接建立后在关闭时会进入TIME_WAIT状态。如果连接的频率很高,TIME_WAIT状态的连接数量就会迅速增加。
短暂连接: 一些应用或协议(如HTTP/1.0),特点是在每个请求完成后立即关闭连接,导致大量短暂连接产生。这些短暂连接会在关闭后进入TIME_WAIT状态,从而积累大量TIME_WAIT连接。
连接重用不当: 如果服务器没有正确地重用连接,而是频繁地创建新的连接,就会导致大量TIME_WAIT状态的连接堆积。在高并发场景下,应尽量复用现有连接,避免频繁创建和关闭连接。
服务器性能不足: 当服务器的性能较低或网络负载很高时,处理连接的速度可能跟不上连接的建立和关闭速度,导致连接在TIME_WAIT状态停留的时间较长。
客户端异常: 如果客户端异常地关闭连接而没有经过正常的四次挥手过程,服务器可能会出现大量处于TIME_WAIT状态的连接,因为服务器没有收到客户端的确认。
为了解决大量TIME_WAIT状态的问题,可以考虑以下方法:
tcp_tw_reuse
和tcp_tw_recycle
等,根据实际情况减少TIME_WAIT等待时间。当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。当服务端出现大量 CLOSE_WAIT 状态的连接的时候,通常都是代码的问题,这时候我们需要针对具体的代码一步一步的进行排查和定位,主要分析的方向就是服务端为什么没有调用 close
Socket编程是一种用于在计算机网络中实现通信的编程方法。它是基于TCP/IP协议栈的一种编程接口,允许程序通过网络在不同计算机之间进行数据传输和通信。
在Socket编程中,程序可以通过创建Socket对象来建立网络连接。Socket对象提供了一组函数(通常是系统调用)来进行数据的发送和接收。通过Socket编程,程序可以实现客户端和服务器之间的通信,实现数据的传输和交换。
Socket编程通常使用两种类型的Socket:
流式Socket(Stream Socket): 使用TCP协议来进行数据传输,提供可靠的、面向连接的通信。流式Socket适用于需要可靠数据传输的场景,如HTTP、FTP等。
数据报Socket(Datagram Socket): 使用UDP协议来进行数据传输,提供不可靠的、无连接的通信。数据报Socket适用于对数据传输时延要求较低,但可靠性要求较低的场景,如视频流传输、实时游戏等。
Socket编程常用于网络编程和分布式系统中,它允许不同计算机上的应用程序之间进行通信和数据交换,实现了跨网络的数据传输和通信功能。在不同编程语言中,都提供了对Socket编程的支持,例如C、C++、Python、Java等。
socket
,得到文件描述符;bind
,将 socket 绑定在指定的 IP 地址和端口;listen
,进行监听;accept
,等待客户端连接;connect
,向服务端的地址和端口发起连接请求;accept
返回用于传输的 socket
的文件描述符;write
写入数据;服务端调用 read
读取数据;close
,那么服务端 read
读取数据的时候,就会读取到了 EOF
,待处理完数据后,服务端调用 close
,表示连接关闭。当涉及到socket编程的面试题时,面试官可能会问一系列问题来评估候选人的网络编程能力。以下是一些常见的socket编程面试题及其答案:
这些问题涵盖了socket编程的一些基本概念、函数调用和网络通信流程。面试时,理解这些问题的答案并能够清晰地解释socket编程的原理和用法,将有助于展示你的网络编程能力和理解水平。
发送数据时,会设定一个定时器,当超过指定的时间后,没有收到对方的ACK确认应答报文,就会重发数据
超时重传的情况
RTT:是数据发送时刻到接受到确认的时刻的差值,也就是包的往返时间
超时重传时间是以 RTO
(Retransmission Timeout 超时重传时间)表示(超时重传时间 RTO 的值应该略大于报文往返 RTT 的值)
不以时间为驱动,而是以数据驱动重传
所以,快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。快速重传机制只解决了一个问题,就是超时时间的问题,但是还有另一个问题,就是重传的时候重传一个,还是所有。于是后面有了SACK
在TCP头部选项字段里加一个SACK的东西,他可以将已收到的数据的信息发送给发送方,这样发送方就知道哪些数据收到了,哪些没有收到,就可以重传丢失的数据
其主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了
好处:
窗口大小:指无需等待确认应答,而可以继续发送数据的最大值
TCP头部有个字段叫Window,也就是窗口的大小,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来,通常窗口的大小是由接受方的窗口大小决定的。
TCP滑动窗口方案使用三个指针来跟踪四个传输类别中的每个类别中的字节。其中两个指针是绝对指针(指确定的序列号),一个是相对指针(需要做偏移)
SND.WND
:表示发送窗口的大小(大小是由接收方指定的)SND.UNA
(Send Unacknoleged):是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节SND.NXT
:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节SND.UNA
指针加上 SND.WND
大小的偏移量,就可以指向 #4 的第一个字节了那么可用窗口大小的计算就可以是:可用窗口大小 = SND.WND -(SND.NXT - SND.UNA)
并不是完全相等,接收窗口的大小是约等于发送窗口的大小的。
因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。
TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。
TCP 流量控制是指通过一系列的机制来确保发送方与接收方之间的数据传输速率适应接收方的处理能力,以防止过多的数据发送导致接收方无法及时处理而发生数据丢失或网络拥塞的情况。
TCP 流量控制的主要机制是通过滑动窗口(Sliding Window)和确认机制来实现的。
总之,TCP 流量控制确保了在发送方和接收方之间的数据传输速率匹配,从而保证了可靠的数据传输,避免了数据丢失和网络拥塞问题。
如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。
那么,当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文,如果这个通告窗口的 ACK 报文在网络中丢失了,那麻烦就大了。这会导致发送方一直等待接收方的非 0 窗口通知,接收方也一直等待发送方的数据,如不采取措施,这种相互等待的过程,会造成了死锁的现象。
为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
窗口探测的次数一般为 3 次,每次大约 30-60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST
报文来中断连接。
尽管TCP已经引入了流量控制机制来确保发送方不会以过快的速率向接收方发送数据,但流量控制仅仅关注于发送方和接收方之间的数据传输。然而,网络中的其他因素,如网络拥塞、延迟、丢包等问题,可能会影响整体的网络性能和吞吐量。这就是为什么TCP还需要引入拥塞控制机制的原因。
拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。
我们在前面提到过发送窗口 swnd
和接收窗口 rwnd
是约等于的关系,那么由于加入了拥塞窗口的概念后,此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。
拥塞窗口 cwnd
变化的规则:
cwnd
就会增大;cwnd
就减少;只要「发送方」没有在规定时间内接收到 ACK 应答报文,当发生了超时重传,就会认为网络出了拥塞
TCP刚开始建立连接,一点一点发送数据包,当发送方每收到一个ACK,拥塞控制cwnd的大小就会加1
有一个叫慢启动门限 ssthresh
(slow start threshold)状态变量。
cwnd
< ssthresh
时,使用慢启动算法。cwnd
>= ssthresh
时,就会使用「拥塞避免算法」。一般来说 ssthresh
的大小是 65535
字节。
那么进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd。
我们可以发现,拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,还是增长阶段,但是增长速度缓慢了一些。就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。当触发了重传机制,也就进入了「拥塞发生算法」。
当发生了「超时重传」,则就会使用拥塞发生算法。这个时候,ssthresh 和 cwnd 的值会发生变化:
ssthresh
设为 cwnd/2
,cwnd
重置为 1
(是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)当发生了「快速重传」,则就会使用拥塞发生算法。这个时候,ssthresh 和 cwnd 的值会发生变化:
cwnd = cwnd/2
,也就是设置为原来的一半;ssthresh = cwnd
;进入快速恢复算法如下:
cwnd = ssthresh + 3
( 3 的意思是确认有 3 个数据包被收到了);在理解TCP半连接队列(SYN队列)和全连接队列(Accept队列)之前,让我们先了解TCP的三次握手过程:
客户端发送SYN(同步): 客户端向服务器发送一个SYN包,表示客户端想要建立连接。
服务器发送SYN + ACK: 服务器收到客户端的SYN包后,会发送一个带有ACK确认号的SYN包作为响应,表示接受连接请求并准备好建立连接。
客户端发送ACK: 客户端收到服务器的SYN + ACK包后,发送一个ACK包,表示确认服务器的响应。此时连接建立完成。
现在,我们可以来看看半连接队列和全连接队列:
半连接队列(SYN队列): 在TCP的三次握手过程中,当服务器发送了SYN + ACK包后,它会等待客户端发送最终的ACK包以完成连接的建立。在这个等待期间,服务器将客户端的连接请求(即半连接)放入半连接队列,等待客户端的最终确认。半连接队列的大小通常是有限的,操作系统根据资源和配置来决定。
全连接队列(Accept队列): 一旦连接的三次握手完成,服务器将连接从半连接队列移至全连接队列。全连接队列中的连接已经建立并且可以开始进行数据传输。服务器将按顺序从全连接队列中选择连接,并分配资源来处理这些连接。
总结起来,半连接队列(SYN队列)用于存放已经发送了SYN请求但尚未完成三次握手的连接,而全连接队列(Accept队列)则存放已经完成三次握手的连接,等待服务器分配资源来处理。这些队列在TCP服务器中起着重要的作用,帮助管理并发连接并维护网络性能。