Boost学习之深入理解asio库

news2025/7/17 6:45:50

Asio简介

Boost C++ 库 Asio,它是异步输入输出的核心。 名字本身就说明了一切:Asio 即异步输入/输出。该库可以让 C++ 异步地处理数据,且平台独立。 异步数据处理就是指,任务触发后不需要等待它们完成。 相反,Boost.Asio 会在任务完成时触发一个应用。 异步任务的主要优点在于,在等待任务完成时不需要阻塞应用程序,可以去执行其它任务。

异步任务的典型例子是网络应用。 如果数据被发送出去了,比如发送至 Internet,通常需要知道数据是否发送成功。 如果没有一个像 Boost.Asio 这样的库,就必须要等待接收函数的返回值,并得到一个确认或是错误代码。 而使用 Boost.Asio,这个过程被分为两个单独的步骤:第一步是作为一个异步任务开始数据传输。 一旦传输完成,不论成功或是错误,应用程序都会在第二步中得到相应的结果通知。 主要的区别在于,应用程序无需阻塞至传输完成,而可以在这段时间里执行其它操作。

I/O服务与I/O对象

使用 Boost.Asio 进行异步数据处理的应用程序基于两个概念:I/O 服务 和 I/O 对象 。

I/O 服务抽象了操作系统的接口,允许第一时间进行异步数据处理. I/O 对象则用于初始化特定的操作。

Boost.Asio 只提供了一个名为 boost::asio::io_service 的类作为 I/O 服务,它针对所支持的每一个操作系统都分别实现了优化的类,另外库中还包含了针对不同 I/O 对象的几个类。 其中,类 boost::asio::ip::tcp::socket 用于通过网络发送和接收数据,而类 boost::asio::deadline_timer 则提供了一个计时器,用于测量某个固定时间点到来或是一段指定的时长过去了。

以下示例使用了计时器,与 Asio 所提供的其它 I/O 对象相比较而言,它不需要任何有关于网络编程的知识。

#include <boost\asio.hpp>
#include <iostream>

void handler(const boost::system::error_code& ec)
{
	std::cout << "5s" << std::endl;
}


int main()
{
	boost::asio::io_service io_service;
	boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5));
	timer.async_wait(handler);
	io_service.run();
	return 0;
}

函数 main() 首先定义了一个 I/O 服务 io_service ,用于初始化 I/O 对象 timer 。 由于 timer 的作用类似于一个闹钟,所以 boost::asio::deadline_timer 的构造函数可以传入第二个参数,用于表示在某个时间点或是在某段时长之后闹钟停止。 本例指定了五秒的时长,该闹钟在 timer 被定义之后立即开始计时。

通过调用方法 async_wait() 并传入handler() 函数的名字作为唯一参数,可以让 Asio 启动一个异步操作。 请留意,我们只是传入了 handler() 函数的名字,而该函数本身并没有被立即调用。

async_wait() 的好处是,该函数调用会立即返回,而不是等待五秒钟。 一旦闹钟时间到,作为参数所提供的函数就会被调用。 因此,应用程序可以在调用了 async_wait() 之后执行其它操作,而不是阻塞在这里。

像 async_wait() 这样的方法被称为是非阻塞式的。 I/O 对象通常还提供了阻塞式的方法,可以让执行流在特定操作完成之前保持阻塞。 例如,可以调用阻塞式的 wait() 方法,取代 boost::asio::deadline_timer 的调用。 由于它会阻塞调用,所以它不需要传入一个函数名,而是在指定时间点或指定时长之后返回。

在调用 async_wait() 之后,又在 I/O 服务之上调用了一个名为 run() 的方法。这是必须的,因为控制权必须被操作系统接管,才能在五秒之后调用 handler() 函数。

async_wait() 会启动一个异步操作并立即返回,而 run() 则是阻塞的。因此调用 run()后程序执行会停止。 具有讽刺意味的是,许多操作系统只是通过阻塞函数来支持异步操作。 以下例子显示了为什么这个限制通常不会成为问题。

#include <boost\asio.hpp>
#include <iostream>

void handler(const boost::system::error_code& ec)
{
	std::cout << "5s" << std::endl;
}

void handler1(const boost::system::error_code& ec)
{
	std::cout << "2m " << std::endl;
}

int main()
{
	boost::asio::io_service io_service;

	boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5));
	timer.async_wait(handler);

	boost::asio::deadline_timer timer1(io_service, boost::posix_time::minutes(2));
	timer1.async_wait(handler1); 

	io_service.run(); 
	return 0;
}

上面的程序用了两个 boost::asio::deadline_timer 类型的 I/O 对象。 第一个 I/O 对象表示一个5秒后触发的闹钟,而第二个则表示一个2分钟后触发的闹钟。 每一段指定时长过去后,都会相应地调用函数 handler() 和 handler1()。

在 main() 的最后,再次在唯一的 I/O 服务之上调用了 run() 方法。 如前所述,这个函数将阻塞执行,把控制权交给操作系统以接管异步处理。 在操作系统的帮助下,handler() 函数会在五秒后被调用,而 handler1() 函数则在2分钟后被调用。

很多人会问,为什么异步处理还要调用阻塞式的 run() 方法?很明显,应用程序必须防止被中止执行,所以这样做实际上不会有任何问题。 如果 run() 不是阻塞的,main() 就会结束从而中止该应用程序。 如果应用程序不应被阻塞,那么就应该在一个新的线程内部调用 run(),它自然就会仅仅阻塞那个线程。

一旦特定的 I/O 服务的所有异步操作都完成了,控制权就会返回给 run() 方法,然后它就会返回。 以上两个例子中,应用程序都会在闹钟到时间后马上结束。

相关视频推荐

boost.asio是什么?解决了网络编程中哪些痛点?

程序性能上不去怎么办? 异步来解决你的问题

手把手带你看 mmorpg 开源框架的网络模块封装

Linux C/C++开发(后端/音视频/游戏/嵌入式/高性能网络/存储/基础架构/安全)

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

扩展与多线程

用 Boost.Asio 这样的库来开发应用程序,与一般的 C++ 风格不同。那些可能需要较长时间才返回的函数不再是以顺序的方式来调用。 不再是调用阻塞式的函数,Boost.Asio 是启动一个异步操作。 而那些需要在操作结束后调用的函数则实现为相应的句柄。 这种方法的缺点是,本来顺序执行的功能变得在物理上分割开来了,从而令相应的代码更难理解。

像 Boost.Asio 这样的库通常是为了使应用程序具有更高的效率。 应用程序不需要等待特定的函数执行完成,而可以在期间执行其它任务,如开始另一个需要较长时间的操作。一般用在并发操作中,每次执行的任务都是相同的原子性任务,每个任务执行先后没有顺序概念。比如博主最近实现的httpserver,多个客户端请求实时视频,可以抽象为一个个原子操作,每个原子任务可以单独异步执行,没有任何先后顺序。

如果在某个 boost::asio::io_service 类型的对象之上调用 run() 方法,则相关联的句柄也会在同一个线程内被执行。 通过使用多线程,应用程序可以同时调用多个 run() 方法。 一旦某个异步操作结束,相应的 I/O 服务就将在这些线程中的某一个之中执行句柄。 如果第二个操作在第一个操作之后很快也结束了,则 I/O 服务可以在另一个线程中执行句柄,而无需等待第一个句柄终止。

#include<boost/asio.hpp>
#include<boost/thread/thread.hpp>
#include<iostream>

boost::asio::io_service io_service; //创建io_service对象,后续初始化所有boost对象都需要传入该服务。与操作系统进行交互。

void handler(const boost::system::error_code& ec)
{
	std::cout << "handler 5 s" << std::endl;
}

void handler1(const boost::system::error_code& ec)
{
	std::cout << "handler1 20 s" << std::endl;
}

void run()
{
	io_service.run();
}

int main()
{
	boost::asio::deadline_timer timer1(io_service, boost::posix_time::seconds(20));
	timer1.async_wait(handler1);

	boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5));
	timer.async_wait(handler);

	boost::thread thread1(run);
	boost::thread thread2(run);

	thread1.join();
	thread2.join();
	return 0;
}

运行结果:

以上代码段是多线程的应用, 通过使用在boost/thread.hpp中定义的boost::thread类,在 main() 中创建了两个线程。 这两个线程均针对同一个 I/O 服务调用了 run() 方法。 这样当异步操作完成时,这个 I/O 服务就可以使用两个线程中的任意一个去执行句柄函数。

这个例子中的第一个计时数20s后执行,第二个5s后执行。 由于有两个线程,所以 handler() 和 handler1() 可以并行执行,通过运行结果可以看出,第二个执行完后,第一个过了15秒才执行结束。 如果第二个计时器触发时第一个仍在执行,则第二个句柄就会在第二个线程中执行。 如果第一个计时器的句柄已经终止,则 I/O 服务可以自由选择任一线程。

要注意,使用线程并不总是值得的。 多线程程序运行会导致不同信息在标准输出流上混合输出,因为这两个句柄可能会并行运行,访问同一个共享资源:标准输出流 std::cout。 这种访问必须被同步,以保证每一条信息在另一个线程可以向标准输出流写出另一条信息之前被完全写出。这就涉及到在多线程编程中,死锁的情况,即多个线程抢占同一份资源,导致死锁。这种情况解决方法在后面的博客中会讲到。

多次调用同一个 I/O 服务的 run() 方法,是为基于 Boost.Asio 的应用程序增加可扩展性的推荐方法。 另外还有一个不同的方法:不要绑定多个线程到单个 I/O 服务,而是创建多个 I/O 服务。 然后每一个 I/O 服务使用一个线程。 如果 I/O 服务的数量与系统的处理器内核数量相匹配,则异步操作都可以在各自的内核上执行。

#include<boost/asio.hpp>
#include<boost/thread/thread.hpp>
#include<iostream>

boost::asio::io_service io_service1;
boost::asio::io_service io_service2;

void handler(const boost::system::error_code &ec)
{
	std::cout << "handler run 20s" << std::endl;
}

void handler1(const boost::system::error_code &ec)
{
	std::cout << "handler1 run 5s" << std::endl;
}

void run1()
{
	io_service1.run();
}

void run2()
{
	io_service2.run();
}

int main()
{
	boost::asio::deadline_timer timer1(io_service1, boost::posix_time::seconds(20));
	timer1.async_wait(handler);

	boost::asio::deadline_timer timer2(io_service2, boost::posix_time::seconds(5));
	timer2.async_wait(handler1);

	boost::thread thread1(run1);
	boost::thread thread2(run2);

	thread1.join();
	thread2.join(); 
	return 0;
}

运行结果:

添加图片注释,不超过 140 字(可选)

前面使用两个计时器的例子被重写为使用两个 I/O 服务。 这个应用程序仍然基于两个线程;但是现在每个线程被绑定至不同的 I/O 服务。 此外,两个 I/O 对象 timer1 和 timer2 现在也被绑定至不同的 I/O 服务。

这个应用程序的功能与前一个相同。 在一定条件下使用多个 I/O 服务是有好处的,每个 I/O 服务有自己的线程,最好是运行在各自的处理器内核上,这样每一个异步操作连同它们的句柄就可以局部化执行。 如果没有远端的数据或函数需要访问,那么每一个 I/O 服务就像一个小的自主应用。 这里的局部和远端是指类似高速缓存、内存页这样的资源。 由于在确定优化策略之前需要对底层硬件、操作系统、编译器以及潜在的瓶颈有专门的了解,所以应该仅在清楚这些好处的情况下使用多个I/O 服务。

Asio网络编程

虽然 Boost.Asio 是一个可以异步处理任何种类数据的库,但是它主要被用于网络编程。 网络功能是异步处理的一个很好的例子,因为通过网络进行数据传输可能会需要较长时间,从而不能直接获得确认或错误条件。

Boost.Asio 提供了多个 I/O 对象以开发网络应用。 以下例子使用了boost::asio::ip::tcp::socket类来建立与另一台PC的连接,并下载 ‘baidu’ 主页。

#include<boost/asio.hpp>
#include<boost/array.hpp>
#include<iostream>
#include<string>

boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::socket sock(io_service);
boost::array<char, 4096> buffer;

void read_handler(const boost::system::error_code &ec, std::size_t transferred_byte)
{
	if (!ec)
	{
		std::cout << std::string(buffer.data(), transferred_byte) << std::endl;
		sock.async_read_some(boost::asio::buffer(buffer), read_handler);
	}
}
void connect_handler(const boost::system::error_code& ec)
{
	if (!ec)
	{
		boost::asio::write(sock, boost::asio::buffer("GET / HTTP 1.1\r\nHost: www.baidu.com\r\n\r\n"));
		sock.async_read_some(boost::asio::buffer(buffer), read_handler);
	}
}

void resolve_handler(const boost::system::error_code& ec, boost::asio::ip::tcp::resolver::iterator it)
{
	if (!ec)
	{
		sock.async_connect(*it, connect_handler);
	}
}

int main()
{
	boost::asio::ip::tcp::resolver::query query("www.baidu.com", "80");
	resolver.async_resolve(query, resolve_handler);
	io_service.run();

	system("pause");
	return 0;
}

这个程序最明显的部分是三个句柄的使用:connect_handler() 和 read_handler() 函数会分别在连接被建立后以及接收到数据后被调用。 那么为什么需要 resolve_handler() 函数呢?

互联网使用了IP地址来标识每台PC。 IP地址实际上只是一长串数字,难以记住。 而记住象 www.baidu.com 这样的名字就容易得多。 为了在互联网上使用类似的名字,需要通过一个叫作域名解析的过程将它们翻译成相应的IP地址。 这个过程由所谓的域名解析器来完成,对应的 I/O 对象是:boost::asio::ip::tcp::resolver。

域名解析也是一个需要连接到互联网的过程。 有些专门的PC,被称为DNS服务器,其作用就像电话本,它知晓哪个IP地址被赋给了哪台PC。 由于这个过程本身的透明的,只要明白其背后的概念以及为何需要 boost::asio::ip::tcp::resolver I/O 对象就可以了。 由于域名解析不是发生在本地的,所以它也被实现为一个异步操作。 一旦域名解析成功或被某个错误中断,resolve_handler() 函数就会被调用。

因为接收数据需要一个成功的连接,进而需要一次成功的域名解析,所以这三个不同的异步操作要以三个不同的句柄来启动。 resolve_handler() 访问 I/O 对象 sock,用由迭代器 it 所提供的解析后地址创建一个连接。 而 sock 也在 connect_handler() 的内部被使用,发送 HTTP 请求并启动数据的接收。 因为所有这些操作都是异步的,各个句柄的名字被作为参数传递。 取决于各个句柄,需要相应的其它参数,如指向解析后地址的迭代器 it 或用于保存接收到的数据的缓冲区 buffer。

开始执行后,该应用将创建一个类型为 boost::asio::ip::tcp::resolver::query 的对象 query,表示一个查询,其中含有名字 www.baidu.com以及互联网常用的端口80。 这个查询被传递给 async_resolve() 方法以解析该名字。 最后,main() 只要调用 I/O 服务的 run() 方法,将控制交给操作系统进行异步操作即可。

当域名解析完成后,resolve_handler() 被调用,检查域名是否能被解析。 如果解析成功,则存有错误条件的对象 ec 被设为0。 只有在这种情况下,才会相应地访问 socket 以创建连接。 服务器的地址是通过类型为 boost::asio::ip::tcp::resolver::iterator 的第二个参数来提供的。

调用了 async_connect() 方法之后,connect_handler() 会被自动调用。 在该句柄的内部,会访问 ec 对象以检查连接是否已建立。 如果连接是有效的,则对相应的 socket 调用 async_read_some() 方法,启动读数据操作。 为了保存接收到的数据,要提供一个缓冲区作为第一个参数。 在以上例子中,缓冲区的类型是 boost::array。

每当有一个或多个字节被接收并保存至缓冲区时,read_handler() 函数就会被调用。 准确的字节数通过 std::size_t 类型的参数 bytes_transferred 给出。 同样的规则,该句柄应该首先看看参数 ec 以检查有没有接收错误。 如果是成功接收,则将数据写出至标准输出流。

请留意,read_handler() 在将数据写出至 std::cout 之后,会再次调用 async_read_some() 方法。因为无法保证仅在一次异步操作中就可以接收到整个网页。 async_read_some() 和 read_handler() 的交替调用只有当连接被破坏时才中止,如当 web 服务器已经传送完整个网页时。 这种情况下,在 read_handler() 内部将报告一个错误,以防止进一步将数据输出至标准输出流,以及进一步对该 socket 调用 async_read() 方法。 这时该例程将停止。

以下示例将讲述通过本地连接,实现异步socket功能。

#include <boost/asio.hpp> 
#include <string> 

boost::asio::io_service io_service; 
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 80); 
boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint); 
boost::asio::ip::tcp::socket sock(io_service); 
std::string data = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!"; 

void write_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) 
{ 
} 

void accept_handler(const boost::system::error_code &ec) 
{ 
  if (!ec) 
  { 
    boost::asio::async_write(sock, boost::asio::buffer(data), write_handler); 
  } 
} 

int main() 
{ 
  acceptor.listen(); 
  acceptor.async_accept(sock, accept_handler); 
  io_service.run(); 
} 

类型为 boost::asio::ip::tcp::acceptor 的 I/O 对象 acceptor , 被初始化为指定的协议和端口号 ,用于等待从其它PC传入的连接。 初始化工作是通过endpoint 对象完成的,该对象的类型为 boost::asio::ip::tcp::endpoint,将本例中的接收器配置为使用端口80来等待 IP v4 的传入连接。

接收器初始化完成后,main() 首先调用 listen() 方法将接收器置于接收状态,然后再用 async_accept() 方法等待初始连接。 用于发送和接收数据的 socket 被作为第一个参数传递。

当一个PC试图建立一个连接时,accept_handler() 被自动调用。 如果该连接请求成功,就执行函数 boost::asio::async_write()来通过 socket 发送保存在 data 中的信息。 boost::asio::ip::tcp::socket 还有一个名为 async_write_some() 的方法也可以发送数据;不过它会在发送了至少一个字节之后调用相关联的句柄。 该句柄需要计算还剩余多少字节,并反复调用 async_write_some() 直至所有字节发送完毕。 而使用 boost::asio::async_write() 可以避免这些,因为这个异步操作仅在缓冲区的所有字节都被发送后才结束。

在这个例子中,当所有数据发送完毕,空函数 write_handler() 将被调用。 由于所有异步操作都已完成,所以应用程序终止。 与其它PC的连接也被将被关闭。

写在结尾

本文在深入学习研究Boost::asio库后,对学习的每个示例,进行详细步骤的分解。因为异步操作代码执行逻辑比较混乱,只看代码理解起来有一定的难度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1356094.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

调用百度地图 API 的步骤详解

百度地图 Web 服务 API 为开发者提供 http/https 接口&#xff0c;即开发者通过 http/https 形式发起检索请求&#xff0c;获取返回 json 或 xml 格式的检索数据。用户可以基于此开发 JavaScript、C#、C、Java 等语言的地图应用。百度地图 API 在线地址为&#xff1a;baidumap.…

Gromacs make_ndx建组问题

1. 选择特定分子或原子&#xff1a; gmx make_ndx -f input.gro -o output.ndx这将打开交互式界面&#xff0c;您可以在其中选择要包含在索引文件中的分子和原子。按照提示进行操作&#xff0c;选择适当的分组。 2. 手动创建索引文件&#xff1a; 您还可以手动创建一个文本文件…

JavaWeb——后端之Mybatis

四、Mybatis 概念&#xff1a; Mybatis是一款持久层&#xff08;Dao层&#xff09;框架&#xff0c;用于简化JDBC&#xff08;Sun操作数据库的规范&#xff0c;较繁琐&#xff09;的开发 历史&#xff1a; Apache的一个开源项目iBatis&#xff0c;2010年由apache迁移到了goog…

常见位运算总结

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 前言 1.基础位运算 &运算 |运算 ^运算 >>运算 <<运算 ~运算 2.给一个数n&#xff0c;确定他的二进制表示中的第x位&#xff0c;是0还是1 3.将一个数n的二进制表示的第x位修改成1 4.将一个数…

QML 项目中使用 Qt Design Studio 生成的UI界面

作者&#xff1a;billy 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 前言 今天来和大家聊一下 Qt Design Studio 这个软件。这个软件的主要功能是用来快速完成 UI 界面&#xff0c;就和 widget 中的 desig…

Redis学习笔记(1)——感谢尚硅谷官方文档

Redis学习笔记&#xff08;1&#xff09;——感谢尚硅谷官方文档 1. NoSQL1.1 NoSQL数据库概述1.2 各种NoSQL数据库 2. Redis数据库安装2.1 安装条件2.2 Widows下如何安装Redis?2.3 Linux下如何安装Redis? 3. Redis介绍3.1 Redis 简介3.2 Redis 优势3.3 Redis与其他key-value…

HttpClient库与代理IP在爬虫程序中的应用

目录 前言 一、HttpClient库的基本使用方法 二、代理IP的使用方法 三、代理IP池的使用方法 四、总结 前言 在编写爬虫程序时&#xff0c;我们经常会使用HttpClient库来发送HTTP请求&#xff0c;获取网页内容。然而&#xff0c;有些网站可能会对频繁的请求进行限制&#x…

python的课后练习总结3之条件语句

1,简单点&#xff0c;只有IF IF 后面加入条件然后冒号: 条件成立执行的代码1 条件成立执行的代码2 条件是否成立都执行的代码 身高 float(input(请输入你的身高(米):)) if 身高 > 1.3:print(f您的身高是{身高}米,请您买票) print(祝您旅途愉快) 2,IF 加个else if 条件:…

[论文分享]TimesURL:通用时间序列表示学习的自监督对比学习

论文题目&#xff1a;TimesURL: Self-supervised Contrastive Learning for Universal Time Series Representation Learning 论文地址&#xff1a;https://arxiv.org/abs/2312.15709 代码地址&#xff1a;暂无 摘要 学习适用于各种下游任务的通用时间序列表示具有挑战性&…

无人机低空视角:针对人群密集场景的检测、跟踪和计数技术

无人机低空视角&#xff1a;针对人群密集场景的检测、跟踪和计数技术 DroneCrowdPaper简介数据集ECCV2020挑战DroneCrowd&#xff08;完整版&#xff09; DroneCrowd Paper 无人机在人群中的检测、跟踪和计数&#xff1a;基准研究。 简介 本文提出了一种时空多尺度注意力网络…

数据结构与算法——第一次大作业【考点罗列//错题修正//题解】

目录 一、选择题 ——绪论—— 1.【单选题】——数据结构的研究方向 2.【单选题】 ——数据结构的研究问题 3.【单选题】——数据结构的基本术语及概念 4.【单选题】 ——数据结构与算法 5.【单选题】 ——时间复杂度计算 ——顺序表与链表—— 6.【单选题】——顺序表…

算法训练营Day36(贪心-重叠区间)

都算是 重叠区间 问题&#xff0c;大家可以好好感受一下。 都属于那种看起来好复杂&#xff0c;但一看贪心解法&#xff0c;惊呼&#xff1a;这么巧妙&#xff01; 还是属于那种&#xff0c;做过了也就会了&#xff0c;没做过就很难想出来。 不过大家把如下三题做了之后&#…

多通道病虫害分子检测仪-百科科普知识

在农业科技日新月异的今天&#xff0c;病虫害防治已经成为现代农业的重要一环。为了更精准、更快速地检测和防治病虫害&#xff0c;多通道病虫害分子检测仪应运而生&#xff0c;成为守护绿色家园的"黑科技"。 WX-XC1多通道病虫害分子检测仪是一款集成了分子生物学、…

音频DAC,ADC,CODEC高性能立体声

想要让模拟信号和数字信号顺利“交往”&#xff0c;就需要一座像“鹊桥”一样的中介&#xff0c;将两种不同的语言转变成统一的语言&#xff0c;消除无语言障碍。这座鹊桥就是转换器芯片&#xff0c;也就是ADC芯片。ADC芯片的全称是Analog-to-Digital Converter, 即模拟数字转换…

webpack知识点总结(高级应用篇)

除开公共基础配置之外&#xff0c;我们意识到两点: 1. 开发环境(modedevelopment),追求强大的开发功能和效率&#xff0c;配置各种方便开 发的功能;2. 生产环境(modeproduction),追求更小更轻量的bundle(即打包产物); 而所谓高级应用&#xff0c;实际上就是进行 Webpack 优化…

linux 系统 kill 指令笔记

kill 名称 kill - send a signal to a process 向指定的线程或进程发送信号 描述 The default signal for kill is TERM. Use -l or -L to list availablesignals. Particularly useful signals include HUP, INT, KILL, STOP,CONT, and 0. Alternate signals …

C#: 和时间相关,延时、获取系统时间、时间格式转换、定时器 等

说明&#xff1a;本文记录C# 和时间相关&#xff0c;延时、获取系统时间、时间格式转换、定时器 等&#xff0c;应用和代码。 1.延时函数 System.Threading.Thread.Sleep(20); //毫秒 1.1 主线程不卡延时函数 /* 主线程不卡延时函数 */public static void Delay_ms(int mil…

【C语言期末】基于VS2022的学生成绩管理系统

诚接计算机专业编程任务(C语言、C、Python、Java、HTML、JavaScript、Vue等)10/15R&#xff0c;如有需要请私信我&#xff0c;或者加我的企鹅号&#xff1a;1404293476 本文资源&#xff1a;https://download.csdn.net/download/weixin_47040861/88702521https://download.csd…

Spring Cloud Config相关面试题及答案(2024)

1、什么是 Spring Cloud Config&#xff0c;它解决了哪些问题&#xff1f; Spring Cloud Config 是一个为微服务架构提供集中化外部配置支持的项目。它是构建在 Spring Cloud 生态系统之上&#xff0c;利用 Spring Boot 的开发便利性&#xff0c;简化了分布式系统中的配置管理…

LCR 174. 寻找二叉搜索树中的目标节点

解题思路&#xff1a; 二叉搜索树一般采用中序遍历&#xff08;从小到大排列&#xff09;。 class Solution {int res, cnt;public int findTargetNode(TreeNode root, int cnt) {this.cnt cnt;dfs(root);return res;}void dfs(TreeNode root) {if(root null) return;dfs(ro…