从数据包到可靠性:UDP/TCP协议的工作原理分析
之前我们已经使用udp/tcp的相关接口写了一些简单的客户端与服务端代码。也了解了协议是什么,包括自定义协议和知名协议比如http/https和ssh等。现在我们再回到传输层,对udp和tcp这两传输层巨头协议做更深一步的分析。
一.UDP
UDP相关内容很简单,因为它不保证可靠性,面向数据报的特性。常用于直播和游戏等场景,所以我们分析也会简单些。
1.1UDP协议端格式
UDP报文的格式非常简单。16位源/目的端口号,这个不多介绍。16位UDP长度表示整个数据报(UDP 首部+UDP 数据)的最大长度,16位检验和则是确认UDP报文是否正确,如果错误直接丢弃。所以我们说UDP是面向数据报的,本身也不会产生粘包的问题。
1.2UDP的特点
UDP 传输的过程类似于寄信,也就是说,我们自己(应用层)吧信(数据报)寄出去后。邮局派人(UDP)将我们的信封放到我们事先说好的位置(目的端口号)后,如果说信封在邮箱里面别人取错了。原来要收我们信件的人此时就收不到信了。但邮局不管,想要对方收到信我们只能自己重发一次再邮寄出去。
所以我们可以总结UDP如下的几个特点:
- 无连接: 知道对端的 IP 和端口号就直接进行传输, 不需要建立连接;
- 不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方,UDP 协议层也不会给应用层返回任何错误信息;
- 面向数据报: 不能够灵活的控制读写数据的次数和数量;
1.3面向数据报
我们可以这样理解,应用层交给 UDP 多长的报文, UDP 原样发送, 既不会拆分, 也不会合并;用 UDP 传输 100 个字节的数据。也就是说如果发送端调用一次 sendto, 发送 100 个字节, 那么接收端也必须调用对应的一次 recvfrom, 接收 100 个字节; 而不能循环调用 10 次 recvfrom, 每次接收 10 个字节。
1.4UDP的缓冲区
- UDP 没有真正意义上的 发送缓冲区. 调用 sendto 会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;
- UDP 具有接收缓冲区. 但是这个接收缓冲区不能保证收到的 UDP 报的顺序和发送 UDP 报的顺序一致; 如果缓冲区满了, 再到达的 UDP 数据就会被丢弃;
所以我们此时就明白了为什么UDP是全双工的,因为UDP的socket 既能读,也能写。
1.5UDP使用时的注意事项
我们注意到, UDP 协议首部中有一个 16 位的最大长度. 也就是说一个 UDP 能传输的数据最大长度是 64K(包含 UDP 首部).然而 64K 在当今的互联网环境下, 是一个非常小的数字.如果我们需要传输的数据超过 64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装;
基于UDP知名的应用层协议有:NFS: 网络文件系统,TFTP: 简单文件传输协议,DHCP: 动态主机配置协议,BOOTP: 启动协议(用于无盘设备启动),DNS: 域名解析协议等等。
二.TCP
在分析TCP之前,博主想多提一嘴。我们经常说UDP比TCP快,但博主自己认为这是不太对的。快也是要就场景的不同而言的,有的场景下TCP还比UDP快呢。所以我们不谈UDP和TCP谁快谁慢的问题。归根结底, TCP 和 UDP 都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定。
2.1TCP协议与其协议端格式
TCP 全称为 "传输控制协议(Transmission Control Protocol"). 人如其名, 要对数据的传输进行一个详细的控制。
- 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去。
- 序号与确认信号:个人认为是TCP中最为重要的部分,我们后面结合场景细致分析。
- 4位TCP首部长度:表示TCP报头长度大小。首先4位bits可以表示0~15的大小。因为我们的每个选项都是4字节的,所以0~15的每一个单位表示4字节大小。也就是说TCP报头实际长度为:4位首部长度*4(字节)。又因为选项最多有10个,最少为0个。所以TCP的4位首部长度取值范围为5~15(20字节大小~60字节大小)。
- 6位保留位:是预留给未来扩展使用的字段,当前协议(RFC 793及后续标准)中并未定义其具体用途。它们的默认值为全0,发送方必须将这些位设置为0,接收方则会忽略其值。
- 6位标志位: URG(确认紧急指针是否有效),ACK(表示当前报文中的确认序号是否有效),PSH(催促对端应用程序尽快把数据从缓冲区读走),RST(表示对方请求重新连接,也就是重新进行三次握手过程),SYN(对端请求建立连接; 我们把携带 SYN 标识的称为同步报文段),FIN(对端请求断开连接,我们称携带 FIN 标识的为结束报文段)。
- 16位窗口大小:表示发送当前报文的一方TCP缓冲区的剩余大小,后面我们还会再提到它。
- 16位校验和:与UDP一样,确认报文的合法性,错误了直接丢弃。由发送端填充, CRC 校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含 TCP 首部, 也包含 TCP 数据部分。
- 16位紧急指针:标识那部分是紧急数据,位置为报头首部地址+紧急指针大小。其实也就是记录了紧急数据位置相对于报头首部地址的偏移量。
- 40字节头部选项:暂时不提。
那么TCP是如何保证其传输的可靠性的呢,有什么优化传输速度的机制吗?我们从下面几个TCP的核心机制来分析。
2.2确认应答机制
TCP的确认应答机制是TCP可靠性保证的基石。因为我们最终的目的是要让对方收到我发出的数据,怎么能确认100%收到对方发送的数据呢。也就是确认应答机制,但这里说的确认是对历史数据的100%确认,我们下面细说:
我们以C->S一个传输方向为例:
(多提一嘴,为什么我们画箭头是斜着画的而不是平直的呢,这是因为报文到达对端需要一定的时间,斜着画能体现出这种时间间隔)。
当客户端发送数据给对端时,对方会发送一个携带有确认应答号的报文(此时报文中的ACK置为1)。那我们此时就能够确定我们历史上发给服务端的1000序号以前的数据都被服务端收到了。那服务端如何保证自己的应应答报文被客户端收到了呢?
客户端也发一个ACK回去吗,显然这又成了一个鸡生蛋,蛋生鸡的问题。所以我们的策略是:不对应答做应答,使用超时重传机制来让服务端确定对端是否收到应答报文。
2.3超时重传机制
我们先说数据丢失的情况:
主机 A 发送数据给 B 之后, 可能因为网络拥堵等原因, 数据无法到达主机 B,如果主机 A 在一个特定时间间隔内没有收到 B 发来的确认应答, 就会进行重发。
再说我们之前提到的ACK丢失的情况:
因此主机 B 会收到很多重复数据。那么 TCP 协议需要能够识别出那些包是重复的包,并且把重复的丢弃掉,这时候我们可以利用前面提到的序列号,就可以很容易做到去重的效果。
那么这个超时的时间大小如何确定呢?最理想的情况下, 找到一个最小的时间, 保证 "确认应答一定能在这个时间内返回".但是这个时间的长短, 随着网络环境的不同, 是有差异的.如果超时时间设的太长, 会影响整体的重传效率;如果超时时间设的太短, 有可能会频繁发送重复的包;
TCP 为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间。具体的过程如下:
- Linux 中(BSD Unix 和 Windows 也是如此), 超时以 500ms 为一个单位进行控制, 每次判定超时重发的超时时间都是 500ms 的整数倍.
- 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.
- 如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.
- 累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接.
2.4连接管理机制
在正常情况下, TCP 要经过三次握手建立连接, 四次挥手断开连接:
由于TCP中通信双方的地位是对等的,所以我们只分析客户端主动向服务端发起连接/断开请求的情况。服务端主动的情况是一样的。
2.4.1三次握手过程
客户端流程:connect()
→ 发送SYN
→ SYN_SENT
→ 收到SYN+ACK
→ 发送ACK
→ ESTABLISHED。
服务端流程:listen()
→ 收到SYN
→ 发送SYN+ACK
→ SYN_RCVD
→ 收到ACK
→ ESTABLISHED
→ accept()
返回confd
。
为什么要进行三次握手,因为通信必须要双方同意才能进行。就比如结婚,一方如果不同意怎么结吗。其次,此过程中双方也进行了数据序号起始位置的交换,同时也以最短的方式验证了双方的全双工能力是否正常。(因为此时双方都能进行正常的接收发送)
其次我们发现ACK其实在传输过程中在双方的报文99%的情况下是置为1的,什么时候不置为1呢,一个是真失效了,还有一个就是主动发起连接的一方首次发送SYN给对端请求建立连接时。
还有几个细节问题,第一个既然要双方同意,不应该是4次握手吗,为什么是三次握手。因为服务端一般对客户端的请求是无条件同意的(这个我们下面说四次挥手时就明白了)。
第二个,TCP为了数据传输的高效性,通常会在应答报文中捎带上我当前端要发的数据。那么这个捎带数据不可能在前两次握手中出现。也就是说前两次握手时,双方发给对端的报文只有报头没有数据。因为此时连接并没有建立,不能发送数据给对方。
2.4.2四次挥手过程
因为双方断开连接,需要双方均同意才会断开连接。当主动断开方发送的FIN请求到达对端时,对端此时进入半关闭状态(如果想要看到这个状态可以把服务端代码中的close(fd)删除即可)CLOSE_WAIT。并发送ACK到主动断开方,主动断开方收到ACK后进入FIN_WAIT_2状态。
过了一段时间,服务端想要与客户端断开连接(调用close(fd)),发送FIN请求到客户端,客户端收到服务端断开连接请求后进入TIME_WAIT状态,并发送ACK报文给对方。客户端等待一段时间后进入CLOSED状态。服务端收到ACK后当前通信文件立马进入CLOSED状态。
TIME_WAIT状态
为什么主动断开连接的一方要等待一段时间呢?因为有的时候,对端在发送完FIN请求后。TIME_WAIT这段时间内有可能收到之前因为网络问题此时才到的对端之前发送的报文(因为我们的报文为了效率是并发的而不是上面那样一个一个发的,下面我们会说这个问题,此处先了解即可)。所以他的第一个作用为处理过期报文。
第二个作用,确保发送的ACK到达对端。因为超时重传机制,对端没有收到我发回的ACK时会重发FIN请求。收到了就不发了。也就是说在TIME_WAIT时间内,只要客户端没有收到消息,我客户端就认为对方收到了我的ACK应答。
唉,这时候就有人要钻牛角尖了。那如果更为极端的一种情况,过了TIME_WAIT时间后,再使用原来的端口号绑定,这个过期报文此时到达了,此时怎么办。
-
需要同时满足以下极不可能的条件:
-
报文在网络中存活超过MSL(违反IP协议规范)
-
新连接的五元组完全匹配旧连接
-
新连接的初始序列号恰好与旧报文匹配
-
-
现代TCP实现会使用随机化初始序列号(ISN),使得这种碰撞概率约为1/4 billion
所以基本上不太可能,有了我们也不考虑。因为工程上不会为了 “理论上可能,但实际几乎不会发生” 的情况增加过多复杂度。
那么TIME_WAIT等待的时间是多长呢,为什么?
TIME_WAIT 的时间是 2MSL,MSL 在 RFC1122 中规定为两分钟,但是各操作系统的实现不同, 在 linux 上默认配置的值是 60s。可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看 msl 的值。
MSL 是 TCP 报文的最大生存时间, 因此 TIME_WAIT 持续存在 2MSL 的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);同时也是在理论上保证最后一个报文可靠到达(假设最后一个 ACK 丢失, 那么服务器会再重发一个 FIN. 这时虽然客户端的进程不在了, 但是 TCP 连接还在, 仍然可以重发 LAST_ACK)。
bind_error的原因
因为主动断开连接的一方会进入TIME_WAIT状态。所以原来绑定的端口号在这段时间内仍然是被占用的。但是1分钟内不允许重新绑定,有些不太合理。比如说即将到来的618,如果在618期间我们就说tb,tb的服务器在此期间发生了服务器崩溃,重启要等上一分钟,但对于人家的服务器来说,分分钟几百万上下啊。所以这种情况下我们就可以在程序内设置TIME_WAIT的时间。
使用 setsockopt()设置 socket 描述符的 选项 SO_REUSEADDR 为 1, 表示允许创建端口号相同但 IP 地址不同的多个socket描述符。此处 opt=1
表示启用 SO_REUSEADDR。

2.5滑动窗口机制
刚才我们讨论了确认应答策略, 对每一个发送的数据段, 都要给一个 ACK 确认应答. 收到 ACK 后再发送下一个数据段. 这样做有一个比较大的缺点, 就是性能较差. 尤其是数据往返的时间较长的时候。既然这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)。
那么每次并行发送的数据量为多少,丢包了怎么办。应答报文丢了怎么办。TCP中的滑窗机制正是解决这些问题的存在。
先来说窗口大小的问题。还记得上面说的TCP报文里面的16位窗口大小吗?唉,那我们滑窗的大小根据对方剩余接收缓冲区大小来动态变化,大的时候我窗口大些发快些,小的时候反之。也就是说,滑窗的大小是由对方的接收缓冲区剩余空间大小决定的。(这里我们为了方便理解暂时这样说,后面说到拥塞机制了再补充)。这其实也正是TCP的流量控制机制。
再来说丢包丢应答的问题:
我们把要发送的数据抽象化为一个一维的数组:
我们一般以滑窗的位置对数据进行划分。start左边的我们称为已经发出并且确认收到的数据。start-end之间的我们称为已经发出但未确认对方收到的数据。end右边的便是未发出的数据。
所以我们先说丢包的情况,可以分为三种:最左端数据包丢失,中间丢失,右端丢失。实际情况一般是上面三种情况的组合。我们对最左端数据包丢失进行讨论,如果最左端数据包丢失,那么我们发出数据包的一段会收到多个相同的确认序号应答。比如以上图为例,会收到多个2001。此时我们客户端就可以确定100%我们最左端的数据包丢失了,此时我们对该位置的数据包重发即可。(我们也称这种机制为快重传)。
那中间,最右端以及混合情况呢。比如在中间,那我们收到报文的确认序号就可以判断中间那个位置100%数据丢失,对其进行重发同时start指针移动到该位置即可。我们发现,最终无论是哪种情况,都会转化为最左端数据包丢失的情况。
同样的我们说应答丢失的情况,如果说最左端,中间应答丢失了,我们不管,为什么?因为最右端的数据发出后的确认应答就可以帮助我们确认前面没有数据丢失,如果后面一连串的应答都丢失了?过了超时重传时间我们客户端就认为数据包丢失了,于是又回到了上面丢包时候的情况了。
传数据时应答序号一直增加,会溢出吗?
当然不会,通过环形数组的思想便可以轻松解决这个问题。每次取收数据时start与end对整个缓冲区大小取模不就可以了。
2.6拥塞控制机制
虽然 TCP 有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的.
TCP 引入慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;
此处引入一个概念称为拥塞窗口。发送开始的时候, 定义拥塞窗口大小为 1;每次收到一个 ACK 应答, 拥塞窗口加 1;每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;也就是说,实际上我们的窗口大小是取拥塞窗口和接收端主机的反馈窗口的较小值。
为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.此处引入一个叫做慢启动的阈值,当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长:
当 TCP 开始启动的时候, 慢启动阈值等于窗口最大值;
在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回 1;少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;当 TCP 通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;拥塞控制, 归根结底是 TCP 协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案。但这个中折的几乎完美,所以我们说指数增长的方法极为巧妙。
那么线性增长会有上限吗,因为网络稳定时他会一直增长,答案是有的。 线性增长到最后最终由接收方窗口、网络容量(BDP)或算法自身的收敛机制限制。毕竟网络稳定的时候再增长本身这件事情也就失去了意义。
2.7延迟应答机制
如果接收数据的主机立刻返回 ACK 应答, 这时候返回的窗口可能比较小.
假设接收端缓冲区为 1M. 一次收到了 500K 的数据; 如果立刻应答, 返回的窗口就是 500K;但实际上可能处理端处理的速度很快, 10ms 之内就把 500K 数据从缓冲区消费掉了;在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;如果接收端稍微等一会再应答, 比如等待 200ms 再应答, 那么这个时候返回的窗口大小就是 1M;一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
那么所有的包都可以延迟应答么? 肯定也不是;数量限制: 每隔 N 个包就应答一次;时间限制: 超过最大延迟时间就应答一次;具体的数量和超时时间, 依操作系统不同也有差异; 一般 N 取 2, 超时时间取 200ms;
2.8捎带应答机制
在延迟应答的基础上,我们发现, 很多情况下, 客户端服务器在应用层也是 "一发一收"的. 意味着客户端给服务器说了 "How are you", 服务器也会给客户端回一个 "Fine, thank you";那么这个时候 ACK 就可以搭顺风车, 和服务器回应的 "Fine, thank you" 一起回给客户端。
其他的面向字节流,粘包等问题,我们之前编写简答服务器时已经有结合场景介绍过,这里不再过多介绍。
2.9TCP异常情况
- 进程终止: 进程终止会释放文件描述符, 仍然可以发送 FIN. 和正常关闭没有什么区别.
- 机器重启: 和进程终止的情况相同.
- 机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行 reset. 即使没有写入操作, TCP 自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.
- 另外, 应用层的某些协议, 也有一些这样的检测机制. 例如 HTTP 长连接中, 也会定期检测对方的状态. 例如 QQ, 在 QQ 断线之后, 也会定期尝试重新连接.
2.10小结
为什么 TCP 这么复杂? 因为要保证可靠性, 同时又尽可能的提高性能。
可靠性:
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
其他:
- 定时器(超时重传定时器, 保活定时器, TIME_WAIT 定时器等)
基于 TCP 应用层的知名协议有HTTP,HTTPS,SSH,Telnet,FTP,SMTP等,有兴趣的读者可以自行下去了解这些协议。
相关文章:
从数据包到可靠性:UDP/TCP协议的工作原理分析
之前我们已经使用udp/tcp的相关接口写了一些简单的客户端与服务端代码。也了解了协议是什么,包括自定义协议和知名协议比如http/https和ssh等。现在我们再回到传输层,对udp和tcp这两传输层巨头协议做更深一步的分析。 一.UDP UDP相关内容很简单…...
Prometheus实战教程:k8s平台-Mysql监控案例
配置文件优化后的 Prometheus 自动发现 MySQL 实例的完整 YAML 文件。该配置包括: MySQL Exporter 部署:使用 ConfigMap 提供 MySQL 连接信息。Prometheus 自动发现:通过 Kubernetes 服务发现自动抓取 MySQL 实例。 1、mysql 配置文件 &…...
执行apt-get update 报错ModuleNotFoundError: No module named ‘apt_pkg‘的解决方案汇总
Ubuntu版本ubuntu18.04 报错内容: //执行apt-get upgrade报错: Traceback :File “/usr/lib/cnf-update-db”, line 8, in <module>from CommandNotFound.db.creator import DbcreatorFile “/usr/lib/python3/dist-packages/CommandNotFound/db…...
QT6 源(101)篇一:阅读与注释 QPlainTextEdit,其继承于QAbstractScrollArea,属性学习与测试
(1) (2) (3)属性学习与测试 : (4) (5) 谢谢...
Redis(2):Redis + Lua为什么可以实现原子性
Redis 作为一款高性能的键值对存储数据库,与 Lua 脚本相结合,为实现原子性操作提供了强大的解决方案,本文将深入探讨 Redis Lua 实现原子性的相关知识 原子性概念的厘清 在探讨 Redis Lua 的原子性之前,我们需要明确原子性的概念…...
ios打包ipa获取证书和打包创建经验分享
在云打包或本地打包ios应用,打包成ipa格式的app文件的过程中,私钥证书和profile文件是必须的。 其实打包的过程并不难,因为像hbuilderx这些打包工具,只要你输入的是正确的证书,打包就肯定会成功。因此,证书…...
Python生成器:高效处理大数据的秘密武器
生成器概述 生成器是 Python 中的一种特殊迭代器,通过普通函数的语法实现,但使用 yield 语句返回数据。生成器自动实现了 __iter__() 和 __next__() 方法,因此可以直接用于迭代。生成器的核心特点是延迟计算(lazy evaluation&…...
C++11(2)
文章目录 右值引用和移动语义在传参中的提效list容器push_back & insert右值版本的模拟实现类型分类 (了解即可)引用折叠万能引用 完美转发(跟引用折叠有关) 简介:这篇文章是继续介绍C11的一些新语法知识点,也是对…...
unity terrain 在生成草,树,石头等地形障碍的时候,无法触发碰撞导致人物穿过模型
1.terrain地形的草,石头之类要选择模型预制体 2.在人物身上挂碰撞器和刚体,或者单挂一个character controller组件也行 3.在预制体上挂碰撞盒就好了,挂载meshcollider会导致碰撞无效...
以项目的方式学QT开发C++(二)——超详细讲解(120000多字详细讲解,涵盖qt大量知识)逐步更新!
API 描述 函数原型 参数说明 push_back() 在 list 尾部 添加一个元素 void push_back(const T& value); value :要添 加到尾部的元 素 这个示例演示了如何创建 std::list 容器,并对其进行插入、删除和迭代操作。在实际应用中&am…...
养生:健康生活的极简攻略
在追求高效生活的当下,养生也能化繁为简。通过饮食、运动、睡眠与心态的精准调节,就能轻松为健康续航。 饮食上,遵循 “均衡、节制” 原则。早餐用一杯热豆浆搭配水煮蛋和半个苹果,唤醒肠胃活力;午餐以糙米饭为主食&am…...
C语言-8.数组
8.1数组 8.1.1初试数组 如何写一个程序计算用户输入的数字的平均数? #include<stdio.h> int main() {int digit;//输入要求平均数的数字double sum=0;//记录输入数字的和int count=0;//记录输入数字的个数printf("请输入一组数字,用来求平均数,以-1结束\n&quo…...
代码随想录算法训练营第四十一天
LeetCode题目: 739. 每日温度496. 下一个更大元素 I503. 下一个更大元素 II 其他: 今日总结 往期打卡 739. 每日温度 跳转: 739. 每日温度 学习: 代码随想录公开讲解 问题: 给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer &…...
c++,windows,多线程编程详细介绍
目录 一、C11 标准库 <thread> 实现多线程编程1. 基本线程创建2. 线程管理3. 线程传参4. 同步机制5. 异步编程 二、Windows API 实现多线程编程1. 基本线程创建2. 线程管理3. 线程传参 三、两种方法的对比 在 Windows 平台上使用 C 进行多线程编程,可以通过 C…...
Python多线程
Python多线程 作为一名Python开发者,你是否遇到过这样的场景:程序需要同时处理多个任务,但单线程执行效率太低?这时候,多线程技术就能派上用场了。本文将带你深入浅出地理解Python多线程,并通过丰富的示例…...
VisionPro斑点寻找工具Blob
斑点寻找工具Blob 斑点概述 斑点分析 探测并且分析图像中的二维形状Blob是先根据用户设定好的灰阶范围对图像进行分割,然后对目标进行查找和分析。斑点报告多种属性: 面积质心周长主轴…….. 应用场景 Blob分析非常适合以下场合的应用: 对…...
【Python】【面试凉经】Fastapi为什么Fast
核心的关键词:ASGI、原生异步、协程、uvloop、异步生态、Pydantic编译时生成校验代码、DI system预计算依赖树 interviewer 00:32:49 FastAPI 它优越于其他一些主流web框架像 django或 flask 的这个点在哪里? 我 00:33:00fastapi 就是说它的 fast 性能高…...
LocalDateTime类型的时间在前端页面不显示或者修改数据时因为LocalDateTime导致无法修改,解决方案
1.数据库中的时间数据,在控制台可以正常返回,在前端无法返回,即显示空白,如下图所示: 2.这种问题一般时由于数据库和我们实体类的名称不一致引起的,我们数据库一般采用_的方式命名,但是在Java中我们一般采用…...
【Linux】gcc从源码编译安装,修改源码,验证修改的源码
前阵子电脑使用的win10,win10过几天就让升级,烦得不行。 然后把操作系统切换到ubuntu24的样子,然后也是让升级,又烦的不行,然后切换到ubuntu server版本,感觉用起来要舒服些了,至少不会天天让升级。 回到标…...
牛客网NC22157:牛牛学数列2
牛客网NC22157:牛牛学数列2 📝 题目描述 🔍 输入输出说明 输入描述: 输入一个整数 N,范围在 0 到 1000 输出描述: 输出一个保留6位小数的浮点数 示例: 输入:2输出:1.500000 …...
智能手表集成测试报告(Integration Test Report)
📄 智能手表集成测试报告(Integration Test Report) 项目名称:Aurora Watch S1 测试阶段:系统集成测试 测试周期:2025年xx月xx日 – 2025年xx月xx日 报告编号:AW-S1-ITR-2025-001 版本…...
1C:ENTERPRISE 8.3 实用开发者指南-示例和标准技术(Session1-Session3)
1C:ENTERPRISE 8.3(1课-3课) 本博客是全网首个关于1C:Enterprice的中文指南,支持快速吸收使用 1C:Enterprise 8.3 软件开发和调整应用程序的技术 在这篇博客中我会基于实际应用示例,演示各种系统对象的结构、功能和用法。使用内…...
AgenticSeek开源的完全本地的 Manus AI。无需 API,享受一个自主代理,它可以思考、浏览 Web 和编码,只需支付电费。
一、软件介绍 文末提供程序和源码下载 AgenticSeek开源的完全本地的 Manus AI。无需 API,享受一个自主代理,它可以思考、浏览 Web 和编码,只需支付电费。这款支持语音的 AI 助手是 Manus AI 的 100% 本地替代品 ,可自主浏览网页…...
Java类一文分解:JavaBean,工具类,测试类的深度剖析
解锁Java类的神秘面纱:从JavaBean到测试类的深度剖析 前言一、JavaBean 类:数据的守护者(一)JavaBean 类是什么(二)JavaBean 类的特征(三)JavaBean 类的使用场景(四&…...
2025认证杯数学建模第二阶段C题:化工厂生产流程的预测和控制,思路+模型+代码
2025认证杯数学建模第二阶段思路模型代码,详细内容见文末名片 一、探秘化工世界:问题背景大揭秘 在 2025 年 “认证杯”数学中国数学建模网络挑战赛第二阶段 C 题中,我们一头扎进了神秘又复杂的化工厂生产流程预测与控制领域。想象一下&…...
day 17 无监督学习之聚类算法
一、聚类流程 1. 利用聚类发现数据模式 无监督算法中的聚类,目的就是将数据点划分成不同的组或 “簇”,使得同一簇内的数据点相似度较高,而不同簇的数据点相似度较低,从而发现数据中隐藏的模式。 2. 对聚类后的类别特征进行可视…...
渗透测试流程-上篇
#作者:允砸儿 #日期:乙巳青蛇年 四月十八 本期就开始进入到网安的内容了笔者会和大家一起开始实操练习。在此之前笔者的老师和我说要知己知彼,胆大心细。笔者也把他的理念传出去,网安的知识比较复杂且使用的工具很多。笔者看过…...
Ubuntu离线安装Minio
MinIO 支持在 Linux 环境下离线安装,非常适合内网或无法联网的服务器环境。下面是详细的 Linux 离线安装 MinIO 服务端 的步骤: ✅ 一、准备工作 1. 创建安装目录(可选) mkdir -p /opt/minio cd /opt/minio2. 下载 MinIO 可执行…...
2025年山东省数学建模F题思路
2025年山东省数学建模F题思路 一、问题背景 在现代金融市场中,资产价格波动呈现出非线性、高噪声、强跨市场联动性等复杂动态特征。例如,2020年新冠疫情期间,美股数次熔断事件引发全球股市剧烈震荡;而2023年美元加息周期&#x…...
C++核心编程--3 函数提高
函数的一些高级用法。 3.1 函数形参默认值 C中,函数的形参可以有默认值,调用函数时,未进行赋值的形参会使用默认值 void func(int f_var1 10, int f_var2 20); // 声明 void func(int f_var1, int f_var2) // 定义 {std::cout <&l…...
AI Agent开发第67课-彻底消除RAG知识库幻觉(1)-文档分块全技巧
开篇 在上篇《AI Agent开发第66课-彻底消除RAG知识库幻觉-带推理的RAG》放出后,网友们反响很大。有得告诉我:原来还有Rewrite这么一招?早知道这一招很多之前的一些遗留问题都能解决了。不过在上一篇结尾我已经提到了,要真正解决一个AI Agent在响应时产生的幻觉我们用提示语…...
c++多态面试题之(析构函数与虚函数)
有以下问题展开 析构函数要不要定义成虚函数?基类的析构函数要不要定义成虚函数?如果不定义会有什么问题,定义了在什么场景下起作用。 1. 基类析构函数何时必须定义为虚函数? 当且仅当通过基类指针(或引用)…...
buildroot使用外部编译链编译bluez蓝牙工具
buildroot使用外部编译链编译bluez蓝牙工具 主要参见这个csdn buildroot使用外部编译链编译bluez蓝牙工具_bluez编译-CSDN博客 设置交叉编译工具路径时,设置到bin目录之前 如果menuconfig不能改路径,就去 .config下去改 这样才能编译过...
自定义类型:结构体
1.结构体类型的声明 1.1.1结构的声明 struct tag {member-list; }variable-list; 描述一个学生:只包含了学生的名字、年龄、性别、学号 struct Stu {char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号 }; 1.1.2 结构体变量的创建和初始…...
以项目的方式学QT开发C++(一)——超详细讲解(120000多字详细讲解,涵盖qt大量知识)逐步更新!
以项目的方式学QT开发 以项目的方式学QT开发 P1 QT介绍 1.1 QT简介 1.2 QT安装 1.2.1 Windows QT安装 1.2.2 QT Creator 使用基本介绍 P2 C基础 2.1 命名空间 2.1.1 命名空间作用 2.1.2 自定义命名空间 2.2 从C语言快速入门 2.2.1 输入输出 2.2.2 基…...
Spring框架的事务管理
引言 在企业级应用开发中,事务管理是一个至关重要的环节,它确保了数据的一致性和完整性。Spring 框架为我们提供了强大而灵活的事务管理功能,能够帮助开发者更轻松地处理复杂的事务场景。本文将深入探讨 Spring 框架的事务管理,包…...
TypeScript:类
一、基本概念 TypeScript 类是基于 ES6 类的语法扩展,增加了类型注解和访问修饰符等特性,提供了更强大的面向对象编程能力。 二、基本语法 class Person {name: string;age: number;constructor(name: string, age: number) {this.name name;this.ag…...
Python继承
在Python编程中,继承是一个让新手又爱又怕的概念。今天我们就来聊聊这个看似高深实则简单的特性,保证让你看完后能拍着胸脯说:“继承嘛,小菜一碟!” 一、什么是继承? 想象一下你正在玩一个养成游戏。你创…...
浏览器宝塔访问不了给的面板地址
注意你们的端口,服务器的端口开放了没!!!宝塔给的端口是否在范围之内!! 我的当时是1000/10000 (阿里云服务器) 但是宝塔给的是 4W多 对不上!! 更换安全组…...
强化学习入门:马尔科夫奖励过程
文章目录 前言1、组成部分2、应用例子3、马尔科夫奖励过程总结 前言 最近想开一个关于强化学习专栏,因为DeepSeek-R1很火,但本人对于LLM连门都没入。因此,只是记录一些类似的读书笔记,内容不深,大多数只是一些概念的东…...
RHCE实验:通过脚本判断用户是否存在
一、实验要求 1、 写一个脚本,使用函数完成 1 、函数能够接受一个参数,参数为用户名; 判断一个用户是否存在 如果存在, 就返回此用户的 shell 和 UID ;并返回正常状态值; 如果不存在,就说此用…...
Windows软件插件-音视频捕获
下载本插件 音视频捕获就是获取电脑外接的话筒,摄像头,或线路输入的音频和视频。 本插件捕获电脑外接的音频和视频。最多可以同时获取4个视频源和4个音频源。插件可以在win32和MFC程序中使用。 使用方法 首先,加载本“捕获”DLL,…...
每日算法 - 【Swift 算法】Two Sum 问题:从暴力解法到最优解法的演进
【Swift 算法】Two Sum 问题:从暴力解法到最优解法的演进 本文通过“Two Sum”问题,带你了解如何从最直观的暴力解法,逐步优化到高效的哈希表解法,并对两者进行对比,适合算法入门和面试准备。 💡 问题描述 …...
2025年,如何制作并部署一个完整的个人博客网站
欢迎访问我的个人博客网站:欢迎来到Turnin的个人博客 github开源地址:https://github.com/Re-restart/my_website 前言 2024年年初,从dji实习回来之后,我一直想着拓宽自己的知识边界。在那里我发现虽然大家不用java,…...
深度学习框架---TensorFlow概览
一、TensorFlow 概述 1. 发展历程 1.x 版本:基于静态图(Graph)和会话(Session),需预先定义计算图,调试较复杂。2.x 版本:默认启用动态图(Eager Execution)&…...
鸿蒙OSUniApp制作自定义的下拉菜单组件(鸿蒙系统适配版)#三方框架 #Uniapp
UniApp制作自定义的下拉菜单组件(鸿蒙系统适配版) 前言 在移动应用开发中,下拉菜单是一个常见且实用的交互组件,它能在有限的屏幕空间内展示更多的选项。虽然各种UI框架都提供了下拉菜单组件,但在一些特定场景下&…...
扣子(Coze)案例:工作流生成小红书心理学卡片
大家好!我是 Robin。专注于 AI 技术探索与实践,持续分享 Coze 智能体、Coze 模板,以及 Coze 工作流搭建案例。 工作流智能体作用: 输入需要生成小红书心理学知识卡片的数量,工作流自动批量生成图文。 首先演示一下生…...
深度理解用于多智能体强化学习的单调价值函数分解QMIX算法:基于python从零实现
引言:合作式多智能体强化学习与功劳分配 在合作式多智能体强化学习(MARL)中,多个智能体携手合作,共同达成一个目标,通常会收到一个团队共享的奖励。在这种场景下,一个关键的挑战就是功劳分配&a…...
C语言经典笔试题目分析(持续更新)
1. 描述下面代码中两个static 各自的含义 static void func (void) {static unsigned int i; }static void func(void) 中的 static 作用对象:函数 func。 含义: 限制函数的作用域(链接属性),使其仅在当前源文件&…...
射击游戏demo11
完善地图,加载出装饰品,检测人员与地面的碰撞,检测子弹与岩壁的碰撞,检测手雷与地面的碰撞。 import pygame import sys import os import random import csv # 初始化Pygame pygame.init()# 屏幕宽度 SCREEN_WIDTH 1200 # 屏幕高…...