当前位置: 首页 > news >正文

【仿Mudou库one thread per loop式并发服务器实现】项目介绍+前置技术知识点

HTTP协议模块实现

  • 1. 项目实现的目标
  • 2. 项目储备知识
    • 2.1 HTTP服务器
    • 2.2 Reactor模型
  • 3. 功能模块划分
    • 3.1 SERVER模块
      • 3.1.1 Buffer模块
      • 3.1.2 Socket模块
      • 3.1.3 Channel模块
      • 3.1.4 Poller模块
      • 3.1.5 EventLoop模块
      • 3.1.6 Connection模块
      • 3.1.7 7. Acceptor模块
      • 3.1.8 TimerQueue模块
      • 3.1.9 通信模块总结
      • 3.1.10 TcpServer模块
    • 3.2 HTTP协议模块
      • 3.2.1 Util模块
      • 3.2.2 HttpRequest模块
      • 3.2.3 HttpResponse模块
      • 3.2.3 HttpContext模块
      • 3.2.4 HttpServer模块
  • 4. 项目前置知识技术点
    • 4.1 C++11中的bind
    • 4.2 简单的秒级定时任务实现
    • 4.3 正则库的简单使用
    • 4.5 通用类型any类型的实现

在这里插入图片描述

点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃

1. 项目实现的目标

仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器这个项目是通过咱们实现的高并发服务器组件,可以简洁快速的完成一个高性能的服务器搭建。并且,通过组件内提供的不同应用层协议支持,也可以快速完成一个高性能应用服务器的搭建。在这里,要明确的是咱们要实现的是一个高并发服务器组件,因此当前的项目中并不包含实际的业务内容。

2. 项目储备知识

2.1 HTTP服务器

HTTP(Hyper Text Transfer Protocol),超文本传输协议是应用层协议,是一种简单的请求-响应协议(客户端根据自己的需要向服务器发送请求,服务器针对请求提供服务,完毕后通信结束)。

协议细节在linux网络部分有详细介绍,这里不在赘述。但是需要注意的是HTTP协议是一个运行在TCP协议之上的应用层协议,这一点本质上是告诉我们,HTTP服务器其实就是个TCP服务器,只不过在应用层基于HTTP协议格式进⾏数据的组织和解析来明确客户端的请求并完成业务处理。

因此实现HTTP服务器简单理解,只需要以下几步即可

  1. 搭建一个TCP服务器,接收客户端请求。
  2. 以HTTP协议格式进⾏解析请求数据,明确客户端目的。
  3. 明确客户端请求目的后提供对应服务。
  4. 将服务结果以HTTP协议格式进行组织,发送给客户端

实现一个HTTP服务器很简单,但是实现一个高性能的服务器并不简单,这个项目中将讲解基于Reactor模式的高性能服务器实现。

当然准确来说,因为我们要实现的服务器本身并不存在业务,咱们要实现的应该算是一个高性能服务器基础库,是一个基础组件。

2.2 Reactor模型

Reactor 模式,是指通过一个或多个输入同时传递给服务器进行请求处理时的事件驱动处理模式。

服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,Reactor 模式也叫Dispatcher 模式。

简单理解就是使用 I/O多路复用 统一监听事件,收到事件后分发给处理进程或线程,是编写高性能网络服务器的必备技术之一。

分类

单Reactor单线程:单I/O多路复用+业务处理

  1. 通过IO多路复用模型进行客户端请求监控
  2. 触发事件后,进行事件处理
    • a. 如果是新建连接请求,则获取新建连接,并添加至多路复用模型进行事件监控。
    • b. 如果是数据通信请求,则进行对应数据处理(接收数据,处理数据,发送响应)。

优点:所有操作均在同⼀线程中完成,思想流程较为简单,不涉及进程/线程间通信及资源争抢问题。

缺点:无法有效利用CPU多核资源,很容易达到性能瓶颈。
适用场景:适用于客户端数量较少,且处理速度较为快速的场景。(处理较慢或活跃连接较多,会导致串行处理的情况下,后处理的连接长时间无法得到响应)。

在这里插入图片描述

单Reactor多线程:单I/O多路复用+线程池(业务处理)

  1. Reactor线程通过I/O多路复用模型进行客户端请求监控
  2. 触发事件后,进行事件处理
    • a. 如果是新建连接请求,则获取新建连接,并添加至多路复用模型进行事件监控。
    • b. 如果是数据通信请求,则接收数据后分发给Worker线程池进行业务处理。
    • c. 工作线程处理完毕后,将响应交给Reactor线程进行数据响应

优点:充分利用CPU多核资源

缺点:多线程间的数据共享访问控制较为复杂,单个Reactor 承担所有事件的监和响应,在单线程中运行,高并发场景下容易成为性能瓶颈。

在这里插入图片描述

多Reactor多线程:多I/O多路复用+线程池(业务处理)

  1. 在主Reactor中处理新连接请求事件,有新连接到来则分发到子Reactor中监控
  2. 在子Reactor中进行客户端通信监控,有事件触发,则接收数据分发给Worker线程池
  3. Worker线程池分配独立的线程进行具体的业务处理
    • a. 工作线程处理完毕后,将响应交给子Reactor线程进行数据响应

优点:充分利用CPU多核资源,主从Reactor各司其职

在这里插入图片描述

咱们要实现的是主从Reactor模型服务器,也就是主Reactor线程仅仅监控监听描述符,获取新建连接,保证获取新连接的高效性,提高服务器的并发性能。

主Reactor获取到新连接后分发给子Reactor进行通信事件监控。而子Reactor线程监控各自的描述符的读写事件进行数据读写以及业务处理。

One Thread One Loop的思想就是把所有的操作都放到一个线程中进行,一个线程对应一个事件处理的循环。

当前实现中,因为并不确定组件使用者的使用意向,因此并不提供业务层工作线程池的实现,只实现主从Reactor,而Worker工作线程池,可由组件库的使用者的需要自行决定是否使用和实现。

在这里插入图片描述

3. 功能模块划分

基于以上的理解,我们要实现的是一个带有协议支持的Reactor模型高性能服务器,因此将整个项目的实现划分为两个大的模块:

  • SERVER模块:实现Reactor模型的TCP服务器;
  • 协议模块:对当前的Reactor模型服务器提供应用层协议支持

3.1 SERVER模块

SERVER模块就是对所有的连接以及线程进行管理,让它们各司其职,在合适的时候做合适的事,最终 完成高性能服务器组件的实现。

而具体的管理也分为三个方面:

  • 监听连接管理:对监听连接进行管理。
  • 通信连接管理:对通信连接进行管理。
  • 超时连接管理:对超时连接进行管理。

基于以上的管理思想,将这个模块进行细致的划分又可以划分为多个子模

3.1.1 Buffer模块

Buffer模块是一个缓冲区模块,用于实现通信中用户态的接收缓冲区和发送缓冲区功能。
在这里插入图片描述

3.1.2 Socket模块

Socket模块是对套接字操作封装的一个模块,主要实现的socket的各项操作。

在这里插入图片描述

3.1.3 Channel模块

Channel模块是对一个描述符需要进行的IO事件管理的模块,实现对描述符可读,可写,错误…事件的管理操作,以及Poller模块对描述符进行IO事件监控就绪后,根据不同的事件,回调不同的处理函数功能。

在这里插入图片描述

3.1.4 Poller模块

Poller模块是对epoll进行封装的一个模块,主要实现epoll的IO事件添加,修改,移除,获取活跃连接功能。

在这里插入图片描述

3.1.5 EventLoop模块

EventLoop模块可以理解就是我们上边所说的Reactor模块,它是对Poller模块,TimerQueue模块,进行所有描述符的事件监控。

EventLoop模块是一个EventLoop对象对应一个线程的模块,线程内部的目的就是运行EventLoop的启动函数。

EventLoop模块为了保证整个服务器的线程安全问题,因此要求使用者对于Connection的所有操作一定要在其对应的EventLoop线程内完成,不能在其他线程中进行(比如组件使用者使用Connection发送数据,以及关闭连接这种操作)。

EventLoop模块保证自己内部所监控的所有描述符,都要是活跃连接,非活跃连接就要及时释放避免资源浪费。

  • EventLoop模块内部包含有一个eventfd:eventfd其实就是linux内核提供的一个事件fd,专门用于事件通知。
  • EventLoop模块内部包含有一个Poller对象:用于进行描述符的IO事件监控。
  • EventLoop模块内部包含有一个TimerQueue对象:用于进行定时任务的管理。
  • EventLoop模块内部包含有一个PendingTask队列:组件使用者将对Connection进行的所有操作,都加入到任务队列中,由EventLoop模块进行管理,并在EventLoop对应的线程中进行执行。
  • 每一个Connection对象都会绑定到一个EventLoop上,这样能保证对这个连接的所有操作都是在一个线程中完成的

具体操作流程:

  1. 通过Poller模块对当前模块管理内的所有描述符进行IO事件监控,有描述符事件就绪后,通过描述符对应的Channel进行事件处理。
  2. 所有就绪的描述符IO事件处理完毕后,对任务队列中的所有操作顺序进行执行。
  3. 由于epoll的事件监控,有可能会因为没有事件到来而持续阻塞,导致任务队列中的任务不能及时得到执行,因此创建了eventfd,添加到Poller的事件监控中,用于实现每次向任务队列添加任务的时候,通过向eventfd写⼊数据来唤醒epoll的阻塞。

在这里插入图片描述

3.1.6 Connection模块

Connection模块是对Buffer模块,Socket模块,Channel模块的一个整体封装,实现了对一个通信套接字的整体的管理,每一个进行数据通信的套接字(也就是accept获取到的新连接)都会使用Connection进行管理。

  • Connection模块内部包含有三个由组件使用者传入的回调函数:连接建立完成的回调,任意事件的回调,新数据来到的回调,关闭连接的回调。

  • Connection模块内部包含有两个组件使用者提供的接口:数据发送接口,连接关闭接口

  • Connection模块内部包含有两个用户态缓冲区:用户态接收缓冲区,用户态发送缓冲区

  • Connection模块内部包含有一个Socket对象:完成描述符面向系统的IO操作

  • Connection模块内部包含有一个Channel对象:完成描述符IO事件就绪的处理

具体处理流程如下:

  1. 实现向Channel提供可读,可写,错误等不同事件的IO事件回调函数,然后将Channel和对应的描述符添加到Poller事件监控中。
  2. 当描述符在Poller模块中就绪了IO可读事件,则调用描述符对应Channel中保存的读事件处理函数,进行数据读取,将socket接收缓冲区全部读取到Connection管理的用户态接收缓冲区中。然后调用由组件使用者传入的新数据到来回调函数进行处理。
  3. 组件使者者进行数据的业务处理完毕后,通过Connection向使用者提供的数据发送接⼝,将数据写入Connection的发送缓冲区中。
  4. 启动描述符在Poll模块中的IO写事件监控,就绪后,调用Channel中保存的写事件处理函数,将发送缓冲区中的数据通过Socket进行面向系统的实际数据发送。

在这里插入图片描述

3.1.7 7. Acceptor模块

Acceptor模块是对Socket模块,Channel模块的一个整体封装,实现了对一个监听套接字的整体的管理。

  • Acceptor模块内部包含有一个Socket对象:实现监听套接字的操作
  • Acceptor模块内部包含有一个Channel对象:实现监听套接字IO事件就绪的处理

具体处理流程如下:

  1. 实现向Channel提供可读事件的IO事件处理回调函数,函数的功能其实也就是获取新连接
  2. 为新连接构建一个Connection对象出来

在这里插入图片描述

3.1.8 TimerQueue模块

TimerQueue模块是实现固定时间定时任务的模块,可以理解就是要给定时任务管理器,向定时任务管理器中添加一个任务,任务将在固定时间后被执行,同时也可以通过刷新定时任务来延迟任务的执行。

这个模块主要是对Connection对象的生命周期管理,对非活跃连接进行超时后的释放功能。

TimerQueue模块内部包含有一个timerfd:linux系统提供的定时器。
TimerQueue模块内部包含有一个Channel对象:实现对timerfd的IO时间就绪回调处理。

在这里插入图片描述

3.1.9 通信模块总结

通过Accpect模块创建一个监听套接字listsock,并给listsock对应的channel设置读事件回调,然后添加到主EventLoop线程中的Poller添加读事件监控,当读事件就绪后,通过Accpect模块给listsock设置读回调函数获取新连接,获取新连接之后,然后在调用主EventLoop线程给它设置的新连接获取之后的回调函数,创建一个Connection对象,它内部也有对应的Channel对象,创建Connection对象后它构造函数内部对应的Channel对象已经设置好对应的读、写、错误、挂断事件的回调,所以就不用管。然后设置给这个Connection对象分配给某个从EventLoop线程,并且给它设置调用者设置的连接建立好后的回调、新数据来了后的回调,任意事件的回调、关闭连接的回调。再看是否启动非活跃连接,然后给Connection启动读事件监控。

如果该Connection对应的fd读事件就绪后,就可以通过管理该Connection的从EventLoop中的Poller模块中拿到,然后调用给它内部Channel设置读事件回调,从Connection中的Socket模块读取数据到Buffer模块接收缓存区,有新数据就调用设置好的新数据来了之后的回调函数。如果业务处理好之后,把返回的数据写到Buffer输出缓存区,然后将该fd写事件的关心添加到管理它的从EventLoop中的Poller中,等下次写事件就绪之后,就调用对应Channel写事件回调将Buffer输出缓存区的数据通过Connection中的socket发送给对方。

此时就完成了一次往返通信的过程。

在这里插入图片描述

3.1.10 TcpServer模块

这个模块是一个整体Tcp服务器模块的封装,内部封装了Acceptor模块,EventLoopThreadPool模块。

  • TcpServer中包含有一个EventLoop对象:以备在超轻量使用场景中不需要EventLoop线程池,只需要在主线程中完成所有操作的情况。

  • TcpServer模块内部包含有一个EventLoopThreadPool对象:其实就是EventLoop线程池,也就是子Reactor线程池

  • TcpServer模块内部包含有一个Acceptor对象:一个TcpServer服务器,必然对应有⼀个监听套接字,能够完成获取客⼾端新连接,并处理的任务。

  • TcpServer模块内部包含有一个std::shared_ptr的hash表:保存了所有的新建连接对应的Connection,注意,所有的Connection使用shared_ptr进行管理,这样能够保证在hash表中删除了Connection信息后,在shared_ptr计数器为0的情况下完成对Connection资源的释放操作

具体操作流程如下:

  1. 在实例化TcpServer对象过程中,完成BaseLoop(主Reactor)的设置,Acceptor对象的实例化,以及EventLoop线程池的实例化,以及std::shared_ptr的hash表的实例化。
  2. 为Acceptor对象设置回调函数:获取到新连接后,为新连接构建Connection对象,设置Connection的各项回调,并使用shared_ptr进行管理,并添加到hash表中进行管理,并为Connection选择一个EventLoop线程,为Connection添加一个定时销毁任务,为Connection添加事件监控,
  3. 启动BaseLoop进行监听套接字事件监控。

在这里插入图片描述

3.2 HTTP协议模块

HTTP协议模块用于对高并发服务器模块进行协议⽀持,基于提供的协议支持能够更方便的完成指定协议服务器的搭建。

而HTTP协议支持模块的实现,可以细分为以下几个模块

3.2.1 Util模块

这个模块是一个工具模块,主要提供HTTP协议模块所用到的一些工具函数,比如url编解码,文件读写…等。

3.2.2 HttpRequest模块

这个模块是HTTP请求数据模块,用于保存HTTP请求数据被解析后的各项请求元素信息。

3.2.3 HttpResponse模块

这个模块是HTTP响应数据模块,用于业务处理后设置并保存HTTP响应数据的的各项元素信息,最终会被按照HTTP协议响应格式组织成为响应信息发送给客户端。

3.2.3 HttpContext模块

这个模块是一个HTTP请求接收的上下文模块,主要是为了防止在一次接收的数据中,不是一个完整的HTTP请求,则解析过程并未完成,无法进行完整的请求处理,需要在下次接收到新数据后继续根据上下文进行解析,最终得到⼀个HttpRequest请求信息对象,因此在请求数据的接收以及解析部分需要一个上下文来进行控制接收和处理节奏。

3.2.4 HttpServer模块

这个模块是最终给组件使用者提供的HTTP服务器模块了,用于以简单的接口实现HTTP服务器的搭建。

HttpServer模块内部包含有⼀个TcpServer对象:TcpServer对象实现服务器的搭建

HttpServer模块内部包含有两个提供给TcpServer对象的接口:连接建立成功设置上下文接口,数据处理接口。

HttpServer模块内部包含有一个hash-map表存储请求与处理函数的映射表:组件使用者向HttpServer设置哪些请求应该使用哪些函数进行处理,等TcpServer收到对应的请求就会使用对应的函数进行处理。

4. 项目前置知识技术点

4.1 C++11中的bind

bind (Fn&& fn, Args&&... args)

我们可以将bind接口看作是一个通用的函数适配器,它接受一个函数对象,以及函数的各项参数,然后返回一个新的函数对象,但是这个函数对象的参数已经被绑定为设置的参数。运行的时候相当于总是调用传入固定参数的原函数。

但是如果进行绑定的时候,给与的参数为 std::placeholders::_1, _2... 则相当于为新适配生成的函数对象的调用预留一个参数进行传递。

#include <iostream>
#include <string>
#include <vector>
#include <functional>void print(const std::string &str, int num)
{std::cout << str << num << std::endl;
}int main()
{using Task = std::function<void()>;std::vector<Task> arry;arry.push_back(std::bind(print, "hello", 10));arry.push_back(std::bind(print, "leihou", 20));arry.push_back(std::bind(print, "nihao", 30));for (auto &f:arry) {f();}return 0;
}

基于bind的作用,当我们在设计一些线程池,或者任务池的时候,就可以将将任务池中的任务设置为函数类型,函数的参数由添加任务者直接使用bind进行适配绑定设置,而任务池中的任务被处理,只需要取出一个个的函数进行执行即可。

这样做有个好处就是,这种任务池在设计的时候,不用考虑都有哪些任务处理方式了,处理函数该如何设计,有多少个什么样的参数,这些都不用考虑了,降低了代码之间的耦合度。

4.2 简单的秒级定时任务实现

在当前的高并发服务器中,我们不得不考虑一个问题,那就是连接的超时关闭问题。我们需要避免一个连接长时间不通信,但是也不关闭,空耗资源的情况。

这时候我们就需要一个定时任务,定时的将超时过期的连接进行释放。

Linux提供给我们的定时器

#include <iostream>
#include <sys/timerfd.h>
#include <stdint.h>
#include <unistd.h>//linux下的定时器//创建定时器
// int timerfd_create(int clockid, int flags);//clockid:
//CLOCK_REALTIM   -- 以系统时间作为记时基准值(如果系统时间发生改变就会出问题)
//CLOCK_MONOTONIC -- 以系统启动时间进行递增的一个基准值(定时不会随着系统时间改变而改变)//flags:对定时器读取操作是阻塞/非阻塞 0 默认阻塞操作
//linux下一切皆文件,定时器的操作跟文件操作没什么区别
//定时器定时原理,每隔一段时间(定时器的超时时间),系统就会给这个文件描述符对应的定时器写入一个8字节的数据
//没到时间就阻塞,到时间就可以读取成功了//启动定时器
//int timerfd_settime(int fd, int flags,   
//        const struct itimerspec *new_value,     
//       struct itimerspec *_Nullable old_value);//fd: timerfd_create返回值
// flags: 0-相对时间, 1-绝对时间;默认设置为0即可.
//  new: ⽤于设置定时器的新超时时间
//  old: ⽤于接收原来的超时时间//  struct timespec {
//      time_t tv_sec; /* Seconds */  秒
//      long tv_nsec; /* Nanoseconds */ 纳秒
//  };
//
//  struct itimerspec {
//      struct timespec it_interval; /* 第⼀次之后的超时间隔时间 */
//      struct timespec it_value; /* 第⼀次超时时间 */
//  };int main()
{int timefd = timerfd_create(CLOCK_MONOTONIC,0);if(timefd < 0){std::cout<<"open file fail"<<std::endl;return -1;}struct itimerspec item;item.it_value.tv_sec = 1; item.it_value.tv_nsec = 0; //第一次超时时间item.it_interval.tv_sec = 1;item.it_interval.tv_nsec = 0;//第⼀次之后的超时间隔时间timerfd_settime(timefd,0,&item,nullptr);//sleep(10);while(1){uint64_t cnt = 0;int ret = read(timefd,&cnt,8);if(ret < 0){std::cout<<"read fail"<<std::endl;break;}std::cout<<"超时了,距离上次超时次数: "<<cnt<<std::endl;}close(timefd);return 0;
}

上边例子,是一个定时器的使用示例,是每隔1s钟触发一次定时器超时。

基于这个例子,我们可以实现每隔n秒,检测一下哪些连接超时了,然后将超时的连接释放掉。

时间轮思想

上述的例子,存在一个很大的问题,每次超时都要将所有的连接遍历一遍,如果有上万个连接,效率无疑是较为低下的。

这时候大家就会想到,我们可以针对所有的连接,根据每个连接最近一次通信的系统时间建立一个小根堆,这样只需要每次针对堆顶部分的连接逐个释放,直到没有超时的连接为止,这样也可以大大提高处理的效率。

上述方法可以实现定时任务,但是这立给大家介绍另一种方案:时间轮

时间轮的思想来源于钟表,如果我们定了一个3点钟的闹铃,则当时针走到3的时候,就代表时间到了。

同样的道理,如果我们定义了一个数组,并且有一个指针,指向数组起始位置,这个指针每秒钟向后走动一步,走到哪里,则代表哪里的任务该被执行了,那么如果我们想要定一个3s后的任务,则只需要将任务添加到tick+3位置,则每秒中走一步,三秒钟后tick走到对应位置,这时候执行对应位置的任务即可。

但是,同一时间可能会有大批量的定时任务,因此我们可以给数组对应位置下拉一个数组,这样就可以在同一个时刻上添加多个定时任务了。

在这里插入图片描述

当然,上述操作也有一些缺陷,比如我们如果要定义一个60s后的任务,则需要将数组的元素个数设置为60才可以,如果设置一小时后的定时任务,则需要定义3600个元素的数组,这样无疑是比较麻烦的。

因此,可以采用多层级的时间轮,有秒针轮,分针轮,时针轮, 60<time<3600则time/60就是分针轮对应存储的位置,当tick/3600等于对应位置的时候,将其位置的任务向分针,秒针轮进行移动。

在这里插入图片描述

因为当前我们的应用中,倒是不用设计的这么麻烦,因为我们的定时任务通常设置的30s以内,所以简单的秒级时间轮就够用了。

但是,我们也得考虑一个问题,当前的设计是时间到了,则主动去执行定时任务,释放连接,那能不能在时间到了后,自动执行定时任务呢,这时候我们就想到一个操作—类的析构函数一个类的析构函数,在对象被释放时会自动被执行,那么我们如果将一个定时任务作为一个类的析构函数内的操作,则这个定时任务在对象被释放的时候就会执行。

但是仅仅为了这个目的,而设计一个额外的任务类,好像有些不划算,但是,这里我们又要考虑另一个问题,那就是假如有一个连接立成功了,我们给这个连接设置了一个30s后的定时销毁任务,但是在第10s的时候,这个连接进行了一次通信,那么我们应该时在第30s的时候关闭,还是第40s的时候关闭呢?无疑应该是第40s的时候。也就是说,这时候,我们需要让这个第30s的任务失效,但是我们该如何实现这个操作呢?

这里,我们就用到了智能指针shared_ptr,shared_ptr有个计数器,当计数为0的时候,才会真正释放一个对象,那么如果连接在第10s进行了一次通信,则我们继续向定时任务中,添加一个30s后(也就是第40s)的任务类对象的shared_ptr,则这时候两个任务shared_ptr计数为2,则第30s的定时任务被释放的时候,计数-1,变为1,并不为0,则并不会执行实际的析构函数,那么就相当于这个第30s的任务失效了,只有在第40s的时候,这个任务才会被真正释放。

#include <iostream>
#include <stdint.h>
#include <functional>
#include <vector>
#include <memory>
#include <unordered_map>
#include <unistd.h>using TaskFunc = std::function<void()>;
using ReleaseFunc  = std::function<void()>;class TimerTask
{
private:uint64_t _id;   // 定时器任务对象IDuint32_t _timeout; //定时任务的超时时间bool _cancel;      // 定时任务是否取消,false不取消,true取消TaskFunc _task_cb;      // 定时器对象要执行的定时任务ReleaseFunc _release_cb;   /// 用于删除TimerWheel中保存的定时器对象信息public:TimerTask(uint64_t id,uint32_t time,const TaskFunc& cb):_id(id),_timeout(time),_task_cb(cb),_cancel(false){}~TimerTask(){if(_cancel == false){_task_cb();}_release_cb();}uint32_t DelayTime(){return _timeout;}void SetRelease(const ReleaseFunc & rb){_release_cb = rb;}void Cancel(){_cancel = true;}};using SharedPtr = std::shared_ptr<TimerTask>;
using WeakPtr = std::weak_ptr<TimerTask>;class TimeWheel
{
private:int _tick; //滴答指针,当前的秒,走到哪里释放哪里,释放哪里,就相当于执行哪里的任务int _capacity; //表盘最大数量---其实就是最大延迟时间std::vector<std::vector<SharedPtr>> _wheel; //时间轮std::unordered_map<uint64_t,WeakPtr> _timers;//通过id找到任务对象,方便后续刷新任务//unordered_map第二个参数不能用shared_ptr,如果还用用shared_ptr去指向原始对象,那么就和之前用shared_ptr指向原始对象//而产生的引用计数根本就不是同一个引用计数,这样如果unordered_map第二个参数引用计数是1,而其他指向它引用计数是2//万一unordered_map第二个参数shared_ptr被释放了,这个原始对象就会被释放,这是由问题的.//释放时间轮上任务对象void RemoveTimer(uint64_t id){auto it = _timers.find(id);if(it == _timers.end()){return;}_timers.erase(it);}public:TimeWheel():_tick(0),_capacity(60),_wheel(_capacity){}~TimeWheel(){}//设置定时任务void TimerAdd(uint64_t id,uint32_t delay,const TaskFunc& cb){SharedPtr spt(new TimerTask(id,delay,cb));spt->SetRelease(std::bind(&TimeWheel::RemoveTimer,this,id));int pos = (_tick + delay) % _capacity;_wheel[pos].push_back(spt);_timers[id] = WeakPtr(spt);}//刷新/延迟定时任务void TimerRefresh(uint64_t id){auto it = _timers.find(id);if(it == _timers.end()){return;}//spt是临时对象,出栈后会自动销毁,不会增加引用计数SharedPtr spt = it->second.lock();//lock获取weak_ptr管理的对象对应的shared_ptrint delaytime = spt->DelayTime();int pos = (_tick + delaytime) % _capacity;_wheel[pos].push_back(spt);}//这个函数应该每秒钟被执行一次,相当于秒针向后走了一步void RunTimerTask(){_tick = (_tick + 1) % _capacity;//清空指定位置的数组,就会把数组中保存的所有管理定时器对象的shared_ptr释放掉_wheel[_tick].clear();//执行任务,释放shared_ptr指针,当引用计数到0,调用Task析构,执行定时任务}//取消定时任务,等时间到了,释放任务对象,但是析构里面的超时任务不去执行void TimerCancel(uint64_t id){auto it = _timers.find(id);if(it == _timers.end()){return;}//spt是临时对象,出栈后会自动销毁,不会增加引用计数SharedPtr spt = it->second.lock();if(spt) spt->Cancel();}};class Test {public:Test() {std::cout << "构造" << std::endl;}~Test() {std::cout << "析构" << std::endl;}
};void DelTest(Test *t) {delete t;
}int main()
{TimeWheel tw;Test *t = new Test();tw.TimerAdd(888, 5, std::bind(DelTest, t));for(int i = 0; i < 5; i++) {sleep(1);tw.TimerRefresh(888);//刷新定时任务tw.RunTimerTask();//向后移动秒针std::cout << "刷新了一下定时任务,重新需要5s中后才会销毁\n";}tw.TimerCancel(888);while(1) {sleep(1);std::cout << "-------------------\n";tw.RunTimerTask();//向后移动秒针}return 0;
}

4.3 正则库的简单使用

正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等

正则表达式的使用,可以使得HTTP请求的解析更加简单(这用指的时程序员的工作变得的简单,这并不代表处理效率会变高,实际上效率上是低于直接的字符串处理的),使我们实现的HTTP组件库使用起来更加灵活。

#include <iostream>
#include <regex>
#include <string>int main()
{//HTTP请求行格式:  GET /bitejiuyeke/login?user=xiaoming&pass=123123 HTTP/1.1\r\n//std::string str = "GET /bitejiuyeke/login?user=xiaoming&pass=123123 HTTP/1.1";std::smatch matches;//请求方法的匹配  GET HEAD POST PUT DELETE ....//std::regex e("(GET|HEAD|POST|PUT|DELETE) .*");//首先字符串格式一定要匹配,然后才能提取内容//std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*).*");//std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)\\?(.*) .*");//std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)\\?(.*) (HTTP/1\\.[01])");//std::string str = "GET /bitejiuyeke/login?user=xiaoming&pass=123123 HTTP/1.1\r\n";std::string str = "GET /bitejiuyeke/login HTTP/1.1\r\n" ;std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:n|\r\n)?", std::regex::icase);//() 表示捕获组,满足里面条件就捕获,不符合条件捕获组就为空//(?:) 表示非捕获组,即使满足条件但也不捕获,不满足更不捕获// ? 表示匹配前面表达式0次或者1次//()? 表示满足()里条件就只去匹配一次,并且符合条件就去捕获,不符合条件捕获组为空,不满足()里条件捕获组也是为空//(?:)? 表示满足或者不满足条件都不捕获//(?:\\?())? 表示有以?作为开头的字符串,满足()里面条件就去捕获?后面的,不满足()捕获组为空// . 点匹配除"\n"和"\r"之外的任何单个字符// [] 只允许字符串中一个字符,去匹配[]括号里面的任意一个字符, \r [\r\n] 可以, \r\n [\r\n] 不行// GET|HEAD|POST|PUT|DELETE   |表示匹配并提取其中任意一个字符串// [^?]*     [^?]匹配非问号字符, 后边的*表示0次或多次, +表示1次或多次// \\?(.*)   \\?  表示原始的?字符 (.*)表示提取?之后的任意字符0次或多次,直到遇到空格/*(?:\\?(.*?))? 最后的?表⽰匹配前边的表达式0次或1次,因为有的请求可能没有查询字符串(?:\\?(.*?)) (?:pattern)表⽰匹配pattern但是不获取匹配结果\\?(.*?) \\?表⽰原始的?字符,这⾥表⽰以?字符作为起始. 表⽰\n之外任意单字符,* 表⽰匹配前边的字符0次或多次, ?跟在*或+之后表⽰懒惰模式, 也就是说以?开始的字符串就只匹配这⼀次就⾏,后边还有以?开始的同格式字符串也不不会匹配() 表⽰捕捉获取符合内部格式的数据合并起来表⽰的就是,匹配以?开始的字符串,但是字符串整体不要,只捕捉获取?之后的字符串,且只匹配⼀次,就算后边还有以?开始的同格式字符串也不不会匹配*/// HTTP/1\\.[01]  表示匹配以HTTP/1.开始,后边有个0或1的字符串//(?:\n|\r\n)?   (?: ...) 表示匹配某个格式字符串,但是不提取, 最后的?表示的是匹配前边的表达式0次或1次(前面有字符串或者没有字符串都可以)/*bool std:regex_match (const std:string &src, std:smatch &matches, std:regex &e)src:原始字符串matches:正则表达式可以从原始字符串中匹配并提取符合某种规则的数据,提取的数据就放在matches中是一个类似于数组的容器e:正则表达式的匹配规则返回值:用于确定匹配是否成功*/bool ret = std::regex_match(str, matches, e);if (ret == false) {return -1;}for(int i = 0; i < matches.size(); ++i){std::cout << i << " : ";std::cout << matches[i] << std::endl;}return 0;
}

4.5 通用类型any类型的实现

每一个Connection对连接进行管理,最终都不可避免需要涉及到应用层协议的处理,因此在Connection中需要设置协议处理的上下文来控制处理节奏。但是应用层协议千千万,为了降低耦合度,这个协议接收解析上下文就不能有明显的协议倾向,它可以是任意协议的上下文信息,因此就需要一个通用的类型来保存各种不同的数据结构。

在C语言中,通⽤类型可以使用void*来管理,但是在C++中,boost库和C++17给我们提供了⼀个通用类型any来灵活使用,如果考虑增加代码的移植性,尽量减少第三方库的依赖,则可以使用C++17特性中的any,或者自己来实现。而这个any通用类型类的实现其实并不复杂,以下是简单的部分实现。

#include <iostream>
#include <typeinfo>
#include <cassert>
#include <unistd.h>
#include <any>/*Any类主要是实现⼀个通⽤类型出来,在c++17和boost库中都有现成的可以使⽤,但是这⾥实现⼀下了解其思想,这样也就避免了第三⽅库的使⽤了*//*⾸先Any类肯定不能是⼀个模板类,否则编译的时候 Any<int> a, Any<float>b,需要传类型作为模板参数,也就是说在使⽤的时候就要确定其类型*//*因此考虑Any内部设计⼀个模板容器placehoder类,可以保存各种类型数据*//*但在Any类中⽆法定义这个placehoder对象或指针,因为any也不知道这个类要保存什么类型的数据,因此⽆法传递类型参数*//*所以,定义⼀个基类让holder,让placeholde继承于让holder,⽽Any类保存⽗类指针即可*//*当需要保存数据时,则new⼀个带有模板参数的⼦类holder对象出来保存数据,然后让Any类中的⽗类指针,指向这个⼦类对象就搞定了*/class Any
{class holder{public:virtual ~holder(){}virtual const std::type_info& type() = 0;virtual holder *clone() = 0;};template<class T>class placeholder : public holder{public:placeholder(const T& val) : _val(val) {}~placeholder(){}// 获取子类对象保存的数据类型virtual const std::type_info& type(){return typeid(T);}// 针对当前的对象自身,克隆出一个新的子类对象virtual holder *clone(){return new placeholder(_val);}public:T _val;};private:holder* _content;public:Any():_content(nullptr){}~Any() { delete _content; }template<class T>Any(const T& val){_content = new placeholder<T>(val);}Any(const Any& other){_content = _content != nullptr ? other._content->clone() : nullptr;}// 返回子类对象保存的数据的指针template<class T>T* Get(){assert(typeid(T) == _content->type());//父类指针指向子类中属于父类的,找不到子类中成员,因此需要转成子类指针return &(((placeholder<T>*)_content)->_val);}Any &swap(Any &other) {std::swap(_content, other._content);return *this;}//赋值运算符的重载函数template<class T>Any& operator=(const T &val) {Any(val).swap(*this);return *this;}Any& operator=(const Any &other){Any(other).swap(*this);return *this;}};class Test{public:Test() {std::cout << "构造" << std::endl;}Test(const Test &t) {std::cout << "拷贝" << std::endl;}~Test() {std::cout << "析构" << std::endl;}
};
int main()
{std::any a;a = 10;int *pi = std::any_cast<int>(&a);std::cout << *pi << std::endl;a = std::string("hello");std::string *ps = std::any_cast<std::string>(&a);std::cout << *ps << std::endl;// Any a;// {//     Test t;//     a = t;// }// a = 10;// int *pa = a.Get<int>();// std::cout << *pa << std::endl;// a = std::string("nihao");// std::string *ps = a.Get<std::string>();// std::cout << *ps << std::endl;while(1) sleep(1);return 0;
}

相关文章:

【仿Mudou库one thread per loop式并发服务器实现】项目介绍+前置技术知识点

HTTP协议模块实现 1. 项目实现的目标2. 项目储备知识2.1 HTTP服务器2.2 Reactor模型 3. 功能模块划分3.1 SERVER模块3.1.1 Buffer模块3.1.2 Socket模块3.1.3 Channel模块3.1.4 Poller模块3.1.5 EventLoop模块3.1.6 Connection模块3.1.7 7. Acceptor模块3.1.8 TimerQueue模块3.1…...

Open Interpreter:重新定义人机交互的开源革命

引言 在人工智能技术蓬勃发展的今天&#xff0c;人机交互的方式正经历着前所未有的变革。Open Interpreter&#xff0c;作为一个开源项目&#xff0c;正在重新定义我们与计算机的互动方式。它允许大型语言模型&#xff08;LLMs&#xff09;在本地运行代码&#xff0c;通过自然…...

Shell编程之条件语句

目录 一.条件测试操作 1.文件测试 2.整数值比较 3.字符串比较 4.逻辑测试 二&#xff1a;if条件语句 1.if语句的结构 &#xff08;1&#xff09;单分支if语句 &#xff08;2&#xff09;双分支if语句 &#xff08;3&#xff09;多分支if语句 2.if语句应用示例 &…...

Python编程快速上手 让繁琐工作自动化笔记

编程基础 字符串使用单引号...

高性能文件上传服务

高性能文件上传服务 —— 您业务升级的不二选择 在当今互联网数据量激增、文件体积日益庞大的背景下&#xff0c;高效、稳定的文件上传方案显得尤为重要。我们的文件分块上传服务端采用业界领先的 Rust HTTP 框架 Hyperlane 开发&#xff0c;凭借其轻量级、低延时和高并发的特…...

【从零开始学习JVM | 第二篇】HotSpot虚拟机对象探秘

对象的创建 1.类加载检查 虚拟机遇到一条new的指令&#xff0c;首先去检查这个指令的参数能否在常量池中定位到这个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有&#xff0c;那必须先执行类的加载过程。 2.分配内存 在类…...

浅谈前端开发中的 npm、cnpm、pnpm、yarn各自特点

在前端开发中的 npm、cnpm、pnpm、yarn 等工具都是包管理器&#xff08;Package Manager&#xff09;&#xff0c;用于安装/更新/卸载 JavaScript 项目的依赖。 下面我详细地给你梳理下这些包管理器的作用、特点和适用场景&#x1f447; 一. npm&#xff08;Node Package Mana…...

【数据结构】包装类和泛型

目录 1.包装类 1.1 基本数据类型和对应的包装类 1.2 装箱和拆箱 1.3 自动装箱和自动拆箱 2.泛型 2.1泛型的概念 2.2引出泛型 3.语法 4.泛型类的使用 5.泛型的上界 1.包装类 在Java中&#xff0c;由于基本类型不是继承自Object&#xff0c;为了在泛型代码中可以支持基…...

红帽9运行容器一

运行容器&#xff1a;容器概念&#xff0c;构建&#xff0c;存储和运行容器的核心技术&#xff08;用户资源管理的控制组&#xff0c;进程隔离的命名空间&#xff0c;加强安全边界的SELinux和Seccomp&#xff09; 软件运行需要环境&#xff0c;系统库&#xff0c;配置文件和服…...

使用poi+itextpdf把word转成pdf

使用 Apache POI 和 iTextPDF 将 Word 转换为 PDF 需要分两步操作&#xff1a;先用 POI 读取 Word 内容&#xff0c;再用 iText 生成 PDF。 apache poi官方文档:Apache POI™ - Javadocs 以下是详细的代码实现示例&#xff1a; 环境准备 在 pom.xml 中添加依赖&#xff1a; …...

民安智库:开启零售行业客户满意度提升新征程​

在当今这个瞬息万变的商业世界中&#xff0c;零售市场的竞争愈发激烈&#xff0c;犹如一场没有硝烟的战争。各大零售企业为了抢占市场份额&#xff0c;纷纷使出浑身解数&#xff0c;从商品种类的丰富到店铺环境的优化&#xff0c;从价格策略的调整到服务质量的提升&#xff0c;…...

自行搭建一个Git仓库托管平台

1.安装Git sudo apt install git 2.Git本地仓库创建&#xff08;自己选择一个文件夹&#xff09; git init 这里我在 /home/test 下面初始化了代码仓库 1. 首先在仓库中新建一个txt文件&#xff0c;并输入一些内容 2. 将文件添加到仓库 git add test.txt 执行之后没有任何输…...

无锡无人机超视距驾驶证怎么考?

无锡无人机超视距驾驶证怎么考&#xff1f;在近年来&#xff0c;无人机技术的迅猛发展使得无人机的应用场景变得愈发广泛&#xff0c;其不仅在环境监测、农业喷洒、快递配送等领域展现出真金白银的价值&#xff0c;同时也推动了无人机驾驶证的需求。尤其是在无锡&#xff0c;随…...

pyautogui是什么:自动化鼠标和键盘操作

pyautogui是什么:自动化鼠标和键盘操作 目录 pyautogui是什么:自动化鼠标和键盘操作安装方法主要功能及使用示例1. 鼠标操作2. 键盘操作3. 获取屏幕信息应用场景注意事项pyautogui 是一个用于自动化鼠标和键盘操作的 Python 第三方库,它允许开发者通过编写 Python 代码来模拟…...

小白学习java第12天:IO流之缓冲流

1.IO缓冲流&#xff1a; 之前我们学习的都是原始流&#xff08;FileInputStream字节输入流、FileOutputStream字节输出流、FIleReader字符输入流、FIleWriter字符输出流&#xff09;其实我们可以知道对于这些其实性能都不是很好&#xff0c;要么太慢一个一个&#xff0c;要么就…...

智能导诊系统方案:人体画像导诊实现从症状到科室推荐及院内导航链路拆解(python示范 TensorFlow Embedding 层源码)

本文面向医院信息科负责人、医疗AI开发者、医院管理者&#xff0c;解决传统分诊依赖人工经验&#xff0c;效率低且易出错&#xff1b;患者跨科室就诊路径不清晰等痛点问题&#xff0c;实现症状到科室的精准推荐及动态导航链路优化。 如需获取智慧医院导航导诊系统解决方案请前往…...

声学测温度原理解释

已知声速&#xff0c;就可以得到温度。 不同温度下的胜诉不同。 25度的声速大约346m/s 绝对温度-273度 不同温度下的声速。 FPGA 通过测距雷达测温度&#xff0c;固定测量距离&#xff0c;或者可以测出当前距离。已知距离&#xff0c;然后雷达发出声波到接收到回波的时间&a…...

30天学Java第九天——线程

并行与并发的区别 并行是多核 CPU 上的多任务处理&#xff0c;多个任务在同一时间真正的同时执行并发是单核 CPU 上的多任务处理&#xff0c;多个任务在同一时间段内交替执行&#xff0c;通过时间片轮转实现交替执行&#xff0c;用于解决 IO 密集型任务的瓶颈 线程的创建方式…...

SaaS微服务架构的智慧工地源码,基于Spring Cloud +UniApp +MySql开发

基于微服务架构JavaSpring Cloud UniApp MySql技术开发&#xff0c;saas模式的一套智慧工地云平台源码&#xff0c;支持多端展示&#xff1a;PC端、大屏端、手机端、平板端。包含项目人员管理、视频监控管理、危大工程监管、绿色施工管理、现场物料管理、安全隐患排查等功能。 …...

Qt学习笔记——TableWidget的一些学习东西

TableWidget的一些学习东西 使用QtDesigner绘制表格&#xff0c;但是表格出现很多问题&#xff0c;烦死了&#xff0c;整理了一些内容。 在使用 Qt Designer 设置 QTableWidget 时&#xff0c;涉及大量属性选项&#xff0c;尤其是在初学阶段常常因为属性设置不当而导致表格显…...

《Uniapp-Vue 3-TS 实战开发》Pinia 及 Pinia 持久化

前言: 正文: 一、Pinia 基础用法 1. 安装与初始化 bash npm install pinia # 或 yarn add pinia 在 main.js/ts 中初始化: import { createApp } from vue import { createPinia } from pinia import App from ./App.vue const app = createApp(App) app.use(createPinia()…...

JAVA:SpringBoot 实现图片防盗链的技术指南

1、简述 防盗链(Hotlink Protection)是一种保护网站资源不被其他网站直接引用的技术,特别是在图片、视频等静态资源方面。防盗链的核心思想是检查请求的来源(Referer),只允许来自指定域名的请求访问资源。 在 Spring Boot 中,我们可以通过拦截器(Interceptor)或过滤…...

量子指纹识别

场景设定 某金融机构部署量子指纹认证系统&#xff0c;要求用户通过手机&#xff08;传感器A&#xff09;注册指纹&#xff0c;并在ATM机&#xff08;传感器B&#xff09;完成量子安全认证。系统需满足&#xff1a; 抗模板泄露&#xff1a;即使数据库被攻破&#xff0c;攻击者…...

图像变换方式区别对比(Opencv)

1. 变换示例 import cv2 import matplotlib.pyplot as plotimg cv2.imread(url) img_cut img[100:200, 200:300] img_rsize cv2.resize(img, (50, 50)) (hight,width) img.shape[:2] rotate_matrix cv2.getRotationMatrix2D((hight//2, width//2), 50, 1) img_wa cv2.wa…...

快速上手Linux联网管理

RHEL9版本特点 在RHEL7版本中&#xff0c;同时支持network.service和NetworkManager.service&#xff08;简称NM&#xff09;。在RHEL8上默认只能通过NM进行网络配置&#xff0c;包括动态ip和静态ip,若不开启NM&#xff0c;否则无法使用网络RHEL8依然支持network.service&…...

加速度计芯片的主要参数定义、计算、测试方法

加速度计的主要参数包括量程、分辨率、灵敏度、输出数据速率、接口类型、功耗、噪声等。量程决定了加速度的测量范围&#xff0c;比如2g到16g&#xff0c;不同的应用需要不同的量程。分辨率关系到能检测到的最小变化&#xff0c;通常用位数表示&#xff0c;比如12位或16位。灵敏…...

FFMPEG大文件视频分割传输教程,微信不支持1G文件以上

如下是一个2.77g的文件分割教程 . 前言 FFmpeg 是一个用于处理视频、音频等多媒体文件的开源工具包。它支持几乎所有的多媒体格式转换、剪辑和编辑&#xff0c;是开发者和多媒体工作者必备的工具。本文详细讲解如何在 Windows 系统上安装 FFmpeg 并进行基本配置。 2. 下载 FF…...

interfaceResidue:一款用于分析蛋白复合物“接触界面残基”的pymol插件

当我们使用AF3或其他结构预测工具获得蛋白复合物后&#xff0c;逃不掉的一步就是分析接触界面的残基互作&#xff0c;而分析互作的前提是要准确地识别出接触界面上的残基有哪些&#xff0c;如果手动找则太耗费精力而且也容易遗漏。本期向大家安利的这样一款pymol插件&#xff0…...

【Qt】常用控件【按钮类】

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Qt 目录 一&#xff1a;&#x1f525; 前言 二&#xff1a;&#x1f525; 按钮类控件 &#x1f98b; Push Button 按钮&#x1f380; 带有图标的按钮 -- 纯代码实现&#x1f380; 带有快捷键的按钮…...

996引擎-源码学习:PureMVC Lua 中的系统启动,初始化并注册 Mediator

996引擎-源码学习:PureMVC Lua 中的系统启动,初始化并注册 Mediator 一、PureMVC 核心架构二、系统启动流程系统启动注册 StartUp 通知发送 StartUp 通知,开始初始化三、Mediator 初始化1. gameStateInit.lua2. LoadingBeginCommand.lua3. RegisterWorldMediatorCommand.lua…...

SDP(一)

SDP(Session Description Protocol)会话描述协议相关参数 Session Description Protocol Version (v): 0 --说明&#xff1a;SDP当前版本号 Owner/Creator, Session Id (o): - 20045 20045 IN IP4 192.168.0.0 --说明&#xff1a;发起者/创建者 会话ID&#xff0c;那么该I…...

深入理解Apache Kafka

引言 在现代分布式系统架构中&#xff0c;中间件扮演着至关重要的角色&#xff0c;它作为系统各组件之间的桥梁&#xff0c;负责处理数据传递、消息通信、负载均衡等关键任务。在众多中间件解决方案中&#xff0c;Apache Kafka凭借其高吞吐量、低延迟和可扩展性&#xff0c;已…...

【AI News | 20250411】每日AI进展

AI Repos 1、docext docext是一款无需OCR的本地化文档信息提取工具&#xff0c;利用视觉语言模型&#xff08;VLM&#xff09;从发票、护照等文档图像中高效提取结构化字段和表格数据。其支持自定义字段或预置模板&#xff0c;提供置信度评分、多页处理及REST API集成&#xf…...

风暴之眼:在AI重构的数字世界重绘职业坐标系

硅谷的某个深夜&#xff0c;GitHub Copilot在程序员的注视下自动生成出完美代码&#xff0c;这个场景正在全球数百万开发者的屏幕上同步上演。当AI生成的代码通过图灵测试&#xff0c;当机器学习模型开始理解业务需求&#xff0c;一个根本性命题浮出水面&#xff1a;在人类亲手…...

关于深度学习局部视野与全局视野的一些思考

关于深度学习局部视野与全局视野的一些思考 最近&#xff0c;我在学习一个基于Transformer的网络模型时&#xff0c;注意到了一些局部特征和全局特征的概念。引发了一些疑问: 为什么说CNN只能看到局部区域&#xff0c;而transformer能看到全局区域?什么是token? 对于图像中…...

Asp.NET Core WebApi 配置文件

在 ASP.NET Core Web API 中&#xff0c;配置文件&#xff08;如 appsettings.json&#xff09;是管理应用程序设置的核心部分。ASP.NET Core 提供了一套灵活的配置系统&#xff0c;允许开发者从多种来源加载配置数据&#xff0c;并根据需要使用这些配置。 以下是关于如何在 A…...

免费的AI原创文章批量生成工具,站长内容更新工具推荐

说到AI生成文章&#xff0c;现在已经不是什么热门话题了&#xff0c;因为国内有很多的AI模型现在也越来越成熟了&#xff0c;那么科技工具的出现就是为人民服务的&#xff0c;我们要合理的用好它。 今天给大家推荐的是一款很厉害的站长网站内容更新工具&#xff0c;它可以利用…...

在ASP.NET Core 中实现幂等API和WinForms客户端防重提交实践

前言 大家好&#xff0c;欢迎关注dotnet研习社。今天&#xff0c;我想和大家聊聊在 ASP.NET Core 中如何实现幂等 API&#xff0c;这是我们在实际项目开发中非常重要、但又常常被忽略的一个话题。 什么是幂等性&#xff1f; 幂等性&#xff08;Idempotency&#xff09;指的是…...

Vue如何利用Postman和Axios制作小米商城购物车

小编最近太忙了&#xff0c;没来得及更新博客&#xff01;上一条博客我们写了小米商城购物车的简版&#xff0c;今天我们就在简版的基础之上来增加一些功能&#xff0c;写一下数量的加减、总价、删除&#xff08;批量删除&#xff09;、全选取消全选等功能。如果上一条博客没有…...

使用Windows工具进行内存取证(不进行完全内存转储)

内存取证是分析易失性内存以发现恶意活动、恶意软件行为或系统异常的强大技术。一般情况下调查员会转储全部物理内存&#xff0c;并使用Volatility等工具对其进行分析。然而在许多实际场景中&#xff0c;由于系统限制、安全策略或紧迫性等原因&#xff0c;完全转储可能并不可行…...

大厂文章阅读

1.异步任务处理系统&#xff0c;如何解决业务长耗时、高并发难题&#xff1f; 1)任务失败如何处理(CAS失败也可用)&#xff1a;1.指数退避,匹配下游任务执行系统的处理能力。比如收到下游任务执行系统的流控错误&#xff0c;或者感知到任务执行成为瓶颈&#xff0c;需要指数退…...

ubuntu 服务器版本常见问题

一、系统安装与初始化 1. 安装过程中断或失败 原因:镜像损坏、硬件兼容性、磁盘分区错误。 解决: 验证 ISO 文件的完整性(计算 SHA256 校验和)。 检查 BIOS/UEFI 设置(禁用 Secure Boot)。 使用手动分区模式,确保根分区(/)和 EFI 分区(如有)正确配置。 2. 系…...

第十五届蓝桥杯大赛软件赛省赛Python 大学 B 组试做(下)【本期题单: 缴纳过路费, 纯职业小组】

本期题单&#xff1a;缴纳过路费&#xff0c;纯职业小队 文章目录 缴纳过路费题目思路分析代码 纯职业小组题目思路分析 感谢大伙观看&#xff0c;别忘了三连支持一下大家也可以关注一下我的其它专栏&#xff0c;同样精彩喔~下期见咯~ 缴纳过路费 题目 题目链接&#xff1a;缴…...

【Hadoop入门】Hadoop生态之Oozie简介

1 什么是Oozie&#xff1f; Oozie是Apache基金会下的一个开源工作流调度系统&#xff0c;专门设计用于管理Hadoop作业。作为一个基于工作流的调度服务器&#xff0c;它能够在复杂的任务依赖关系中协调Hadoop MapReduce、Pig、Hive等任务的执行&#xff0c;是大数据平台中任务编…...

【Amazon EC2】为何基于浏览器的EC2 Instance Connect 客户端连接不上EC2实例

文章目录 前言&#x1f4d6;一、报错先知❌二、问题复现&#x1f62f;三、解决办法&#x1f3b2;四、验证结果&#x1f44d;五、参考链接&#x1f517; 前言&#x1f4d6; 这篇文章将讲述我在 Amazon EC2 上使用 RHEL9 AMI 时无法连接到 EC2 实例时所遇到的麻烦&#x1f616; …...

【大模型系列篇】最强检索增强技术GraphRAG基本原理详解

GraphRAG是一种结合了知识图谱&#xff08;Knowledge Graph&#xff09;和大型语言模型&#xff08;Large Language Model, LLM&#xff09;的检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;技术。它通过引入图结构化的知识表示和处理方法&#xff…...

【高阶数据结构】第二弹---图的深度解析:从基本概念到邻接矩阵的存储与操作

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【高阶数据结构】 目录 1、图的基本概念 2、图的存储结构 2.1、邻接矩阵 2.1.1、基本结构 2.1.2、图的创建 2.1.3、获取顶点下标…...

【Java实战】——手撕斐波那契数列

&#x1f381;个人主页&#xff1a;User_芊芊君子 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 &#x1f50d;系列专栏&#xff1a;【Java】内容概括 这里写目录标题 1.什么是斐波那契数列&#xff1f;2.代码实现2.1 递归实现2.2 迭代实现 3.执行结果 …...

Python数据可视化-第7章-绘制3D图表和统计地图

环境 开发工具 VSCode库的版本 numpy1.26.4 matplotlib3.10.1 ipympl0.9.7教材 本书为《Python数据可视化》一书的配套内容&#xff0c;本章为第7章 绘制3D图表和统计地图 本章首先介绍了使用mplot3d工具包绘制3D图表&#xff0c;然后介绍了使用animation模块制作动画&#…...

操作系统 4.2-键盘

键盘中断初始化和处理 提取的代码如下&#xff1a; // con_init 函数&#xff0c;初始化控制台&#xff08;包括键盘&#xff09;的中断 void con_init(void) {set_trap_gate(0x21, &keyboard_interrupt); } ​ // 键盘中断处理函数 .globl _keyboard_interrupt _keyboard…...