WebServer 之 http连接处理(上)

目录

🌼基础知识

epoll

HTTP 报文格式

HTTP 状态码

有限状态机

🌙http 处理流程

🐎http 报文处理

🎂http类

🎂请求报文--接收

🐎epoll 相关代码

🧜‍服务器接收 http 请求


🌼基础知识

epoll

此处仅对 API 和 基础知识 作介绍

epoll_create()

#include <sys/epoll.h>
int epoll_create(int size)

创建一个指示 epoll 内核事件表的文件描述符

该描述符将用作其他 epoll 系统调用的一个参数,size 不起作用

epoll_ctl()

#include<sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

该函数用于操作 内核事件表 监控的文件描述符上的时间:

注册,修改,删除

  • epfd:epoll_create() 的句柄
  • fd:文件描述符的缩写,代表文件或者 socket 的引用

句柄:获取另一个对象的方法——一个广义的指针

  • op:表示动作,用 3 个宏表示:
    •  EPOLL_CTL_ADD(注册新的 fd 到 epfd)
    • EPOLL_CTL_MOD(修改已注册 fd 的监听事件)
    • EPOLL_CTL_DEL(从 epfd 删除一个 fd)
  • event:告诉内核需要监听的事件

event 是 epoll_event 结构体指针类型,表示内核所监听的事件,定义如下👇

struct epoll_event {__unit32_t events; // Epoll eventsepoll_data_t data; // User data variable
};

events 描述事件类型,其中 epoll 事件类型如下👇

  • EPOLLIN: 对应的文件描述符可读
  • EPOLLOUT:可写
  • EPOLLPRI:对应的文件描述符有 紧急数据 可读(带外数据)
  • EPOLLERR:发生错误
  • EPOLLHUP:被挂断
  • EPOLLET:将 EPOLL 设置为边缘触发(Edge Triggered)模式,相对水平触发(Level Triggered)而言
  • EPOLLONESHOT:只监听一次事件,监听完该次后,还需要监听这个 socket 的话,需要再次将这个 socket 加入到 EPOLL 队列

epoll_wait()

#include<sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents,int timeout)

该函数用于等待所监控文件描述符上事件的产生,返回就绪的文件描述符个数

  • events -- 存储内核得到事件的集合
  • maxevents -- 告知内核这个 events 多大,maxevents 要 <= 创建 epoll_create() 时的size
  • timeout -- 超时时间
    • -1:阻塞
    • 0:立即返回,非阻塞
    • >0:指定毫秒
  • 返回值:就绪的文件描述符个数,时间结束返回 0,出错返回 -1

select / poll / epoll

  1. 调用函数
    1. select 和 poll 都是一个函数,epoll 是一组函数
  2. 文件描述符数量
    1. select 通过 线性表 描述文件描述符集合,数量上限 1024
    2. poll 通过 链表 描述,突破文件描述符上限,最大可以打开文件的数目
    3. epoll 通过 红黑树 描述,最大可以打开的文件数目,通过命令 
      ulimit -n number 修改,仅对当前终端有效
  3. 文件描述符 从用户传向内核
    1. select / poll 将所有文件描述符 拷贝 到 内核态,每次调用都需要拷贝
    2. epoll 通过 epoll_create() 建立一颗红黑树,通过 epoll_ctl 把要监听的文件描述符注册到红黑树
  4. 内核判断就绪的文件描述符
    1. select / poll 遍历文件描述符集合,判断哪个文件描述符上有时间发生
    2. epoll_create() 时,内核先在 epoll 文件系统里,建立一个红黑树,用于存储以后 epoll_ctl 传来的 fd;再建立一个 list 链表,用于存储准备就绪的事件;当 epoll_wait 调用时,仅仅观察这个 list 链表有无数据即可
    3. epoll 根据每个 fd 上面的回调函数(中断函数)判断,只有发生了事件的 socket 才会主动去调用 callback() 函数,其他空闲状态的 socket 则不会 -- 若是就绪事件,插入 list
  5. 应用程序索引 就绪文件描述符
    1. select /poll 只返回发生了事件的文件描述符个数,即使知道哪个发生了事件,还是需要遍历
    2. epoll 返回发生了事件的 个数 和 结构体数组,结构体包含 socket 信息,因此直接处理返回的数组即可
  6. 工作模式
    1. select / poll 只能工作在相对低效的 LT 模式下
    2. epoll 可以工作在 ET 高效模式下,还支持 EPOLLONESHOT 事件,该事件能进一步减少可读,可写和异常事件被触发的次数
  7. 应用场景
    1. 当所有 fd 都是活跃连接,使用 epoll,需要建立文件系统
      此时 红黑树 和 链表,效率反而不高,不如 select 和 poll
    2. 当监测的 fd 数目较小,且各个 fd 都比较活跃时,用 select 或 poll
    3. 当监测的 fd 数目非常大,成千上万,且单位时间只有一部分 fd 处于就绪状态,用 epoll 能明显提升性能

ET,LT,EPOLLONESHOT

  • LT水平触发模式
    • epoll_wait 检测到文件描述符有事件发生,就将其通知到应用程序,应用程序可以不立即处理该事件
    • 当下一次调用 epoll_wait 时,epoll_wait 还会再次向应用程序报告,直到被处理
  • ET边缘出发模式
    • epoll_wait 检测到文件描述符...通知到应用程序,应用程序立即处理
  • EPOLLONESHOT
    • 一个线程读取某个 socket 上的数据后开始处理,处理过程中,该 socket 又有新数据可读,此时另一个线程被唤醒读取,则同时出现 2 个线程处理同一个 socket
    • 我们希望的是,一个 socket 连接,在任一时刻,只被一个线程处理,通过 epoll_ctl 对该文件描述符注册 epolloneshot 事件,一个线程处理 socket 时,其他线程无法处理
      当该线程处理完后,需要通过 epoll_ctl 重置 epolloneshot 事件

HTTP 报文格式

HTTP报文,分为请求报文和响应报文,每种报文必须按照特有格式生成,才能被浏览器识别

浏览器向服务器发送的 -- 请求报文

服务器返回给浏览器的 -- 响应报文

请求报文

HTTP请求报文由,请求行(request line),请求头部(header),空行,请求数据

四部分组成

其中,请求氛围 GET 和 POST

  • GET
GET /562f25980001b1b106000338.jpg HTTP/1.1
Host:img.mukewang.com
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept:image/webp,image/*,*/*;q=0.8
Referer:http://www.imooc.com/
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-CN,zh;q=0.8
空行
请求数据为空
  • 请求行:请求类型        要访问的资源        HTTP版本
  • 请求头部:服务器要使用的附加信息
    • HOST -- 服务器域名
    • User-Agent -- HTTP客户端程序的信息
    • Accept -- 用户代理,可处理的媒体类型
    • Accept-Encoding -- 用户代理,支持的内容编码
    • Accept-Language -- 自然语言集
    • Content -Type -- 实现主体的媒体类型
    • Content-Length -- 实现主体的大小
    • Connection -- 连接管理(Keep-Alive 或 close)
  • 空行:必须的
  • 请求数据:也叫主体,可以添加任意的其他数据
  • POST
POST / HTTP1.1
Host:www.wrox.com
User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type:application/x-www-form-urlencoded
Content-Length:40
Connection: Keep-Alive
空行
name=Professional%20Ajax&publisher=Wiley

响应报文

HTTP响应,也由 4 部分组成:状态行,消息报头,空行,响应正文

HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8
空行
<html><head></head><body><!--body goes here--></body>
</html>
  •  状态行:HTTP版本号         状态码           状态消息
  • 消息报头:客户端要使用的附加信息

Date -- 生成响应的日期和时间

Content-Type -- 指定MIME类型的HTML,编码类型UTF-8

  • 空行:必须
  • 响应正文:服务器返回给客户端的 文本信息(空行后的html为响应正文)

HTTP 状态码

  • 1xx:请求已接受,继续处理
  • 2xx:成功(请求正常处理完毕)
    • 200 OK -- 客户端请求被正常处理
    • 206 Partial content -- 客户端进行了范围请求
  • 3xx:重定向 -- 需要更进一步的操作
    • 301 Moved Permanently:永久重定向,该资源已被永久移动到新位置,将来任何对该资源的访问,都要使用本响应返回的几个 URI 之一
    • 302 Found:临时重定向,请求的资源,临时从不通过的 URI 获得
  • 4xx:客户端错误 -- 语法错误,服务器无法处理
    • 400 Bad Request:语法错误
    • 403 Forbidden:请求被服务器拒绝
  • 5xx:服务器错误
    • 500 Internal Server Error:服务器,执行请求时,出现错误

有限状态机

抽象的理论模型,把有限个变量的状态变化过程,以可构造可验证的方式,呈现出来

eg:封闭的有向图

通过 if-else, switch-case 和 函数指针 实现(目的:封装逻辑)

含有状态转移的有限状态机👇

STATE_MACHINE() {State cur_State = type_A; // 当前状态初始化 type_Awhile (cur_State != type_C) {Package _pack = getNewPackage(); // 获取新数据包switch(cur_State) { // 根据当前状态处理case type_A:process_pkg_state_A(_pack); // 处理 type_A 数据包cur_State = type_B; // 切换状态break;case type_B:process_pkg_state_B(_pack); // 处理 type_B 数据包cur_State = type_C; // 切换到结束状态break;}}
}

该状态机包含三种状态:type_A,type_B和type_C。其中,type_A是初始状态,type_C是结束状态

状态机的当前状态记录在cur_State变量中,逻辑处理时,状态机先通过getNewPackage获取数据包,然后根据当前状态对数据进行处理,处理完后,状态机通过改变cur_State完成状态转移

有限状态机一种 逻辑单元内部 的一种高效编程方法,在服务器编程中,服务器可以根据不同状态或者消息类型进行相应的处理逻辑,使得程序逻辑清晰易懂

🌙http 处理流程

🐎http 报文处理

  • 浏览器发出 http 连接请求,主线程创建 http 对象接收请求;并将所有数据读入对应 buffer;将该对象插入任务队列;工作线程从任务队列取出一个任务进行处理
  • 工作线程取出任务后,调用 process_read() 函数,通过主从状态机对请求报文解析
  • 解析完,跳转 do_request() 函数 生成响应报文,通过 process_write 写入 buffer,返回给浏览器端

🎂http类

关于 enum 枚举类型👇

列举出一组枚举值,每个枚举值都有一个与之关联的整数值,默认情况下第一个枚举值的整数值为0,后续枚举值的整数值依次递增

#include <iostream>enum Color {RED, // 整数值默认为0GREEN, // 整数值默认为1BLUE // 整数值默认为2
};int main() {Color selectedColor = GREEN;if (selectedColor == RED) {std::cout << "Selected color is red." << std::endl;} else if (selectedColor == GREEN) {std::cout << "Selected color is green." << std::endl;} else if (selectedColor == BLUE) {std::cout << "Selected color is blue." << std::endl;}return 0;
}
Selected color is green.

本部分代码👇,位于 TinyWebServer/http/http_conn.h,主要是 http 类的定义

class http_conn {public:// 文件名称 m_real_file 大小static const int FILENAME_LEN = 200;// 读缓冲区 m_read_buf 大小static const int READ_BUFFER_SIZE = 2048;// 写缓冲区 m_write_buf 大小static const int WRITE_BUFFER_SIZE = 1024;// 报文请求方法,GET / POSTenum METHOD { // 定义枚举类型GET=0,POST,HEAD,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATH};// 主状态机状态enum CHECK_STATE {CHECK_STATE_REQUESTLINE=0,CHECK_STATE_HEADER,CHECK_STATE_CONTENT};// 报文解析结果enum HTTP_CODE {NO_REQUEST,GET_REQUEST,BAD_REQUEST,NO_RESOURCE,FORBIDDEN_REQUEST,FILE_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};// 从状态机状态enum LINE_STATUS {LINE_OK=0,LINE_BAD,LINE_OPEN};public:http_conn(){}; // 构造~http_conn(){}; // 析构public:// 初始化套接字地址,函数内部调用私有方法 initvoid init(int sockfd, const sockaddr_in &addr);// 关闭 http 连接void close_conn(bool real_close=true);void process();// 读取浏览器发来的全部数据bool read_once();// 响应报文写入函数bool write();sockaddr_in *get_address() {return &m_address;}// 同步线程初始化数据库读取表void initmysql_result();// CGI使用线程池初始化数据库表void initresultFile(connection_pool *connPool);private:void init();// 从 m_read_buf 读取,并处理请求报文HTTP_CODE process_read();// 向 m_write_buf 写入响应报文数据bool process_write(HTTP_CODE ret);// 主状态机解析报文中的请求 行数据HTTP_CODE parse_request_line(char *text);// 主状态机解析报文中的请求 头数据HTTP_CODE parse_headers(char *text);// 主状态机解析报文中的 请求内容HTTP_CODE parse_content(char *text);// 生成响应报文HTTP_CODE do_request();// m_start_line 已经解析的字符// get_line 将指针向后偏移,指向未处理的字符char* getline() {return m_read_buf + m_start_line;}// 从状态机读取一行,分析是 请求报文 哪部分LINE_STATUS parse_line();void unmap();// 根据响应报文格式,生成对应 8 部分// 以下函数均有 do_request 调用bool add_response(const char* format, ...);bool add_content(const char* content);bool add_status_line(int status, const char* title);bool add_headers(int content_length);bool add_content_type();bool add_content_length(int content_length);bool add_linger();bool add_blank_line();public:static int m_epollfd;static int m_user_count;MYSQL *mysql;private:int m_sockfd;sockaddr_in m_address;// 存储读取的请求报文数据char m_read_buf[READ_BUFFER_SIZE];// 缓冲区 m_read_buf 数据最后一个字节下一位置int m_read_idx;// m_read_buf 读取的位置int m_checked_idx;// m_read_buf 已经解析的字符个数int m_start_line;// 存储发出的响应报文数据char m_write_buf[WRITE_BUFFER_SIZE];// 指示buffer中的长度int m_write_idx;// 主状态机的状态CHECK_STATE m_check_state;// 请求方法METHOD m_method;// 解析请求报文中的 6 个变量// 存储读取文件的名称char m_real_file[FILENAME_LEN];char *m_url;char *m_version;char *m_host;int m_content_length;bool m_linger;char *m_file_address; // 读取服务器上的文件地址struct stat m_file_stat;struct iovec m_iv[2]; // io 向量机制 iovecint m_iv_count;int cgi; // 是否启用的 POSTchar *m_string; // 存储 请求头数据int bytes_to_send; // 剩余发送字节数int bytes_have_send; // 已发送字节数
};

🎂请求报文--接收

在 http 请求接收部分,会涉及到 init() 和 read_once() 函数,但 init() 仅对私有成员变量初始化,不过多讲解

read_once() 读取浏览器发来的 请求报文,直到无数据可读 或 对方关闭连接

读取到 m_read_buffer,并更新 m_read_idx

// 循环读取客户数据,直到无数据可读 或 对方关闭连接
bool http_conn::read_once()
{if (m_read_idx >= READ_BUFFER_SIZE) {return false;}int bytes_read = 0;while (true) {// 从套接字接收数据,存储在 m_read_buf 缓冲区bytes_read = recv(m_sockfd, m_read_buf + m_read_idx,READ_BUFFER_SIZE - m_read_idx, 0);if (bytes_read == -1) {// 非阻塞ET模式下,需要一次性将数据读完if (errno == EAGAIN || errno == EWOULDBLOCK)break;return false;}else if (bytes_read == 0)return false;// 修改 m_read_idx 的读取字节数m_read_idx += bytes_read;}return true;
}

🐎epoll 相关代码

项目中 epoll 相关代码,包括 4 部分:

非阻塞模式,内核事件表注册事件,删除事件,重置EPOLLONESHOT事件

  • 非阻塞模式
// 对文件描述符设置非阻塞
int setnonblocking(int fd)
{// 获取文件描述符的旧选项int old_option = fcntl(fd, F_GETFL); // 将非阻塞选项与旧选项进行按位或运算int new_option = old_option | P_NONBLOCK; // 将新选项设置为文件描述符的选项fcntl(fd, F_SETFL, new_option);return old_option; // 返回旧选项
}
  • 内核事件表注册新事件,开启EPOLLONESHOT,针对客户端连接的描述符,listenfd不开启
// 内核事件表注册新事件,开启EPOLLONESHOT,针对客户端连接的描述符,listenfd不用开启
void addfd(int epollfd, int fd, bool one_shot)
{epoll_event event; // 定义事件结构体event.data.fd = fd; // 设置事件结构体的文件描述符#ifdef ETevent.events = EPOLLIN | EPOLLET | EPOLLRDHUP; // 使用边缘触发模式,设置事件类型
#endif#ifdef LTevent.events = EPOLLIN | EPOLLRDHUP; // 使用水平触发模式,设置事件类型
#endifif (one_shot)event.events |= EPOLLONESHOT; // 开启 EPOLLONESHOT 选项epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); // 将事件注册到内核事件表中setnonblocking(fd); // 设置文件描述符为非阻塞模式
}
  • 内核事件表删除事件
void removefd(int epollfd, int fd)
{epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);close(fd);
}
  • 重置 EPOLLONESHOT 事件
void modfd(int epollfd, int fd, int ev)
{epoll_event event; // 定义事件结构体变量event.data.fd = fd; // 设置事件结构体文件描述符传入的 fd#ifdef ET// 边缘触发模式,设置事件类型 -- 按位或结果// ev参数   EPOLLET边缘触发    EPOLLONESHOT一次性出发// EPOLLRDHUP对端关闭连接event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
#endif#ifdef LT// 水平触发模式,设置事件类型event.events = ev | EPOLLONESHOT | EPOLLRDHUP;
#endif// 修改已注册事件属性epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);// &event 待修改的事件结构体
}

🧜‍服务器接收 http 请求

浏览器发出的 连接请求,主线程创建 http 对象接收请求,并将所有数据读入对应的 buffer

将该对象插入任务队列

工作线程从任务队列取出一个任务进行处理

// 创建 MAX_FD 个http类对象
http_conn* users = new http_conn[MAX_FD];// 创建内核事件表
epoll_event events[MAX_EVENT_NUMBER];
epollfd = epoll_create(5);
assert(epollfd != -1);// 将 listenfd 放在 epoll 树上
addfd(epollfd, listenfd, false);// 将上述 epollfd 赋值给 http类对象的 m_epollfd 属性
http_conn::m_epollfd = epollfd;while (!stop_server) {// 等待被监控的文件描述符上有事件产生int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);if (number < 0 && errno != EINTR) break;// 处理所有就绪事件for (int i = 0; i < number; ++i) {int sockfd = events[i].data.fd;// 处理新到的客户连接if (sockfd == listenfd) {struct sockaddr_in client_address;socklen_t client_addrlength = sizeof(client_address);
// LT水平触发
#ifdef LTint connfd = accept(listenfd, (struct sockaddr *)&client_address,&client_addrlength);if (connfd < 0) continue;if (http_conn::m_user_count >= MAXFD) {show_error(connfd, "Internal server busy");continue;}users[connfd].init(connfd, client_address);
#endif// ET非阻塞边缘触发
#ifdef ET// 需要循环接收数据while (1) {int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlength);if (connfd < 0) break;if (http_conn::m_user_count >= MAX_FD) {show_error(connfd, "Internal server busy");break;}users[connfd].init(connfd, client_address);}continue;
#endif}// 处理异常事件else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){// 服务器关闭连接}// 处理信号else if ( (sockfd == pipefd[0]) && (events[i].events & EPOLLIN) ){}// 处理客户连接上接收的数据else if (events[i].events & EPOLLIN) {// 读入对应缓冲区if (users[sockfd].read_once()) // 若监测到 读事件,该事件放入请求队列poll->append(users + sockfd);else // 服务器关闭连接 }}
}

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

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

相关文章

Qt 说明Q_PROPERTY的作用

在Qt框架中,Q_PROPERTY是一种宏,用于声明一个类的属性。Q_PROPERTY宏可以告诉Qt元对象系统(Meta-Object System)关于类的属性信息,比如属性的名称、类型、读写权限、通知信号等。这样可以让Qt的元对象系统能够对类的属性进行动态的查询和操作,比如在运行时动态获取和设置…

HTML快速入门教程

HTML&#xff1a;超文本标记语言&#xff08;Hyper Text Markup Language&#xff09;&#xff0c;是通过标签的形式将内容组织起来然后共享到网络之上供其他电脑访问查看。 大家可以思考一下&#xff0c;怎么将自己电脑上的文件或图片共享给其他电脑&#xff1f; 这时候会说通…

.target勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复

导言&#xff1a; 网络安全威胁如勒索病毒已经成为企业和个人数据安全的重大挑战之一。.target勒索病毒作为其中的一种&#xff0c;以其高度复杂的加密算法和迅速变化的攻击手法备受关注。本文将深入介绍.target勒索病毒的特点&#xff0c;探讨如何有效地恢复被加密的数据文件…

【小记】MacOS Install golang

问题 - command not found: go ➜ brew install golang ➜ go version go version go1.21.7 darwin/arm64写在最后&#xff1a;若本文章对您有帮助&#xff0c;请点个赞啦 ٩(๑•̀ω•́๑)۶

关闭Windows系统自动更新的方法

关闭Windows系统自动更新的方法在不同版本的Windows中有所不同&#xff0c;以下是一些适用于不同Windows版本的步骤&#xff1a; Windows 10/11 方法一&#xff1a;通过“设置”关闭 打开“设置”&#xff08;可通过点击开始菜单或使用快捷键Win I&#xff09;。转到“更新和安…

【Visual Studio】使用空格替换制表符

环境 VS版本&#xff1a;VS2013 问题 如何生成空格替换制表符&#xff1f; 步骤 1、菜单 工具->选项&#xff0c;文本编辑器->C/C->制表符&#xff0c;选择【插入空格】。

[嵌入式系统-18]:RT-Thread -4- shell组件与常见命令

目录 一、RT-Thread shell组件 二、FinSH控制台 三、Shell组件提供了一些常用的命令集 一、RT-Thread shell组件 RT-Thread的Shell组件是一个命令行解析器和执行器&#xff0c;它提供了一个交互性的命令行界面&#xff0c;使用户能够通过输入命令来与嵌入式设备进行交互。 …

电容串联额定耐压问题

例如:两个电容器,其中电容C1 200uF,耐压UM1 100V;电容C2 50uF,耐压UM2500V。 &#xff08;1&#xff09;若将两电容串联使用,其等效电容和耐压各是多少? &#xff08;2&#xff09;若将两电容器并联使用,其等效电容和耐压各是多少? 分析&#xff1a; &#xff08;1&…

Ubuntu Desktop - Terminal 输出全部选中 + 复制

Ubuntu Desktop - Terminal 输出全部选中 复制 1. Terminal2. Terminal 最大化3. Edit -> Select All4. Copy & PasteReferences 1. Terminal 2. Terminal 最大化 3. Edit -> Select All 4. Copy & Paste Edit -> Copy or Shift Ctrl C Edit -> Paste…

深度理解实分析:超越公式与算法的学习方法

在数学的学习旅程中&#xff0c;微积分和线性代数为许多学生提供了直观且具体的入门体验。它们通常依赖于明确的公式、算法以及解题步骤&#xff0c;而这些元素往往可以通过记忆和机械练习来掌握。然而&#xff0c;当我们迈入实分析的领域时&#xff0c;我们面临着一种全新的挑…

【蓝桥杯单片机入门记录】认识单片机

目录 单片机硬件平台 单片机的发展过程 单片机开发板 单片机基础知识 电平 数字电路中只有两种电平&#xff1a;高和低 二进制&#xff08;8421码&#xff09; 十六进制 二进制数的逻辑运算 “与” “或” “异或” 标准C与C51 如何学好单片机 端正学习的态度、培…

最详细STM32 启动流程

一、STM32三种启动方式 常规模式&#xff08;主闪存存储器&#xff09;&#xff1a; 这是最常见的启动模式。在此模式下&#xff0c;处理器会执行复位向量表中的复位地址&#xff0c;从而启动芯片。芯片会执行各种初始化操作&#xff0c;包括时钟初始化、外设初始化等&#xff…

devc++跑酷游戏2.4.0

导航&#xff1a; Dev-c跑酷小游戏 1.0.0 devc跑酷小游戏1.2.5 devc跑酷游戏1.2.6 devc跑酷游戏2.0.0 devc跑酷游戏2.0.1 【更新内容&#xff08;废话&#xff09;】&#xff1a; 又更新了两关&#xff0c;用方格便利贴画地图真的超级方便 把z键的功能从重新开始刷新改…

Kubernetes 核心概念

一、什么是 Kubernetes 1、含义&#xff1a; Kubernetes 是一个自动化的容器编排平台&#xff0c;它负责应用的部署、应用的弹性以及应用的管理。 2、核心功能&#xff1a; (1) 调度&#xff1a; Kubernetes 的调度器可以把用户提交的容器放到 Kubernetes 管理的集群的某一…

BUGKU-WEB 社工-初步收集

题目描述 题目截图如下&#xff1a; 描述:其实是杂项&#xff0c;勉强算社工吧。来自当年实战 进入场景看看&#xff1a; 解题思路 做题先看源码关注可下载的资源(zip压缩包)抓包寻找可能存在的加密信息&#xff08;base64&#xff09;不管三七二十一先扫描目录再说 ps&…

【AI之路】使用RWKV-Runner启动大模型,彻底实现大模型自由

文章目录 前言一、RWKV-Runner是什么&#xff1f;RWKV-Runner是一个大语言模型的启动平台RWKV-Runner官方功能介绍 二、使用步骤1. 下载文件 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; ChatGPT的横空出世&#xff0c;打开了AI的大门&#xff…

Android---Jetpack Compose学习005

动画 1. 简单值动画 示例&#xff1a;背景颜色在紫色和绿色之间&#xff0c;以动画形式切换。使用 animateColorAsState() val backgroundColor by animateColorAsState(if (tabPage TabPage.Home) Purple100 else Green300) 该句代码中&#xff0c;有一个 backgroundColo…

HTTP基本概念-HTTP缓存技术

大家好我是苏麟 , 今天说说HTTP缓存技术 . 资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) HTTP缓存技术 HTTP 缓存有哪些实现方式? 对于一些具有重复性的 HTTP 请求&#xff0c;比如每次请求得到的数据都一样的&#xff0c;我们可以把这对「请求-响…

实验5-5 使用函数求素数和

本题要求实现一个判断素数的简单函数、以及利用该函数计算给定区间内素数和的函数。 素数就是只能被1和自身整除的正整数。注意&#xff1a;1不是素数&#xff0c;2是素数。 函数接口定义&#xff1a; int prime( int p ); int PrimeSum( int m, int n ); 其中函数prime当用…

【C语言】指针练习篇(上),深入理解指针---指针和数组练习题和sizeof,strlen的对比【图文讲解,详细解答】

欢迎来CILMY23的博客喔&#xff0c;本期系列为【C语言】指针练习篇&#xff08;上&#xff09;&#xff0c;深入理解指针---指针数组练习题和sizeof&#xff0c;strlen的对比【图文讲解,详细解答】&#xff0c;图文讲解指针和数组练习题&#xff0c;带大家更深刻理解指针的应用…