Sylar C++高性能服务器学习记录23 【Http模块-知识储备篇】

news2025/1/18 17:56:13

早在19年5月就在某站上看到sylar的视频了,一直认为这是一个非常不错的视频。
由于本人一直是自学编程,基础不扎实,也没有任何人的督促,没能坚持下去。
每每想起倍感惋惜,遂提笔再续前缘。

为了能更好的看懂sylar,本套笔记会分两步走,每个系统都会分为两篇博客。
分别是【知识储备篇】和【代码分析篇】
(ps:纯粹做笔记的形式给自己记录下,欢迎大家评论,不足之处请多多赐教)
QQ交流群:957100923


Http模块-知识储备篇

一、Http的基础知识

如果你对Http协议比较陌生,那么请先看下 菜鸟教程关于Http 的介绍。
只要看完了菜鸟教程Http相关的介绍我们就可以开始本篇的讲解了,阅读本篇不需要深入了解Http协议。
深入的知识需要自行学习。

为了方便讲解,摘录部分内容到下面。


客户端请求消息

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。
在这里插入图片描述
请求行(Request Line):

  • 方法:如 GET、POST、PUT、DELETE等,指定要执行的操作。 请求
    URI(统一资源标识符):请求的资源路径,通常包括主机名、端口号(如果非默认)、路径和查询字符串。 HTTP 版本:如 HTTP/1.1
    或 HTTP/2。 请求行的格式示例:GET /index.html HTTP/1.1

请求头(Request Headers):

  • 包含了客户端环境信息、请求体的大小(如果有)、客户端支持的压缩类型等。
    常见的请求头包括Host、User-Agent、Accept、Accept-Encoding、Content-Length等。

空行:

  • 请求头和请求体之间的分隔符,表示请求头的结束。

请求体(可选):

  • 在某些类型的HTTP请求(如 POST 和 PUT)中,请求体包含要发送给服务器的数据。

客户端请求:

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Connection: keep-alive

服务器响应消息

HTTP 响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
在这里插入图片描述
状态行(Status Line):

  • HTTP 版本:与请求消息中的版本相匹配。 状态码:三位数,表示请求的处理结果,如 200 表示成功,404 表示未找到资源。
    状态信息:状态码的简短描述。 状态行的格式示例:HTTP/1.1 200 OK

响应头(Response Headers):

  • 包含了服务器环境信息、响应体的大小、服务器支持的压缩类型等。
    常见的响应头包括Content-Type、Content-Length、Server、Set-Cookie等。

空行:

  • 响应头和响应体之间的分隔符,表示响应头的结束。

响应体(可选):

  • 包含服务器返回的数据,如请求的网页内容、图片、JSON数据等。

服务端响应:

HTTP/1.1 200 OK
Date: Wed, 18 Apr 2024 12:00:00 GMT
Server: Apache/2.4.1 (Unix)
Last-Modified: Wed, 18 Apr 2024 11:00:00 GMT
Content-Length: 12345
Content-Type: text/html; charset=UTF-8

<!DOCTYPE html>
<html>
<head>
    <title>Example Page</title>
</head>
<body>
    <h1>Hello, World!</h1>
    <!-- The rest of the HTML content -->
</body>
</html>

总结下来就是Http协议消息体分两大块,分别是【请求】和【响应】。

请求:请求行 请求头 空行 请求体
响应:状态行 响应头 空行 响应体

了解这些后我们继续…


二、明确目标

为了方便的处理请求和响应,我们需要对Http协议进行封装。
千万不要看到 协议 就觉得很高大上,说白了就是一定格式的文本而已。

那么:协议报文的封装 = 对一段固定格式的文本的封装。

我们要封装成什么样呢?

1.得到一段固定格式的文本后,能解析成C++的对象,方便读取文本中的内容和修改该文本中的内容。
2.根据C++的封装对象,能快速的构建出一段对应格式的文本。

ok 这就是我们要封装的目的,和需要实现的目标。


三、梳理封装内容

由于消息体分 请求响应,所以我们就需要单独封装这两个。
这就出现了 HttpRequest(请求封装对象)HttpResponse(响应封装对象)

由于这两个消息体都有各自的 【行 头 体】,所以 HttpRequestHttpResponse必然有对应的字段来存储 【行 头 体】 信息。
也应该有对应的方法对各自的 【行 头 体】 进行 【增 删 改 查】

当然,对应的 【行 头 体】 也有可能需要再次封装,我们分别来梳理一下。


HttpRequest 应该有的字段:

请求行:(可封装)
请求行 = 请求方法 + URI + 协议版本
URI = 协议protocol + 主机名host(主机域名hostname+端口号port)+ 文件路径 + 参数 + 锚

  • 请求方法:(可封装)
  • 协议protocol:(字符串)
  • 主机名host:(字符串)
  • 文件路径:(字符串)
  • 参数:(字符串,可封装成map)
  • 锚:(字符串)

请求头:(可封装成map,其中cookie可再次封装成map)

请求体:(字符串)

HttpRequest 应该有的方法:

  1. 请求方法的get/set方法
  2. 版本信息的get/set方法
  3. 请求路径的get/set方法
  4. 查询参数的get/set方法
  5. 请求参数的增删改查方法
  6. 请求头的增删改查方法
  7. cookie的增删改查方法
  8. body的get/set方法
  9. body的追加方法
  10. 对象转换为字符串的方法

HttpResponse 应该有的字段:
响应行:
响应行 = 协议 + 版本 + 状态号 + 状态描述

  • 状态:(可封装)
  • 版本:(字符串)

响应头:(可封装为map)

响应体:(字符串)


HttpResponse 应该有的方法:

  1. 响应状态的get/set方法
  2. 响应版本的get/set方法
  3. 响应头的增删改查方法
  4. 响应体的增删改查方法
  5. 响应体的追加方法
  6. 对象转为字符串的方法

如此,我们就可以开始编写对应的代码了。


四、http_parser的使用

参考博客
源码地址 http-parser

初始化
/*
* @brief  用于初始化 http_parser 结构体,为后续的 HTTP 消息解析做好准备
* @param  [IN]  parser  指向一个待初始化的 http_parser 结构体实例
* @param  [IN]  type    枚举值,指定 parser 应该解析 HTTP 请求还是响应
*                           HTTP_REQUEST - 解析请求
*                           HTTP_RESPONSE - 解析响应
*                           HTTP_BOTH - 两者都可以
*/
void http_parser_init(http_parser *parser, enum http_parser_type type);
设置回调函数
/*
* @brief  设置默认的回调函数指针,这些回调函数会在解析HTTP消息的不同阶段被调用。
* 	    通知用户程序有关HTTP请求或响应的详细信息。
* @param  [IN]  settings  指向一个 http_parser_settings 结构体的指针
* 						这个结构体包含了指向各种回调函数的指针,具体如下
*                          	on_message_begin     - 开始解析时回调
*                          	on_url               - 解析URL时回调
*                          	on_status            - 解析响应状态描述信息时回调
*                          	on_header_field      - 解析请求头key值时回调
*                          	on_header_value      - 解析请求头value时回调
*                          	on_headers_complete  - 开始解析请求头时回调
*                          	on_body              - 解析请求或响应体时回调
*                          	on_message_complete  - 解析完成时回调
*                           
*/
void http_parser_settings_init(http_parser_settings *settings);
解析数据
/*
* @brief  解析HTTP请求或响应的消息数据
* @param  [IN]  parser      指向一个已初始化的 http_parser 结构体实例
* 						  该结构体包含了 HTTP 消息的解析状态和其他相关信息,主要有以下参数
*                              status_code - http响应状态码
*                              method      - http请求方式。HTTP_GET,HTTP_POST
* @param  [IN]  settings    指向一个 http_parser_settings 结构体,其中定义了一系列回调函数。
* 						  当解析到 HTTP 消息的不同部分(比如请求行、头部、主体等)时,对应的回调函数会被调用。
* @param  [IN]  data        待解析的HTTP数据
* @param  [IN]  len         待解析的HTTP数据长度
* @return  解析过程中成功处理了多少字节的数据,
* 		如果小于len,可能是因为遇到了解析错误,或者是缓冲区中的数据不足以构成一个完整的 HTTP 消息片段。
*/
size_t http_parser_execute(http_parser *parser, const http_parser_settings *settings, const char *data, size_t len);
示例代码
#include <stdio.h>
#include "http_parser.h"
#include <string>
#include <map>

// 用于解析的全局变量
std::map<std::string, std::string> mapReqHeadField;
std::string strReqUrl;
std::string strReqBody;
std::string strReqFieldKey;

std::map<std::string, std::string> mapRespHeadField;
std::string strRespStatus;
std::string strRespBody;
std::string strRespFieldKey;

// 用于解析http请求的回调函数
int onReqMessageBegin(http_parser* pParser);
int onReqHeaderComplete(http_parser* pParser);
int onReqMessageComplete(http_parser* pParser);
int onReqURL(http_parser* pParser, const char *at, size_t length);
int onReqHeaderField(http_parser* pParser, const char *at, size_t length);
int onReqHeaderValue(http_parser* pParser, const char *at, size_t length);
int onReqBody(http_parser* pParser, const char *at, size_t length);

// 用于解析http响应的回调函数
int onRespMessageBegin(http_parser* pParser);
int onRespHeaderComplete(http_parser* pParser);
int onRespMessageComplete(http_parser* pParser);
int onRespStatus(http_parser* pParser, const char *at, size_t length);
int onRespHeaderField(http_parser* pParser, const char *at, size_t length);
int onRespHeaderValue(http_parser* pParser, const char *at, size_t length);
int onRespBody(http_parser* pParser, const char *at, size_t length);


int main(int argc, char* argv[])
{
    // 解析http请求

    // 待解析的请求报文
    std::string strHttpReq;
    strHttpReq += "POST /http-parser HTTP/1.1\r\n";
    strHttpReq += "Host: 127.0.0.1:10010\r\n";
    strHttpReq += "Accept: */*\r\n";
    strHttpReq += "Content-Type: application/json\r\n";
    strHttpReq += "Content-Length: 25\r\n";
    strHttpReq += "\r\n";
    strHttpReq += "{\"reqmsg\": \"Hello World\"}";

    http_parser httpReqParser;
    http_parser_settings httpReqSettings;

    // 初使化解析器
    http_parser_init(&httpReqParser, HTTP_REQUEST);
    // 设置回调函数
    http_parser_settings_init(&httpReqSettings);
    httpReqSettings.on_message_begin = onReqMessageBegin;
    httpReqSettings.on_headers_complete = onReqHeaderComplete;
    httpReqSettings.on_message_complete = onReqMessageComplete;
    httpReqSettings.on_url = onReqURL;
    httpReqSettings.on_header_field = onReqHeaderField;
    httpReqSettings.on_header_value = onReqHeaderValue;
    httpReqSettings.on_body = onReqBody;

    // 解析请求
    int reqSize = strHttpReq.size();
    int nParseSize = http_parser_execute(&httpReqParser, &httpReqSettings, strHttpReq.c_str(), reqSize);
    if(nParseSize < reqSize){
        printf("http_parser_execute http request failed.\n");
        return -1;
    }

    // 解析成功,打印解析结果
    if(httpReqParser.method == HTTP_GET){
        printf("method: Get\n");
    }else if(httpReqParser.method == HTTP_POST){
        printf("method: Post\n");
    }else if(httpReqParser.method == HTTP_HEAD){
        printf("method: Head\n");
    }else{
        printf("method: other\n");
    }

    printf("url: %s \n", strReqUrl.c_str());
    printf("req heads:\n");
    for (std::map<std::string, std::string>::iterator iter = mapReqHeadField.begin(); iter != mapReqHeadField.end(); ++iter){
        printf("\t %s : %s \n", iter->first.c_str(), iter->second.c_str());
    }
    printf("req body: %s \n", strReqBody.c_str());

    printf("==========================================\n");

    // 解析http响应


    // 待解析的响应报文
    std::string strHttpResponse;
    strHttpResponse += "HTTP/1.1 200 OK\r\n";
    strHttpResponse += "Server: nginx/1.18.0\r\n";
    strHttpResponse += "Accept: */*\r\n";
    strHttpResponse += "Connection: keep-alive\r\n";
    strHttpResponse += "\r\n";
    strHttpResponse += "{\"respmsg\": \"Welcome to http-parser\"}\r\n";

    http_parser httpRespParser;
    http_parser_settings httpRespSettings;

    // 初使化解析器
    http_parser_init(&httpRespParser, HTTP_RESPONSE);
    // 设置回调函数
    http_parser_settings_init(&httpRespSettings);
    httpRespSettings.on_message_begin = onRespMessageBegin;
    httpRespSettings.on_headers_complete = onRespHeaderComplete;
    httpRespSettings.on_message_complete = onRespMessageComplete;
    httpRespSettings.on_status = onRespStatus;
    httpRespSettings.on_header_field = onRespHeaderField;
    httpRespSettings.on_header_value = onRespHeaderValue;
    httpRespSettings.on_body = onRespBody;

    // 解析响应
    int responseSize = strHttpResponse.size();
    nParseSize = http_parser_execute(&httpRespParser, &httpRespSettings, strHttpResponse.c_str(), responseSize);
    if(nParseSize < responseSize){
        printf("http_parser_execute http response failed.\n");
        return -1;
    }

    // 解析成功,打印解析结果
    printf("code: %d\n", httpRespParser.status_code); 
    printf("status: %s \n", strRespStatus.c_str());
    printf("resp heads:\n");
    for (std::map<std::string, std::string>::iterator iter = mapRespHeadField.begin(); iter != mapRespHeadField.end(); ++iter){
        printf("\t %s : %s \n", iter->first.c_str(), iter->second.c_str());
    }
    printf("resp body: %s \n", strRespBody.c_str());
    return 0;
}


int onReqMessageBegin(http_parser* pParser)
{
    // 开始解析报文
    printf("onReqMessageBegin call \n");
    return 0;
}
int onReqHeaderComplete(http_parser* pParser)
{
    // 报文头解析完成
    // HTTP报文头是以两个 \r\n 结尾, 如果解析不到两个 \r\n, 说明http报文格式有问题或者报文不完整,这个回调不会被调用
    printf("onReqHeaderComplete call \n");
    return 0;
}
int onReqMessageComplete(http_parser* pParser)
{
    // 全部解析完成
    printf("onReqMessageComplete call \n");
    return 0;
}
int onReqURL(http_parser* pParser, const char *at, size_t length)
{
    // 解析URL
    strReqUrl.assign(at, length);
    return 0;
}

int onReqHeaderField(http_parser* pParser, const char *at, size_t length)
{
    // 解析请求头key
    strReqFieldKey.assign(at, length);
    return 0;
}
int onReqHeaderValue(http_parser* pParser, const char *at, size_t length)
{
    // 解析请求头value
    std::string strValue(at, length);
    mapReqHeadField.insert(std::make_pair(strReqFieldKey, strValue));
    return 0;
}
int onReqBody(http_parser* pParser, const char *at, size_t length)
{
    // 解析请求或响应体
    strReqBody.append(at, length);
    return 0;
}


// ==================

int onRespMessageBegin(http_parser* pParser)
{
    // 开始解析报文
    printf("onRespMessageBegin call \n");
    return 0;
}
int onRespHeaderComplete(http_parser* pParser)
{
    // 报文头解析完成
    // HTTP报文头是以两个 \r\n 结尾, 如果解析不到两个 \r\n, 说明http报文格式有问题或者报文不完整,这个回调不会被调用
    printf("onRespHeaderComplete call \n");
    return 0;
}
int onRespMessageComplete(http_parser* pParser)
{
    // 全部解析完成
    printf("onRespMessageComplete call \n");
    return 0;
}

int onRespStatus(http_parser* pParser, const char *at, size_t length)
{
    // 解析响应状态码
    strRespStatus.assign(at, length);
    return 0;
}
int onRespHeaderField(http_parser* pParser, const char *at, size_t length)
{
    // 解析响应头key
    strRespFieldKey.assign(at, length);
    return 0;
}
int onRespHeaderValue(http_parser* pParser, const char *at, size_t length)
{
    // 解析响应头value
    std::string strValue(at, length);
    mapRespHeadField.insert(std::make_pair(strRespFieldKey, strValue));
    return 0;
}
int onRespBody(http_parser* pParser, const char *at, size_t length)
{
    // 解析请求或响应体
    strRespBody.append(at, length);
    return 0;
}

打印结果
在这里插入图片描述


五、总结

Http模块的基本封装其实就是对固定格式的文本内容进行封装,没有什么高深的知识。
只要把看似复杂的专业术语翻译成白话文,再看源码一定会很轻松。
Http模块基础知识篇,思来想去也就这些东西了。


【最后求关注、点赞、转发】
QQ交流群:957100923

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

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

相关文章

Python10 python多线程

1.什么是python多线程 Python的多线程指的是在一个Python程序中同时运行多个线程&#xff0c;以达到并发执行多个任务的目的。线程是操作系统能够进行运算调度的最小单位&#xff0c;它被包含在进程之中&#xff0c;是进程中的实际运作单位。 在Python中&#xff0c;多线程的…

C# OCCT Winform 选中模型改变状态

选中状态设置 _context new AIS_InteractiveContext(_viewer);var selectionDrawer new Prs3d_Drawer();selectionDrawer.SetColor(Colors.Selection);selectionDrawer.SetDisplayMode(1);selectionDrawer.SetTransparency(0.1f);_context.SetSelectionStyle(selectionDrawe…

【机器学习】机器学习重要方法——无监督学习:理论、算法与实践

文章目录 引言第一章 无监督学习的基本概念1.1 什么是无监督学习1.2 无监督学习的主要任务 第二章 无监督学习的核心算法2.1 聚类算法2.1.1 K均值聚类2.1.2 层次聚类2.1.3 DBSCAN聚类 2.2 降维算法2.2.1 主成分分析&#xff08;PCA&#xff09;2.2.2 t-SNE 2.3 异常检测算法2.3…

【Gradio】Chatbot | 如何使用 Gradio Blocks 创建自定义聊天机器人

简介 重要提示&#xff1a;如果您刚开始接触&#xff0c;我们建议使用 gr.ChatInterface 来创建聊天机器人——它是一个高级抽象&#xff0c;使得可以快速创建漂亮的聊天机器人应用程序&#xff0c;往往只需一行代码。在这里了解更多信息。 本教程将展示如何使用 Gradio 的低级…

【网络安全】简单的免杀方法(非常详细)零基础入门到精通,收藏这一篇就够了_免杀加壳工具

目录 一、免杀的概念 二、免杀系统搭建 三、免杀工具介绍 1、myccl 2、C32asm 3、OD 4、LordPE 5、ImportREC 6、VC6.0/visual studio 7、数字签名 四、关于杀软排名不分前后 1、360。 2、金山毒霸 3、江民 4、瑞星 5、安天防线 6、卡巴斯基 7、NOD32 8、诺…

【日记】被客户一顿输出该怎么办(431 字)

正文 上午有个客户在电话里对着我一顿输出&#xff0c;说他们没有发票财务账务没法处理怎么怎么的。话里话外满满一股 “全是你们的错” 的味道。 当时我很想笑&#xff0c;大姐&#xff0c;你对我输出有啥用啊。票是上级行开的&#xff0c;我们又没有开票权限&#xff0c;对我…

openEuler23.09安装MySQL8.4.0

在openEuler-23.09上安装MySQL8.4.0 一、MySQL数据库服务环境搭建 操作系统版本 openEuler-23.09-x86_64-dvd.iso &#xff0c;安装步骤此处省略。。。 MySQL8.4.0下载地址 https://dev.mysql.com/downloads/mysql/ 1.1、下载及上传mysql二进制安装包 上传mysql-8.4.0-linu…

游戏中插入音效

一、背景音乐 准备&#xff1a;素材音乐 方法&#xff1a; 1、方法1&#xff1a; (1) 将背景音乐 bgAudio 拖放到Hierarchy面板 (2) 选中 bgAudio&#xff0c;勾选开始运行就播放、循环播放。调节音量&#xff08;volume) 2、方法2&#xff1a; (1) Create Empty&#x…

Zabbix自定义监控JAVA进程

一.定义脚本 二 .ZABBIX得agent允许以root身份执行 三. Zabbix测试自定item是否成功 四.ZABBIX服务端web添加新得item项 五.查看最新数据&#xff0c;取值成功

002.Linux CentOS7 安装

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

客户端输入网址后发生的全过程解析(协议交互、缓存、渲染)

目录 1. 输入 URL 并按下回车键2. DNS 解析3. TCP 连接4. 发送 HTTP 请求5. 服务器处理请求6. 发送 HTTP 响应7. 浏览器接收响应8. 渲染网页9. 执行脚本10. 处理其他资源11. TLS/SSL 加密&#xff08;如果使用 HTTPS&#xff09;握手过程 12. 协议协商和优化 总结 1. 输入 URL …

有关排序的算法

目录 选择法排序 冒泡法排序 qsort排序&#xff08;快速排序&#xff09; qsort排序整型 qsort排序结构体类型 排序是我们日常生活中比较常见的问题&#xff0c;这里我们来说叨几个排序的算法。 比如有一个一维数组 arr[8] {2,5,3,1,7,6,4,8},我们想要把它排成升序&#…

苹果将推出全新AI培训课程;生成式AI手机市场将迎来爆发式增长

&#x1f989; AI新闻 &#x1f680; 苹果将推出全新AI培训课程 摘要&#xff1a;IT之家消息&#xff0c;苹果宣布&#xff0c;今年秋季将在6个国家的18所开发者学院推出AI培训课程&#xff0c;目标群体为学生、导师和校友。课程涵盖机器学习模型的构建及部署&#xff0c;Cor…

姜萍的启示:分数不是唯一,天赋引领专业选择超越名校

你好&#xff0c;我是三桥君。 24年高考帷幕落下&#xff0c;一场新的思考与选择悄然来临。 对于每一位高考考生&#xff0c;学校和专业都是开启大学新生活的两个前置必选项。 在这关键时刻&#xff0c;全网媒体却被一则关于“一名17岁中专女学生姜萍在全球数学竞赛获得第12名”…

数字孪生涉及到的9大技术栈,都是难啃骨头呀。

数字孪生涉及到多个技术栈&#xff0c;包括但不限于以下几个方面&#xff1a; 数据采集和传感器技术&#xff1a; 数字孪生需要实时获取物理世界的数据&#xff0c;因此需要使用各种传感器技术&#xff08;如温度传感器、压力传感器、运动传感器等&#xff09;来采集数据&…

排序(3)【归并排序】【计数排序】【排序算法度及其稳定性分析】

一.归并排序 归并排序&#xff08;MERGE-SORT&#xff09;是建立在归并操作上的一种有效的排序算法,该算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每个子序列有…

C++ 66 之 类模版

#include <iostream> #include <string> using namespace std;// 习惯性 < >中 类模板用class 普通的函数模板就用typename // template<class NAMETYPE, class AGETYPE> template<class NAMETYPE, class AGETYPE int> // 可以设置默认的类型值…

集团门户网站的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;论坛管理&#xff0c;集团文化管理&#xff0c;基础数据管理&#xff0c;公告通知管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;论坛&#xff0…

收银系统源码-千呼新零售2.0【线下促销】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货等连锁店使用。 详细介绍请查看下…

在LangChain中,LLM(大型语言模型)和LLM Chain的区别是什么?

简单来说&#xff0c;LLM是一个大型语言模型&#xff0c;而LLM Chain是由多个LLM或其他组件组成的链式结构&#xff0c;用于在LangChain中构建复杂的自然语言处理流程。 Direct LLM Interface: 直接大型语言模型&#xff08;LLM&#xff09;接口&#xff1a; llm Open…