TLS握手性能测试工具:快速重置、多线程与高级统计分析(C/C++代码实现)

本文主要是介绍TLS握手性能测试工具:快速重置、多线程与高级统计分析(C/C++代码实现),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

随着网络安全的日益重要,传输层安全性(TLS)协议在保护数据传输中扮演着关键角色。TLS握手作为该协议的核心部分,确保了客户端和服务器之间的安全通信。鉴于其重要性,对TLS握手的性能进行精确评估变得至关重要。该工具专注于TLS握手的性能测试,而不涉及数据传输或重协商。

快速重置TCP连接

理解快速重置TCP连接对于优化TLS握手性能具有重要意义

在数据传输过程中,TCP连接的建立和关闭是必要的操作。特别是在TLS握手中,传统的TLS测试工具在每次握手后不会立即关闭TCP连接,而是保持连接状态,这会导致不必要的资源占用,增加服务器的负担。新工具采用的快速TCP连接重置方法能有效缓解这一问题。

首先,快速重置TCP连接有助于减少所谓的TIME_WAIT状态。在标准的TCP连接关闭过程中,当一端(通常是客户端)完成数据发送并发送FIN请求关闭连接时,另一端在确认数据接收无误后也会发送FIN包,最终关闭连接。然而,在这个关闭过程中,连接会处于TIME_WAIT状态,通常持续4分钟(两个MSL,即两倍的最大报文生存时间),以确保网络中仍然在传输中的报文得以妥善处理。

快速重置TCP连接的方法可以显著减少这些资源的占用。通过在TLS握手完成后立即发送RESET信号而不是标准的FIN信号,连接可以被立即关闭,无需等待TIME_WAIT状态的结束。这样,系统资源如端口号能更快被释放并重新利用,极大提高了服务器的性能和并发处理能力。

快速重置TCP连接还有助于防止网络拥塞。在高并发的应用场景下,如数千个连接请求同时到达服务器,如果不及时关闭不再需要的连接,就会造成大量的端点资源占用,进而导致网络拥塞,影响正常的网络通信]。通过实施快速重置,可以避免这种情况,确保网络资源的高效利用。

从安全性角度来看,快速重置TCP连接同样具有优势。长时间保持的TCP连接可能会成为恶意攻击的目标,例如拒绝服务攻击(DoS)。攻击者可能通过大量伪造的SYN包尝试消耗服务器资源,而快速重置可以迅速释放这些无效连接,避免攻击行为对服务器造成实质性的影响。

结合TLS握手的工作流程看,快速重置TCP连接的优势更为明显。在TLS握手过程中,客户端与服务器需要进行多次信息交换以协商加密算法和验证身份等,一旦握手完成,敏感信息交换即告结束,此时即可执行快速重置,从而既保证了安全又提升了效率。

此外,考虑到不同应用和环境可能对快速重置TCP连接的适应性,开发者应当提供灵活的配置选项,允许用户根据实际需求选择是否启用快速重置功能。同时,在进行网络安全设计和性能优化时,也需要考虑这一特性对现有网络架构和业务流程的潜在影响。

快速重置TCP连接不仅提升了TLS握手的效率,减少了服务器的资源占用,同时也增强了网络的安全性。这种技术的应用,特别是在需要高并发、高安全等级的网络环境中,将会成为提升网络性能的重要手段。

下面该工具通过优化TCP连接的重置机制,实现了在完成TLS握手后立即重置TCP连接,从而大幅提高了测试的效率和并发性。这种快速重置机制避免了长时间的连接保持,使得测试工具能够在短时间内发起更多的TLS握手。

多线程

多线程 是指在同一个进程中可以同时运行多个线程执行不同的任务。每个线程可以看作是程序执行的独立流,拥有自己的寄存器和堆栈,但共享进程的内存空间(如代码段、数据段等)。在网络应用中,多线程可以带来以下优势:

  1. 并行处理:多个线程可以同时处理多个网络连接或任务,提高了程序的并发能力。
  2. 资源利用率:通过合理分配线程,可以更高效地利用CPU资源,特别是在多核处理器上。
  3. 响应速度:线程间的上下文切换比进程间的上下文切换要快,这可以提高程序的响应速度。

epoll() IO优化

epoll 是Linux提供的一种高效的I/O多路复用接口,它在处理大量并发socket连接时,相较于传统的select和poll机制,具有显著的性能优势。epoll的工作原理如下:

  1. 事件驱动:epoll通过监听文件描述符(通常是socket)上的事件(如读、写事件),当事件发生时,epoll会通知程序进行处理。
  2. 高效的事件通知:与传统的select和poll不同,epoll不需要在每次调用时都传递和检查整个文件描述符集合,而是通过维护一个内部的数据结构来记录哪些文件描述符有待处理的事件。这大大减少了资源消耗和响应时间。
  3. 边缘触发(Edge Triggered, ET)模式:epoll支持边缘触发模式,在此模式下,只有在状态发生变化时(如从无数据到有数据可读),才会通知程序。这减少了不必要的通知,提高了效率。

多线程与epoll()的结合

将多线程与epoll结合使用,可以进一步提升网络应用的性能:

  1. 线程池管理:可以创建一个线程池,每个线程负责监听一部分epoll事件,或者在新连接到来时动态分配线程。
  2. 负载均衡:通过合理分配线程和epoll监听的文件描述符,可以平衡每个线程的负载,避免某些线程过载而其他线程空闲。
  3. 资源利用最大化:结合多线程和epoll的优势,可以更高效地利用CPU和IO资源,特别是在高并发场景下。

在实际的网络服务器或客户端应用中,开发者可以根据实际需求选择合适的线程数量和epoll的使用方式。例如,可以使用少量线程处理大量的网络连接,每个线程负责一部分连接的读写操作,同时利用epoll高效地管理这些连接的事件。

总之,多线程与epoll()的结合为高性能网络编程提供了强大的工具,使得开发者能够构建出既快速又高效的网络应用。

TLS握手性能测试工具:快速重置、多线程与高级统计分析(C/C++代码实现)

工具的实现基于C++和OpenSSL库,使用了以下关键技术:

  1. epoll():用于高效的I/O多路复用,处理大量并发连接。
  2. 多线程:通过std::thread库实现,每个线程可以独立处理TLS握手。
  3. 原子操作:使用std::atomic确保多线程环境下的数据一致性。
  4. 定时器:使用std::chrono库实现定时任务,如每秒更新统计数据。
...
class IO {
private:static const size_t N_EVENTS = 128;static const size_t TO_MSEC = 5;private:int			ed_;int			ev_count_;SSL_CTX			*tls_ctx_;struct epoll_event	events_[N_EVENTS];std::list<SocketHandler *> reconnect_q_;std::list<SocketHandler *> backlog_;public:IO(): ed_(-1), ev_count_(0), tls_ctx_(NULL){tls_ctx_ = SSL_CTX_new(TLS_client_method());// 只允许TLS 1.2和1.3// requested.if (g_opt.tls_vers != TLS_ANY_VERSION) {SSL_CTX_set_min_proto_version(tls_ctx_, g_opt.tls_vers);SSL_CTX_set_max_proto_version(tls_ctx_, g_opt.tls_vers);} else {SSL_CTX_set_min_proto_version(tls_ctx_, TLS1_2_VERSION);SSL_CTX_set_max_proto_version(tls_ctx_, TLS1_3_VERSION);}// 会话恢复if (!g_opt.use_tickets) {unsigned int mode = SSL_SESS_CACHE_OFF| SSL_SESS_CACHE_NO_INTERNAL;if (!g_opt.adv_tickets)SSL_CTX_set_options(tls_ctx_, SSL_OP_NO_TICKET);SSL_CTX_set_session_cache_mode(tls_ctx_, mode);}else {unsigned int mode = SSL_SESS_CACHE_CLIENT| SSL_SESS_CACHE_NO_AUTO_CLEAR;SSL_CTX_set_session_cache_mode(tls_ctx_, mode);}if (g_opt.cipher) {if (g_opt.tls_vers == TLS1_3_VERSION|| g_opt.tls_vers == TLS_ANY_VERSION)if (!SSL_CTX_set_ciphersuites(tls_ctx_,g_opt.cipher))throw Except("cannot set cipher");if (g_opt.tls_vers == TLS1_2_VERSION|| g_opt.tls_vers == TLS_ANY_VERSION)if (!SSL_CTX_set_cipher_list(tls_ctx_,g_opt.cipher))throw Except("cannot set cipher");}if (g_opt.curve)if (!SSL_CTX_set1_groups_list(tls_ctx_, g_opt.curve))throw Except("cannot set elliptic curve");SSL_CTX_set_verify(tls_ctx_, SSL_VERIFY_NONE, NULL);if (g_opt.keylogfile)SSL_CTX_set_keylog_callback(tls_ctx_, keylog);if ((ed_ = epoll_create(1)) < 0)throw Except("can't create epoll");memset(events_, 0, sizeof(events_));}~IO(){if (ed_ > -1)close(ed_);reconnect_q_.clear();SSL_CTX_set_keylog_callback(tls_ctx_, nullptr);if (tls_ctx_)SSL_CTX_free(tls_ctx_);}voidadd(SocketHandler *sh, int events){struct epoll_event ev = {.events = events | EPOLLONESHOT,.data = { .ptr = sh }};if (epoll_ctl(ed_, EPOLL_CTL_MOD, sh->sd, &ev) < 0) {if (errno == ENOENT &&epoll_ctl(ed_, EPOLL_CTL_ADD, sh->sd, &ev) < 0){throw Except("can't add socket to poller");}}}voiddel(SocketHandler *sh){if (epoll_ctl(ed_, EPOLL_CTL_DEL, sh->sd, NULL) < 0)throw Except("can't delete socket from poller");}voidqueue_reconnect(SocketHandler *sh) noexcept{reconnect_q_.push_back(sh);}voidwait(){retry:ev_count_ = epoll_wait(ed_, events_, N_EVENTS, TO_MSEC);if (ev_count_ < 0) {if (errno == EINTR)goto retry;throw Except("poller wait error");}}SocketHandler *next_sk() noexcept{if (ev_count_)return (SocketHandler *)events_[--ev_count_].data.ptr;return NULL;}voidbacklog() noexcept{backlog_.swap(reconnect_q_);}SocketHandler *next_backlog() noexcept{if (backlog_.empty())return NULL;SocketHandler *sh = backlog_.front();backlog_.pop_front();return sh;}SSL *new_tls_ctx(SocketHandler *sh){SSL *ctx = SSL_new(tls_ctx_);if (!ctx)throw Except("cannot clone TLS context");SSL_set_fd(ctx, sh->sd);BIO_set_tcp_ndelay(sh->sd, true);if (g_opt.use_tickets) {auto sess = sh->get_session();if (sess)SSL_set_session(ctx, sess);}if(g_opt.sni)SSL_set_tlsext_host_name(ctx, g_opt.sni);return ctx;}
};class Peer : public SocketHandler {
private:enum _states {STATE_TCP_CONNECT,STATE_TCP_CONNECTING,STATE_TLS_HANDSHAKING,};private:IO			&io_;int			id_;SSL			*tls_;SSL_SESSION		*sess_;std::chrono::time_point<std::chrono::steady_clock> ts_;enum _states		state_;public:Peer(IO &io, int id) noexcept: io_(io), id_(id), tls_(NULL), sess_(NULL), state_(STATE_TCP_CONNECT){sd = -1;dbg_status("created");}virtual ~Peer(){disconnect();if (sess_)SSL_SESSION_free(sess_);}boolnext_state() final override{switch (state_) {case STATE_TCP_CONNECT:return tcp_connect();case STATE_TCP_CONNECTING:return tcp_connect_try_finish();case STATE_TLS_HANDSHAKING:return tls_handshake();default:throw Except("bad next state %d", state_);}return false;}SSL_SESSION*get_session(){return sess_;}private:voidpoll_for_read(){io_.add(this, EPOLLIN | EPOLLERR);}voidpoll_for_write(){io_.add(this, EPOLLOUT | EPOLLERR);}voiddel_from_poll(){io_.del(this);}voiddbg_status(const char *msg) noexcept{if (g_opt.debug)dbg << "peer " << id_ << " " << msg << std::endl;}booltls_handshake(){using namespace std::chrono;state_ = STATE_TLS_HANDSHAKING;if (!tls_) {tls_ = io_.new_tls_ctx(this);stat.tls_handshakes++;ts_ = steady_clock::now();}int r = SSL_connect(tls_);if (r == 1) {auto t1(steady_clock::now());const duration<double, std::milli> lat = t1 - ts_;lat_stat.update(lat.count());dbg_status("has completed TLS handshake");stat.tls_handshakes--;stat.tls_connections++;stat.tot_tls_handshakes++;disconnect();stat.tcp_connections--;io_.queue_reconnect(this);return true;}switch (SSL_get_error(tls_, r)) {case SSL_ERROR_WANT_READ:poll_for_read();break;case SSL_ERROR_WANT_WRITE:poll_for_write();break;default:if (!stat.tot_tls_handshakes)throw Except("cannot establish even one TLS"" connection");stat.tls_handshakes--;stat.error_count++;disconnect();stat.tcp_connections--;}return false;}boolhandle_established_tcp_conn(){// del_from_poll(); dbg_status("has established TCP connection");stat.tcp_handshakes--;stat.tcp_connections++;return tls_handshake();}voidhandle_connect_error(int err){if (err == EINPROGRESS || err == EAGAIN) {errno = 0;// 继续等待TCP握手//add_to_poll();poll_for_write();return;}if (!stat.tcp_connections)throw Except("cannot establish even one TCP connection");errno = 0;stat.tcp_handshakes--;disconnect();}booltcp_connect_try_finish(){int ret = 0;socklen_t len = 4;if (getsockopt(sd, SOL_SOCKET, SO_ERROR, &ret, &len))throw Except("cannot get a socket connect() status");if (!ret)return handle_established_tcp_conn();handle_connect_error(ret);return false;}booltcp_connect(){sd = socket(g_opt.ip.sin6_family, SOCK_STREAM, IPPROTO_TCP);if (sd < 0)throw Except("cannot create a socket");fcntl(sd, F_SETFL, fcntl(sd, F_GETFL, 0) | O_NONBLOCK);int sz = (g_opt.ip.sin6_family == AF_INET) ? sizeof(sockaddr_in): sizeof(sockaddr_in6);int r = connect(sd, (struct sockaddr *)&g_opt.ip, sz);stat.tcp_handshakes++;state_ = STATE_TCP_CONNECTING;if (!r)return handle_established_tcp_conn();handle_connect_error(errno);return false;}voiddisconnect() noexcept{
...}state_ = STATE_TCP_CONNECT;}
};
...
static int do_getopt(int argc, char *argv[]) noexcept
{
...static struct option long_opts[] = {{"help", no_argument, NULL, 'h'},{"debug", no_argument, NULL, 'd'},{"quiet", no_argument, NULL, 'q'},{"to", no_argument, NULL, 'T'},{"tls", required_argument, NULL, 'V'},{"tickets", required_argument, NULL, 'K'},{"keylogfile", required_argument, NULL, 'F'},{"sni", required_argument, NULL, 's'},{0, 0, 0, 0}};while ((c = getopt_long(argc, argv, "hl:c:C:dqt:n:T:", long_opts, &o))!= -1){switch (c) {case 0:break;case 'c':g_opt.cipher = optarg;break;case 'C':g_opt.curve = optarg;break;case 'd':g_opt.debug = true;break;case 'q':g_opt.quiet = true;break;case 'l':g_opt.n_peers = atoi(optarg);break;case 't':g_opt.n_threads = atoi(optarg);if (g_opt.n_threads > 512) {std::cerr << "ERROR: too many threads requested"<< std::endl;exit(2);}break;case 'n':g_opt.n_hs = atoi(optarg);break;case 'T':g_opt.timeout = atoi(optarg);break;case 'V':if (!strncmp(optarg, "1.2", 4)) {g_opt.tls_vers = TLS1_2_VERSION;} else if (!strncmp(optarg, "1.3", 4)) {g_opt.tls_vers = TLS1_3_VERSION;} else if (!strncmp(optarg, "any", 4)) {g_opt.tls_vers = TLS_ANY_VERSION;}else {std::cout << "Unknown TLS version, fallback to"" 1.2\n" << std::endl;g_opt.tls_vers = TLS1_2_VERSION;}break;case 'K':if (!strncmp(optarg, "on", 3)) {g_opt.use_tickets = true;} else if (!strncmp(optarg, "off", 4)) {g_opt.use_tickets = false;} else if (!strncmp(optarg, "advertise", 10)) {g_opt.use_tickets = false;g_opt.adv_tickets = true;}else {std::cout << "Unknown TLS version, fallback to"" 1.2\n" << std::endl;g_opt.tls_vers = TLS1_2_VERSION;}break;case 'F':g_opt.keylogfile = optarg;break;case 's':g_opt.sni = optarg;break;case 'h':default:usage();return 1;}}if (g_opt.keylogfile) {// 不要丢弃以前保存的密钥bio_keylog = BIO_new_file(g_opt.keylogfile, "a");if (!bio_keylog) {std::cerr << "Error writing keylog file '"<< g_opt.keylogfile << "'" << std::endl;return -ENOENT;}}if (optind != argc && optind + 2 != argc) {std::cerr << "\nERROR: either 0 or 2 arguments are allowed: "<< "none for defaults or address and port."<< std::endl;usage();return -EINVAL;}if (optind >= argc) {parse_ipv4("127.0.0.1", "443");return 0;}const char *addr_str = argv[optind];const char *port_str = argv[++optind];if (parse_ipv4(addr_str, port_str) && parse_ipv6(addr_str, port_str)) {std::cerr << "ERROR: can't parse ip address from string '"<< addr_str << "'" << std::endl;return -EINVAL;}return 0;
}int main(int argc, char *argv[])
{
...if ((r = do_getopt(argc, argv))) {BIO_free_all(bio_keylog);return r;}if (!g_opt.quiet)print_settings();update_limits();signal(SIGTERM, sig_handler);signal(SIGINT, sig_handler);SSL_library_init();SSL_load_error_strings();std::vector<std::thread> thr(g_opt.n_threads);for (auto i = 0; i < g_opt.n_threads; ++i) {dbg << "spawn thread " << (i + 1) << std::endl;thr[i] = std::thread([]() {try {io_loop();}catch (Except &e) {std::cerr << "ERROR: " << e.what() << std::endl;exit(1);}lat_stat.dump();});}auto start_t(steady_clock::now());stat.start_count();while (!end_of_work()) {std::this_thread::sleep_for(std::chrono::seconds(1));statistics_update();auto now(steady_clock::now());auto dt = duration_cast<seconds>(now - start_t).count();if (g_opt.timeout && g_opt.timeout <= dt)finish = true;}for (auto &t : thr)t.join();statistics_dump();BIO_free_all(bio_keylog);return 0;
}

If you need the complete source code, please add the WeChat number (c17865354792)

主要功能:

  1. 仅执行TLS握手:专注于TLS握手过程,快速重置TCP连接,不进行数据传输或重协商。
  2. 多线程与高效I/O:采用基于epoll()的I/O处理,实现了多线程,提高了效率。这对于需要在客户端进行昂贵加密计算的ECC握手尤为重要。
  3. 丰富的统计信息:提供了详尽的统计数据,帮助用户深入了解测试结果。

参数说明

  • -h, --help:显示帮助信息并退出。
  • -d, --debug:开启调试模式。
  • -q, --quiet:减少运行时的统计信息显示。
  • -l <N>:每个线程的并行连接数限制(默认:1)。
  • -n <N>:建立的总握手次数。
  • -t <N>:线程数(默认:1)。
  • -T, --to:测试持续时间(秒)。
  • -c <cipher>:强制选择密码套件。
  • -C <curve>:强制选择椭圆曲线算法的特定曲线。
  • -V, --tls <version>:设置握手的TLS版本。
  • -K, --tickets <mode>:处理TLS会话票证和会话恢复。
  • -F, --keylogfile <f>:将密钥转储到文件,供流量分析器使用。
  • -s, --sni <servernameindicator>:指定要使用的SNI。

示例

以下是一些使用示例:

  • 运行时减少统计信息的显示

      ./perf -q 192.168.1.1 443
    
  • 强制选择特定的密码套件

   ./perf -c "ECDHE-RSA-AES128-GCM-SHA256" 192.168.1.1 443
  • 通过8个线程,每个线程100个并发连接,测试10秒内的TLS v1.3握手:

    ./perf -T 10 -l 100 -t 8 --tls 1.3 192.168.76.7 8081
    
  • 测试100次握手,让OpenSSL自行决定TLS版本和密码套件:

    ./perf -n 100 --tls any ::1 8081
    
  • 设置握手的TLS版本(‘1.2’, ‘1.3’ 或 ‘any’)

    ./perf -V 1.3 192.168.1.1 443
    
  • 将密钥转储到文件,供流量分析器使用

./perf -F keys.log 192.168.1.1 443
  • 指定要使用的服务器名称指示(SNI)
./tperf -s "baidu.com" 192.168.1.1 443

结论

TLS握手性能测试工具,它可以帮助我们评估服务器在处理TLS握手时的性能。通过模拟大量客户端连接,它可以揭示服务器在高负载下的行为,从而为性能优化提供依据。

We also undertake the development of program requirements here. If necessary, please follow the WeChat official account 【程序猿编码】and contact me

这篇关于TLS握手性能测试工具:快速重置、多线程与高级统计分析(C/C++代码实现)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1128722

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi