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

WebServer 解析HTTP 请求报文

一、TCP 状态转换

浏览器访问网址,TCP传输全过程

二、TCP协议的通信过程 

三、TCP 通信流程 

// TCP 通信的流程
// 服务器端 (被动接受连接的角色)
1. 创建一个用于监听的套接字- 监听:监听有客户端的连接- 套接字:这个套接字其实就是一个文件描述符
2. 将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)- 客户端连接服务器的时候使用的就是这个IP和端口
3. 设置监听,监听的fd开始工作
4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字
(fd)
5. 通信- 接收数据- 发送数据
6. 通信结束,断开连接
// 客户端
1. 创建一个用于通信的套接字(fd)
2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信- 接收数据- 发送数据
4. 通信结束,断开连接


套接字通信的客户端和服务端的实现_呵呵哒( ̄▽ ̄)"的博客-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/132046488?spm=1001.2014.3001.5501

四 、解析HTTP请求报文

4.1 读取数据到缓冲区 bool http_conn::read()

// 循环读取客户数据,直到无数据可读或者对方关闭连接
bool http_conn::read() {// printf("一次性读完数据\n");if( m_read_idx >= READ_BUFFER_SIZE ) {return false;}int bytes_read = 0;//读取到的字节while(true) {//开始保存数据// 数组起始位置(0) + 已经读的数据位置(100) = 下次开始写的位置(101)// 从m_read_buf + m_read_idx索引出开始保存数据,大小是READ_BUFFER_SIZE - m_read_idxbytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0 );if (bytes_read == -1) {if( errno == EAGAIN || errno == EWOULDBLOCK ) {// 没有数据break;}return false;   } else if (bytes_read == 0) {   // 对方关闭连接return false;}m_read_idx += bytes_read;//读到数据,更新索引}// printf("读到了数据: %s\n",m_read_buf);return true;
}

4.2 有限状态机

逻辑单元内部的一种高效编程方法:有限状态机(finite state machine)。有的应用层协议头部包含数据包类型字段,每种类型可以映射为逻辑单元的一种执行状态,服务器可以根据它来编写相应的处理逻辑。如下是一种状态独立的有限状态机:

STATE_MACHINE( Package _pack )
{PackageType _type = _pack.GetType();switch( _type ){case type_A:process_package_A( _pack );break;case type_B:process_package_B( _pack );break;}
}

这是一个简单的有限状态机,只不过该状态机的每个状态都是相互独立的,即状态之间没有相互转移。状态之间的转移是需要状态机内部驱动,如下代码:

STATE_MACHINE()
{State cur_State = type_A;while( cur_State != type_C ){Package _pack = getNewPackage();switch( cur_State ){case type_A:process_package_state_A( _pack );cur_State = type_B;break;case type_B:process_package_state_B( _pack );cur_State = type_C;break;}}
}

该状态机包含三种状态:type_A、type_B 和 type_C,其中 type_A 是状态机的开始状态,type_C 是状态机的结束状态。状态机的当前状态记录在 cur_State 变量中。在一趟循环过程中,状态机先通过 getNewPackage 方法获得一个新的数据包,然后根据 cur_State 变量的值判断如何处理该数据包。数据包处理完之后,状态机通过给 cur_State 变量传递目标状态值来实现状态转移。那么当状态机进入下一趟循环时,它将执行新的状态对应的逻辑。

4.3 HTTP 请求格式

>>http get请求报文的格式
请求行\r\n
请求头\r\n
空行(\r\n)提示: 每项信息之间都需要一个\r\n,是由http协议规定************************************************
************************************************
>>http post请求报文的格式
请求行\r\n
请求头\r\n
空行(\r\n)
请求体提示: 请求体就是浏览器发送给服务器的数据

在HTTP报文中,每一行的数据有\r\n作为结束字符,空行则是仅仅是字符\r\n。因此,可以通过查找\r\n将报文拆解成单独的行进行解析,项目中便是利用了这一点。 

推荐文章: 

http请求报文与响应报文_构造http请求报文,发送到天气预报的服务器,并获取http响应报文,将报文进行解析获_JSon liu的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_45912307/article/details/109454522

Http请求报文格式和响应报文格式-腾讯云开发者社区-腾讯云 (tencent.com)icon-default.png?t=N7T8https://cloud.tencent.com/developer/article/1953222我的往期文章:Web服务器简介及HTTP协议_呵呵哒( ̄▽ ̄)"的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_41987016/article/details/132610837?spm=1001.2014.3001.5501

4.4 状态转换图

状态转换图
状态转换图

4.5 主状态机状态和从状态机状态

(1) 从状态机状态

>>从状态机逻辑

负责读取buffer中的数据,将每行数据末尾的\r\n置为\0\0,并更新从状态机在buffer中读取的位置m_checked_idx,从此来驱动主状态机解析

从状态机从m_read_buf中逐字节读取,判断当前字节是否为\r

  • 接下来的字符是\n,将\r\n修改成\0\0,将m_checked_idx指向下一行的开头,则返回LINE_OK
  • 然后达到了buffer末尾,表示buffer还需要继续接收,返回LINE_OPEN
  • 否则,表示语法错误,返回LINE_BAD

当前字节不是\r,判断是否是\n

  • 【注意】什么时候会是②这种情况?
一般是上次读取到\r就到了buffer末尾,没有接收完整,再次接收时会出现这种情况。
  • 【判断】如果前一个字符是\r,则将\r\n修改成\0\0,将m_checked_idx指向下一行的开头,则返回LINE_OK

当前字节既不是\r,也不是\n

  • 表示接受不完整,需要继续接收,返回LINE_OPEN
/*从状态机,用于分析出一行内容返回值为行的读取状态,由LINE_OK,LINE_BAD,LINE_OPENm_read_idx:指向缓冲区m_read_buf的数据末尾的下一个字节m_checked_idx:指向从状态机当前正在分析的字节
*/
// 解析一行,判断依据\r\n
http_conn::LINE_STATUS http_conn::parse_line() {char temp;for ( ; m_checked_idx < m_read_idx; ++m_checked_idx ) {// temp 为将要分析的字节temp = m_read_buf[ m_checked_idx ];// 如果当前是\r字符,则有可能会读取到完整行if ( temp == '\r' ) {// 下一个字符达到了buffer结尾,则接收不完整,需要继续接收if ( ( m_checked_idx + 1 ) == m_read_idx ) {return LINE_OPEN;} // 下一个字符是\n,将\r\n改为\0\0else if ( m_read_buf[ m_checked_idx + 1 ] == '\n' ) {m_read_buf[ m_checked_idx++ ] = '\0';m_read_buf[ m_checked_idx++ ] = '\0';return LINE_OK;}// 如果都不符合,则返回语法错误return LINE_BAD;} // 如果当前字符是\n,也有可能读取到完整行// 一般是上次读取到\r就到 buffer 末尾了,没有接收完整,再次接收时会出现这种情况else if( temp == '\n' )  {// 前一个字符是\r,则接收完整if( ( m_checked_idx > 1) && ( m_read_buf[ m_checked_idx - 1 ] == '\r' ) ) {m_read_buf[ m_checked_idx-1 ] = '\0';m_read_buf[ m_checked_idx++ ] = '\0';return LINE_OK;}return LINE_BAD;}}// 并没有找到\r\n,需要继续接收return LINE_OPEN;
}
例如:
>>http get请求报文的格式
请求行\r\n
请求头\r\n
空行(\r\n)提示: 每项信息之间都需要一个\r\n,是由http协议规定>>http get请求报文的格式
请求行\0\0
请求头\0\0
空行(\0\0)

(2)主从状态机,http_conn::process_read()

>>解析报文整体流程

process_read 通过 while 循环,将主状态机进行封装,对报文的每一行进行循环处理

http_conn::process_read() 是一个HTTP请求处理函数,用于解析HTTP请求报文

① 解析一行数据,得到不同状态(包括请求行、请求头、请求体

  • LINE_OK
  • LINE_BAD
  • LINE_OPEN

根据不同的状态,解析请求行、请求头或请求体,并进行相应的处理

  • CHECK_STATE_REQUESTLINE ---- parse_request_line()
  • CHECK_STATE_HEADER ---- parse_headers()
  • CHECK_STATE_CONTENT ---- parse_content()

 ③ 对于GET请求,根据具体的请求信息进行预处理,分析目标文件属性,并将其映射到内存地址m_file_address

  • do_request()

返回处理结果,包括成功、失败或请求不完整等状态码​​​​​​​

>>主从状态机模式

主从状态机模式:http_conn::process_read()用以应对不同的状态处理。

  • 一个状态机作为主要的控制器,而其他状态机则被设计为从状态机
  • 主状态机将它们之间的通信和事件处理进行协调
  • 主状态机是在系统启动时创建的
  • 从状态机可以在系统运行时动态创建、注册或注销

主状态机 process_read() 中进行循环,直到解析完整个 HTTP 请求数据

当且仅当解析到请求体时,才需要进一步判断是否已经解析完整个 HTTP 请求数据

表示若当前正在解析请求体,并且从状态机已经成功解析了一行请求数据,则需要继续解析下一行请求数据 

(m_check_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK)

或者从状态机还未成功解析出一行完整的请求数据,就需要不断循环调用从状态机、解析请求数据

 ((line_status = parse_line()) == LINE_OK))

 (line_status = parse_line()) 表示调用从状态机 parse_line() ,并将返回值赋给变量 line_status

  • 解析成功,line_status = LINE_OK
  • 否则,line_statusLINE_OPEN、LINE_BAD

直到解析完成整个 HTTP 请求数据

// 主状态机,解析请求
http_conn::HTTP_CODE http_conn::process_read() {// 定义初始状态LINE_STATUS line_status = LINE_OK;HTTP_CODE ret = NO_REQUEST;char* text = 0;// 解析一行数据,得到不同状态// OK表示正常// 主状态机 && 从状态机 || 解析到一行完整数据(或者请求体)// 这里主状态机指的是process_read()函数,从状态机指的是parse_line()while (((m_check_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK))|| ((line_status = parse_line()) == LINE_OK)) {// 获取一行数据text = get_line();m_start_line = m_checked_idx;printf( "got 1 http line: %s\n", text );switch ( m_check_state ) {case CHECK_STATE_REQUESTLINE: {// 解析请求行,也就是 GET 中的 GET/*通过请求行的解析我们可以判断该HTTP请求的类型(GET/POST),而请求行中最重要的部分就是URL部分将这部分保存下来用于后面的生成HTTP响应*/ret = parse_request_line( text );if ( ret == BAD_REQUEST ) {return BAD_REQUEST;}break;// 正常解析就break}case CHECK_STATE_HEADER: {ret = parse_headers( text );//解析请求头,GET 和 POST 中空行以上,请求行以下的部分if ( ret == BAD_REQUEST ) {return BAD_REQUEST;} else if ( ret == GET_REQUEST ) {//遇到换行符就默认你解析完请求头,不管后面还有没有内容return do_request();//解析具体的请求信息}break;}case CHECK_STATE_CONTENT: {/*解析请求数据,对于GET来说这部分是空的,因为这部分内容已经以明文的方式包含在了请求行中的URL部分了;只有POST的这部分是有数据的*/ret = parse_content( text );if ( ret == GET_REQUEST ) {/*do_request() 需要做:需要首先对 GET请求 和不同的 POST请求(登录、注册、请求图片、视频等等)做不同的预处理,然后分析目标文件的属性,若目标文件存在、对所有用户可读且不是目录时,则使用mmap将其映射到内存地址m_file_address处,并告诉调用者获取文件成功*/return do_request();}line_status = LINE_OPEN;break;}default: {return INTERNAL_ERROR;}}}return NO_REQUEST;//主状态机请求不完整
}

注意在http_conn.h 编写 get_line()函数

// m_start_line是已经解析的字符
// get_line用于将指针向后偏移,指向未处理的字符
/*m_start_line是行在bufffer中的起始位置,将该位置后面的数据赋给text此时从状态机已提前将一行的末尾字符\r\n变为\0\0,所以text可以直接取出完整的行进行解析
*/
char* get_line() { return m_read_buf + m_start_line;
}

  (3)主状态机状态 

>>主状态机逻辑 

主状态机初始状态是CHECK_STATE_REQUESTLINE,通过调用从状态机来驱动主状态机,在主状态机进行解析前,从状态机已经将每一行的末尾\r\n符号改为\0\0,以便于主状态机直接取出对应字符串进行处理。

CHECK_STATE_REQUESTLINE

  1. 主状态机的初始状态,调用parse_request_line函数解析请求行
  2. 解析函数从m_read_buf中解析HTTP请求行,获得请求方法,目标URLHTTP版本号
  3. 解析完成后主状态机的状态变为CHECK_STATE_HEADER
// 解析HTTP请求行,获得请求方法,目标URL,以及HTTP版本号
http_conn::HTTP_CODE http_conn::parse_request_line(char* text) {// 在HTTP报文中,请求行用来说明请求类型,要访问的资源以及所使用的HTTP版本,// 其中各个部分之间通过\t或空格分隔// 请求行中最先含有空格和\t任一字符的位置并返回// GET /index.html HTTP/1.1m_url = strpbrk(text, " \t"); // 判断第二个参数中的字符哪个在text中最先出现// 如果没有空格或\t,则报文格式有误if (! m_url) { return BAD_REQUEST;}// 将该位置改为\0,用于将前面数据取出// GET\0/index.html HTTP/1.1*m_url++ = '\0';    // 置位空字符,字符串结束符// 取出数据,并通过与GET和POST比较,以确定请求方式char* method = text;if ( strcasecmp(method, "GET") == 0 ) { // 忽略大小写比较m_method = GET;} else {return BAD_REQUEST;}// /index.html HTTP/1.1// 检索字符串 str1 中第一个不在字符串 str2 中出现的字符下标。m_version = strpbrk( m_url, " \t" );if (!m_version) {return BAD_REQUEST;}*m_version++ = '\0';// 仅支持HTTP/1.1if (strcasecmp( m_version, "HTTP/1.1") != 0 ) {return BAD_REQUEST;}/*** http://192.168.110.129:10000/index.html* 对请求资源前7个字符进行判断* 这里主要是有些报文的请求资源中会带有http://,这里需要对这种情况进行单独处理*/if (strncasecmp(m_url, "http://", 7) == 0 ) {   m_url += 7;// 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。m_url = strchr( m_url, '/' );}// 同样增加https情况if (strncasecmp(m_url, "https://", 8) == 0 ) {   m_url += 8;m_url = strchr( m_url, '/' );}// 一般的不会带有上述两种符号,直接是单独的/或/后面带访问资源if ( !m_url || m_url[0] != '/' ) {return BAD_REQUEST;}// 请求行处理完毕,将主状态机转移处理请求头m_check_state = CHECK_STATE_HEADER; // 检查状态变成检查头return NO_REQUEST;
}

解析完请求行后,主状态机继续分析请求头。在报文中,请求头和空行的处理使用的同一个函数,这里通过判断当前的text首位是不是\0字符

  • 若是,则表示当前处理的是空行
  • 若不是,则表示当前处理的是请求头 

CHECK_STATE_HEADER

  1. 调用parse_headers函数解析请求头部信息
  2. 判断是空行还是请求头。若是空行,进而判断Content-length是否为0,如果不是0,表明是POST请求,则状态转移到CHECK_STATE_CONTENT;否则说明是GET,则报文解析结束
  3. 若解析的是请求头部字段,则主要分析Connection字段,Content-length字段等
  4. Connection字段判断是keep-alive 还是 close,决定是长连接还是短连接;如果是长连接,则将linger标志设置为true
  5. Content-length字段,这里用于读取post请求的消息体长度
// 解析HTTP请求的一个头部信息
http_conn::HTTP_CODE http_conn::parse_headers(char* text) {   // 遇到空行,表示头部字段解析完毕if( text[0] == '\0' ) {// 判断是GET 还是 POST 请求/*如果HTTP请求有消息体,则还需要读取m_content_length字节的消息体,状态机转移到CHECK_STATE_CONTENT状态*/if ( m_content_length != 0 ) {// POST需要跳转到消息体处理状态m_check_state = CHECK_STATE_CONTENT;return NO_REQUEST;}// 否则说明我们已经得到了一个完整的HTTP请求return GET_REQUEST;} // 解析请求头部连接字段else if ( strncasecmp( text, "Connection:", 11 ) == 0 ) {// 处理Connection 头部字段  Connection: keep-alivetext += 11;// 跳过空格和\t字符text += strspn( text, " \t" );if ( strcasecmp( text, "keep-alive" ) == 0 ) {// 如果是长连接,则将linger标志设置为truem_linger = true;}} // 解析请求头部内容长度字段else if ( strncasecmp( text, "Content-Length:", 15 ) == 0 ) {// 处理Content-Length头部字段text += 15;text += strspn( text, " \t" );m_content_length = atol(text);} // 解析请求头部HOST字段else if ( strncasecmp( text, "Host:", 5 ) == 0 ) {// 处理Host头部字段text += 5;text += strspn( text, " \t" );m_host = text;} else {printf( "oop! unknow header %s\n", text );}return NO_REQUEST;
}

GETPOST请求报文的区别之一是有无消息体部分。GET请求没有消息体,当解析完空行之后,便完成了报文的解析。仅用从状态机的状态(line_status = parse_line()) == LINE_OK语句即可。

POST请求报文中,消息体的末尾没有任何字符

>>http post请求报文的格式
请求行\r\n
请求头\r\n
空行(\r\n)
请求体

所以无法使用从状态机的状态,转而使用主状态机的状态作为循环入口条件

((m_check_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK))

解析完消息体之后,报文的完整解析也就完成了,此时主状态机的状态还是CHECK_STATE_CONTENT。意味着符合循环入口条件,还会再次进入循环。为此,需要在完成请求体解析后,将line_status变量更改为LINE_OPEN,便可跳出循环,完成报文解析任务

while (((m_check_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK))|| ((line_status = parse_line()) == LINE_OK)) {// 获取一行数据text = get_line();m_start_line = m_checked_idx;printf( "got 1 http line: %s\n", text );switch ( m_check_state ) {......case CHECK_STATE_CONTENT: {ret = parse_content( text );if ( ret == GET_REQUEST ) {return do_request();}// 在完成请求体解析后,将line_status变量更改为LINE_OPEN,跳出循环,完成报文解析任务line_status = LINE_OPEN;break;}......}
}

CHECK_STATE_HEADER

  • 仅用于解析POST请求,调用parse_content函数解析请求体
  • 用于保存post请求消息体,为后面的登录和注册做准备
// 我们没有真正解析HTTP请求的消息体,只是判断它是否被完整的读入了
http_conn::HTTP_CODE http_conn::parse_content( char* text ) {// 判断buffer中是否读取了消息体if ( m_read_idx >= ( m_content_length + m_checked_idx ) ){text[ m_content_length ] = '\0';return GET_REQUEST;}return NO_REQUEST;
}

参考和推荐文章:

最新版Web服务器项目详解 - 05 http连接处理(中) (qq.com)icon-default.png?t=N7T8https://mp.weixin.qq.com/s?__biz=MzAxNzU2MzcwMw==&mid=2649274278&idx=7&sn=d1ab62872c3ddac765d2d80bbebfb0dd&chksm=83ffbefeb48837e808caad089f23c340e1348efb94bef88be355f4d9aedb0f9784e1f9e072b1&scene=178&cur_album_id=1339230165934882817#rd【从0开始编写webserver·基础篇#02】服务器的核心---I/O处理单元和任务类 - dayceng - 博客园 (cnblogs.com)icon-default.png?t=N7T8https://www.cnblogs.com/DAYceng/p/17418584.html#%E5%A4%84%E7%90%86%E6%96%B0%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%9A%84%E8%BF%9E%E6%8E%A5%E8%AF%B7%E6%B1%82

相关文章:

WebServer 解析HTTP 请求报文

一、TCP 状态转换 浏览器访问网址&#xff0c;TCP传输全过程 二、TCP协议的通信过程 三、TCP 通信流程 // TCP 通信的流程 // 服务器端 &#xff08;被动接受连接的角色&#xff09; 1. 创建一个用于监听的套接字- 监听&#xff1a;监听有客户端的连接- 套接字&#xff1a;这…...

Golang开发--interface的使用

在Go语言中&#xff0c;接口&#xff08;interface&#xff09;是一种特殊的类型&#xff0c;它定义了一组方法的集合。接口为实现多态性提供了一种机制&#xff0c;允许不同的数据类型实现相同的方法&#xff0c;从而可以以统一的方式处理这些不同类型的对象。接口在Go中广泛用…...

2023 年高教社杯全国大学生数学建模竞赛题目 B 题 多波束测线问题

B 题 多波束测线问题 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀速直线传播&#xff0c;在不同界面上产生反射&#xff0c;利用这一原理&#xff0c;从测量船换能器垂直向海底发射声波信号&#xff0c;并记录从声波发射到信号接收的传播…...

leetcode算法题--生成特殊数字的最少操作

原题链接&#xff1a;https://leetcode.cn/problems/minimum-operations-to-make-a-special-number/description/ 感觉还是比较难想到的。。 func minimumOperations(num string) int {res : len(num)if strings.Contains(num, "0") {res-- }f : func(tail string)…...

数学建模--决策树的预测模型的Python实现

目录 1.算法流程简介 2.算法核心代码 3.算法效果展示 1.算法流程简介 """ 决策树的应用:对泰坦尼克号数据集成员进行预测生死 算法流程还是比较简单的,简单学习一下决策树跟着注释写即可 文章参考:https://zhuanlan.zhihu.com/p/133838427 算法种遇上sklear…...

Linkstech多核并行仿真丨光伏发电系统模型及IEEE 39 bus模型多核并行实测

新能源场站和区域电网作为复杂且具有动态特性的大规模电力系统&#xff0c;需要实时仿真测试来验证其性能、稳定性和响应能力。在这种背景下&#xff0c;多核并行仿真运算显得尤为重要。多核并行仿真能够同时处理电力系统的复杂模型&#xff0c;加速仿真过程&#xff0c;实现接…...

在STS里使用Gradle编译Apache POI5.0.0

1、到官方下面地址下载Gradle最新的版本 Gradle Distributions 2、解压后拷贝到D盘下D:\gradle-8.3-rc-4里 3、配置环境变量 新建系统变量 GRADLE_HOME &#xff0c;值为 路径 4、在 Path 中添加上面目录的 bin 文件路径 &#xff08;可以用 %GRADLE_HOME%\bin&#xff0c…...

golang - 使用有缓冲通道控制并发数

在 Go 语言中&#xff0c;使用带缓冲的通道&#xff08;buffered channels&#xff09;可以有效地控制并发数。带缓冲的通道可以让你限制同时运行的 goroutine 数量&#xff0c;从而避免过度并发导致的资源耗尽问题。以下是一个使用带缓冲通道控制并发数的示例&#xff1a; pa…...

AUTOSAR测试指标

测试方法 1、测试相关时间2、检查各个状态下ECU的情况3、程序编写 1、测试相关时间 序号时间参数描述测试方法时间1T_Wakeup从睡眠模式到网络模式&#xff0c;(上位机)发送NM报文的时间唤醒源的时间100ms2T_START_NM从睡眠模式到网络模式&#xff0c;DUT发送的第一帧NM报文捕获…...

Vue 前端项目使用alibaba矢量库svg图标

Vue 前端项目使用alibaba矢量库svg图标 这里主要是记录 vue项目中使用阿里矢量库图标的操作流程&#xff0c;方便以后查阅&#xff01;&#xff01;&#xff01; 一、简介 iconfont 是由阿里巴巴体验团队打造的&#xff0c;一款设计和前端开发的便捷工具.拥有着很强大且图标内…...

蓝桥杯官网填空题(距离和)

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 两个字母之间的距离定义为它们在字母表中位置的距离。例如 A 和 C 的距离为 2&#xff0c;L 和 Q 的距离为 5。 对于一个字符串&#xff0c;我们称字符串中两两字符…...

【座位调整】Python 实现-附ChatGPT解析

疫情期间课堂的座位进行了特殊的调整,不能出现两个同学紧挨着,必须隔至少一个空位,给你一个整数数组desk,表示当前座位的占座情况,由若于0和1组成,其中 0 表示没有占位,1表示占位。在不改变原有座位秩序情况下,还能安排坐几个人? 输入描述: 第一行是一个数组,表示作为…...

前端面试基础面试题——5

1.react 和 vue 的区别是什么&#xff1f; 2.虚拟DOM的原理&#xff1f;优点与缺点&#xff1f; 3.类组件和函数组件之间的区别是&#xff1f; 4.state 和 props 区别是什么&#xff1f; 5.React 中 refs 是做什么的&#xff1f; 6.什么是高阶组件&#xff1f; 7.讲讲什么…...

Java高并发系列: 使用wait - notify实现高效异步方法

1. 背景 在项目开发中, 通常会有异步执行操作, 例如: 提交一个异步清空一系列数据库中ID ${_id} 的记录, 这个时候通常的做法是主线程将任务添加到一个异步队列中, 后台维护一个线程不断地循环扫描这个队列, 如果有需要执行的任务, 则执行相应的逻辑. 如下图所示: 2. 一个简…...

业务安全详解

文章目录 一、 业务安全概述1.1 业务安全现状1.1.1 业务逻辑漏洞1.1.2 黑客攻击的目标 二、 业务安全测试2.1 业务安全测试流程2.1.1 测试准备2.1.2 业务调研2.1.3 业务建模2.1.4 业务流程梳理2.1.5 业务风险点识别2.1.6 开展测试2.1.7 撰写报告 三、 业务安全经典场景3.1 业务…...

算法笔记--最大连续1的个数Ⅲ

leetcode题目链接:1004. 最大连续1的个数 III 题目描述 给定一个二进制数组 nums 和一个整数 k&#xff0c;如果可以翻转最多 k 个 0 &#xff0c;则返回 数组中连续 1 的最大个数 。 思路 这里可以转换思路&#xff0c;让题意更加明确:即&#xff0c;求一个最大连续区间…...

Linux CentOS7 添加中文输入法

在安装CentOS7时&#xff0c;现在默认安装了桌面中文系统。可以切换为英文&#xff0c;中英文可以按要求随时更换。而在CentOS7桌面环境下&#xff0c;显示中文非常方便、正确&#xff0c;但不能录入中文。 在远程登录系统的情况下&#xff0c;不论是系统语言&#xff08;LANG…...

Python接口自动化封装导出excel方法和读写excel数据

一、首先需要思考&#xff0c;我们在页面导出excel&#xff0c;用python导出如何写入文件的 封装前需要确认python导出excel接口返回的是一个什么样的数据类型 如下&#xff1a;我们先看下不对返回结果做处理&#xff0c;直接接收数据类型是一个对象&#xff0c;无法获取返回值…...

React三属性之:refs

作用 refs是为了获取节点,使用场景主要在需要操作dom的时候,比如echarts,就需要真实的dom节点 使用 import React from "react"; class RefsTest extends React.Component{state {value:输入框的值}refPlan React.createRef()logRef ()>{console.log(this.r…...

将Vue项目迁移到微信小程序中

文章目录 一、创建一个Vue.js的应用程序二、构建微信小程序1. 安装微信小程序构建工具2. 在vuejs项目的根目录中创建一个wepy.confgjs文件3. 在vuejs项目的根目录中运行构建 三、错误记录1. 找不到编译器&#xff1a;wepy-compiler-sass 一、创建一个Vue.js的应用程序 使用 Vu…...

php权限调整强制用户退出的解决方案

要强制用户重新登录&#xff0c;你可以采取以下步骤&#xff1a; 当用户登录时&#xff0c;将用户的登录状态和其他相关信息存储在服务器端。例如&#xff0c;你可以将用户ID、用户名或其他标识符存储在服务器的会话&#xff08;session&#xff09;中。当管理员修改用户的某些…...

[uniapp]踩坑日记 unexpected character > 1或‘=’>1 报错

在红色报错文档里下滑&#xff0c;找到Show more 根据提示看是缺少标签&#xff0c;如果不是缺少标签&#xff0c;看看view标签内容是否含有<、>、>、<号,把以上符合都进行以<号为例做{{“<”}}处理...

面试求职-经典面试问题

16个经典面试问题回答思路 面试过程中&#xff0c;面试官会向应聘者发问&#xff0c;而应聘者的回答将成为面试官考虑是否接受他的重要依据。对应聘者而言&#xff0c;了解这些问题背后的“猫腻”至关重要。本文对面试中经常出现的一些典型问题进行了整理&#xff0c;并给出相…...

在Linux服务器上部署Tornado项目

要在Linux服务器上部署Tornado项目&#xff0c;你可以按照以下步骤进行操作&#xff1a; 1、准备服务器&#xff1a; 确保你的服务器上安装了Python。Tornado通常与Python 3兼容&#xff0c;因此建议安装Python 3.x。 安装和配置一个Web服务器&#xff0c;如Nginx或Apache&a…...

JWT认证、drf-jwt安装和简单使用、实战之使用Django auth的User表自动签发、实战之自定义User表,手动签发

一 JWT认证 在用户注册或登录后&#xff0c;我们想记录用户的登录状态&#xff0c;或者为用户创建身份认证的凭证。 我们不再使用Session认证机制&#xff0c;而使用Json Web Token&#xff08;本质就是token&#xff09;认证机制。Json web token (JWT), 是为了在网络应用环境…...

conda常用命令及问题解决-创建虚拟环境

好久没写博文了&#xff0c;感觉在学习的过程中还是要注意积累与分享&#xff0c;这样利人利己。 conda包清理&#xff0c;许多无用的包是很占用空间的 conda clean -p //删除没有用的包 conda clean -y -all //删除pkgs目录下所有的无用安装包及cacheconda创建虚拟环境…...

严选算法模型质量保障

在算法模型整个生命周期**&#xff08;算法模型生命周期&#xff1a;初始训练数据 --> 模型训练 --> 模型评估 --> 模型预估 --> 训练数据&#xff09;**中&#xff0c;任何环节的问题引入都可能导致算法模型质量问题。所以我们在做模型质量保障的过程中&#xff0…...

学习Bootstrap 5的第七天

目录 徽章 徽章 实例 上下文徽章 实例 胶囊徽章 实例 元素内的徽章 实例 进度条 基础进度条 实例 进度条高度 实例 彩色进度条 实例 条纹进度条 实例 动画进度条 实例 混合色彩进度条 实例 徽章 徽章 在 Bootstrap 中&#xff0c;徽章&#xff08;Badg…...

VirtualBox(内有Centos 7 示例安装)

1常见概念以及软件安装 1.1 虚拟化技术&#xff1a; 虚拟化技术指的是将计算机的各种硬件资源加以抽象、转换、分割&#xff0c;最后组合 起来的技术。其目的和作用主要是打破硬件资源不可分的情况&#xff0c;方便程序员自 己集成所需资源。 1.2 Virtual Box 其是虚拟化技术作…...

在 Git 中删除不再位于远程仓库中的本地分支

git 删除远端已经被删除然而本地还存在的分支 1. 修剪不在远程仓库上的跟踪分支 git remote prune origin如果git仓库将branch1被删除&#xff0c;可以用用git remote prune origin删除在本地电脑上的remotes/origin/branch1 git remote show origin可以看到下面所示&#xf…...