C++服务器 支持http、tcp protobuf、websocket,linux开源框架 零依赖轻松编译部署 Reactor
开源地址: https://github.com/crust-hub/tubekit/tree/main
Github:https://github.com/gaowanlu
诚招有兴趣的小伙伴加入开发维护
Tubekit
The C++ TCP server framework based on the Reactor model continues to implement POSIX thread pool, Epoll, non blocking IO, object pool, log, socket network programming, support the dynamic library to implement custom protocol extensions, and use http parser to process http requests. Currently only supports Linux systems
Platform: Linux
Protocol: HTTP
TCP Stream(Protobuf)
WebSocket
Get Start
prepare
$ sudo apt update
$ sudo apt install protobuf-compiler libprotobuf-dev
$ apt install g++ cmake make
$ git clone https://github.com/crust-hub/tubekit.git
Build
$ cd tubekit
$ cd protocol
$ make
$ cd ..
$ cmake .
$ make -j3
Config
$ vim bin/config/main.ini
Run
$ chmod +x ./run.sh
$ ./run.sh
Stop
$ chmod +x ./kill.sh
$ ./kill.sh
App
support tcp keep-alive stream (protobuf) and http app (http-parser)、websocket
Directory Structure
Directory Structure Link
Third Party
@http-parser
@lua
HTTP样例
#include "app/http_app.h"
#include <string>
#include <vector>
#include <filesystem>
#include <tubekit-log/logger.h>#include "utility/mime_type.h"
#include "utility/url.h"using std::string;
using std::vector;
using tubekit::app::http_app;
using tubekit::connection::http_connection;
namespace fs = std::filesystem;
namespace utility = tubekit::utility;class html_loader
{
public:static string load(string body){static string frame1 = "<!DOCTYPE html>\<html>\<head>\<title></title>\</head>\<body>";static string frame2 = "</body>\</html>";return frame1 + body + frame2;}static string a_tag(string url, string text){string frame = "<a href=\"" + url + "\">" + text + "</a></br>";return frame;}
};void http_app::process_connection(tubekit::connection::http_connection &m_http_connection)
{m_http_connection.m_buffer.set_limit_max(202300);// load callbackm_http_connection.destory_callback = [](http_connection &m_connection) -> void{if (m_connection.ptr){FILE *file = (FILE *)m_connection.ptr;::fclose(file);m_connection.ptr = nullptr;}};m_http_connection.process_callback = [](http_connection &connection) -> void{string url = utility::url::decode(connection.url);auto find_res = url.find("..");if (std::string::npos != find_res){connection.set_response_end(true);return;}const string prefix = "/";fs::path t_path;if (url.empty() || url[0] != '/'){t_path = prefix + url;}else{t_path = url;}if (fs::exists(t_path) && fs::status(t_path).type() == fs::file_type::regular){std::string mime_type;try{mime_type = utility::mime_type::get_type(t_path.string());}catch (...){mime_type = "application/octet-stream";}std::string response = "HTTP/1.1 200 OK\r\nServer: tubekit\r\n";response += "Content-Type: ";response += mime_type + "\r\n\r\n";try{connection.m_buffer.write(response.c_str(), response.size());}catch (const std::runtime_error &e){LOG_ERROR(e.what());}connection.ptr = nullptr;connection.ptr = ::fopen(t_path.c_str(), "r");if (connection.ptr == nullptr){connection.set_response_end(true);return;}// Write when the contents of the buffer have been sent write_end_callback will be executed,// and the response must be set response_end to true, then write after write_end_callback will be continuously recalledconnection.write_end_callback = [](http_connection &m_connection) -> void{char buf[202300] = {0};int len = 0;len = ::fread(buf, sizeof(char), 202300, (FILE *)m_connection.ptr);if (len > 0){try{m_connection.m_buffer.write(buf, len);}catch (const std::runtime_error &e){LOG_ERROR(e.what());}}else{m_connection.set_response_end(true);}};return;}if (fs::exists(t_path) && fs::status(t_path).type() == fs::file_type::directory){connection.ptr = nullptr;const char *response = "HTTP/1.1 200 OK\r\nServer: tubekit\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n";try{connection.m_buffer.write(response, strlen(response));}catch (const std::runtime_error &e){LOG_ERROR(e.what());}// generate dir listvector<string> a_tags;for (const auto &dir_entry : fs::directory_iterator{t_path}){std::string sub_path = dir_entry.path().string(); //.substr(prefix.size());a_tags.push_back(html_loader::a_tag(utility::url::encode(sub_path), sub_path));}string body;for (const auto &a_tag : a_tags){body += a_tag;}string html = html_loader::load(body);try{connection.m_buffer.write(html.c_str(), html.size());}catch (const std::runtime_error &e){LOG_ERROR(e.what());}connection.set_response_end(true);return;}const char *response = "HTTP/1.1 404 Not Found\r\nServer: tubekit\r\nContent-Type: text/text; charset=UTF-8\r\n\r\n";try{connection.m_buffer.write(response, strlen(response));}catch (const std::runtime_error &e){LOG_ERROR(e.what());}connection.set_response_end(true);};
}
protobuf样例
#include "app/stream_app.h"
#include "proto_res/proto_cmd.pb.h"
#include "proto_res/proto_example.pb.h"
#include "proto_res/proto_message_head.pb.h"
#include <tubekit-log/logger.h>
#include <string>
#include <set>
#include "thread/mutex.h"
#include "utility/singleton.h"
#include "connection/connection_mgr.h"
#include "socket/socket.h"
#include "socket/socket_handler.h"using tubekit::app::stream_app;
using tubekit::connection::connection_mgr;
using tubekit::connection::stream_connection;
using tubekit::socket::socket;
using tubekit::socket::socket_handler;
using tubekit::utility::singleton;namespace tubekit::app
{std::set<void *> global_player;tubekit::thread::mutex global_player_mutex;
}int process_protocol(tubekit::connection::stream_connection &m_stream_connection, ProtoPackage &package)
{// EXAMPLE_REQif (package.cmd() == ProtoCmd::EXAMPLE_REQ){ProtoExampleReq exampleReq;if (exampleReq.ParseFromString(package.body())){LOG_ERROR("%s", exampleReq.testcontext().c_str());// std::cout << exampleReq.testcontext() << std::endl;}else{return -1;}return 0;}return -1;
}void stream_app::process_connection(tubekit::connection::stream_connection &m_stream_connection)
{using tubekit::app::global_player;using tubekit::app::global_player_mutex;uint64_t all_data_len = m_stream_connection.m_recv_buffer.can_readable_size();char *all_data_buffer = new char[all_data_len];m_stream_connection.m_recv_buffer.copy_all(all_data_buffer, all_data_len);uint64_t offset = 0;do{char *tmp_buffer = all_data_buffer + offset;uint64_t data_len = all_data_len - offset;if (data_len == 0){break;}ProtoPackage protoPackage;if (!protoPackage.ParseFromArray(tmp_buffer, data_len)){// std::cout << "protoPackage.ParseFromArray failed" << std::endl;break;}if (0 != process_protocol(m_stream_connection, protoPackage)){// std::cout << "process_protocol failed" << std::endl;m_stream_connection.mark_close();m_stream_connection.m_recv_buffer.clear();break;}// std::cout << "datalen " << data_len << " package size " << protoPackage.ByteSizeLong() << std::endl;offset += protoPackage.ByteSizeLong();} while (true);if (!m_stream_connection.m_recv_buffer.read_ptr_move_n(offset)){m_stream_connection.mark_close();}delete[] all_data_buffer;
}void stream_app::on_close_connection(tubekit::connection::stream_connection &m_stream_connection)
{using tubekit::app::global_player;using tubekit::app::global_player_mutex;global_player_mutex.lock();global_player.erase(m_stream_connection.get_socket_ptr());LOG_ERROR("player online %d", global_player.size());global_player_mutex.unlock();
}void stream_app::on_new_connection(tubekit::connection::stream_connection &m_stream_connection)
{using tubekit::app::global_player;using tubekit::app::global_player_mutex;global_player_mutex.lock();global_player.insert(m_stream_connection.get_socket_ptr());LOG_ERROR("player online %d", global_player.size());global_player_mutex.unlock();
}bool stream_app::new_client_connection(const std::string &ip, int port)
{socket::socket *socket_object = singleton<socket_handler>::instance()->alloc_socket();if (!socket_object){LOG_ERROR("alloc_socket return nullptr");return false;}bool b_ret = socket_object->connect(ip, port);if (!b_ret){LOG_ERROR("connection remote %s:%d failed", ip.c_str(), port);singleton<socket_handler>::instance()->remove(socket_object);return false;}int i_ret = singleton<socket_handler>::instance()->attach(socket_object);if (0 != i_ret){LOG_ERROR("attach to socket_handler error ret %d", i_ret);singleton<socket_handler>::instance()->remove(socket_object);return false;}// maybe to do some management for client socket...return true;
}
websocket
#include "app/websocket_app.h"
#include <vector>
#include <tubekit-log/logger.h>
#include "utility/singleton.h"
#include "connection/connection_mgr.h"
#include <arpa/inet.h>using namespace tubekit::app;
using namespace tubekit::utility;
using namespace tubekit::connection;struct websocket_frame
{uint8_t fin;uint8_t opcode;uint8_t mask;uint64_t payload_length;std::vector<uint8_t> masking_key;std::string payload_data;
};enum class websocket_frame_type
{CONNECTION_CLOSE_FRAME = 0,TEXT_FRAME = 1,BINARY_FRAME = 2,PONG = 3,PING = 4,CONTINUATION_FRAME = 5,ERROR = 6
};void websocket_app::process_connection(tubekit::connection::websocket_connection &m_websocket_connection)
{LOG_ERROR("process_connection");uint64_t all_data_len = m_websocket_connection.m_recv_buffer.can_readable_size();if (all_data_len <= 0){LOG_ERROR("all_data_len <= 0");return;}char *data = new (std::nothrow) char[all_data_len];if (!data){return;}all_data_len = m_websocket_connection.m_recv_buffer.copy_all(data, all_data_len);size_t index = 0;while (true){if (index >= all_data_len){break;}size_t start_index = index;websocket_frame frame;websocket_frame_type type = websocket_frame_type::ERROR;switch ((uint8_t)data[index]){case 0x81:{type = websocket_frame_type::TEXT_FRAME;break;}case 0x82:{type = websocket_frame_type::BINARY_FRAME;break;}case 0x88:{type = websocket_frame_type::CONNECTION_CLOSE_FRAME;break;}case 0x89:{type = websocket_frame_type::PING;break;}default:{if (data[index] >= 0x00 && data[index] <= 0x7F){type = websocket_frame_type::CONTINUATION_FRAME;}break;}}if (type != websocket_frame_type::TEXT_FRAME && type != websocket_frame_type::BINARY_FRAME){m_websocket_connection.mark_close();break;}frame.fin = (data[index] & 0x80) != 0;frame.opcode = data[index] & 0x0F;index++;if (index >= all_data_len){LOG_ERROR("index[%llu] >= all_data_len[%llu]", index, all_data_len);break;}frame.mask = (data[index] & 0x80) != 0;frame.payload_length = data[index] & 0x7F;index++;if (index >= all_data_len){LOG_ERROR("index[%llu] >= all_data_len[%llu]", index, all_data_len);break;}if (frame.payload_length == 126){frame.payload_length = 0;if (index + 2 >= all_data_len){LOG_ERROR("index[%llu] >= all_data_len[%llu]", index + 2, all_data_len);break;}uint16_t tmp = 0;u_char *ph;ph = (u_char *)&tmp;*ph++ = data[index];*ph++ = data[index + 1];tmp = ntohs(tmp);frame.payload_length = tmp;index += 2;}else if (frame.payload_length == 127){frame.payload_length = 0;if (index + 8 >= all_data_len){LOG_ERROR("index[%llu] >= all_data_len[%llu]", index + 8, all_data_len);break;}uint32_t tmp = 0;u_char *ph = (u_char *)&tmp;*ph++ = data[index++];*ph++ = data[index++];*ph++ = data[index++];*ph++ = data[index++];frame.payload_length = ntohl(tmp);frame.payload_length = frame.payload_length << 32;ph = (u_char *)&tmp;*ph++ = data[index++];*ph++ = data[index++];*ph++ = data[index++];*ph++ = data[index++];tmp = ntohl(tmp);frame.payload_length = frame.payload_length | tmp;}if (frame.payload_length == 0){break;}if (frame.mask){if (index + 4 >= all_data_len){LOG_ERROR("index[%llu] >= all_data_len[%llu]", index + 3, all_data_len);break;}frame.masking_key = {(uint8_t)data[index], (uint8_t)data[index + 1], (uint8_t)data[index + 2], (uint8_t)data[index + 3]};index += 4;}// payload data [data+index,data+index+frame.payload_length]if (index >= all_data_len){LOG_ERROR("index[%llu] >= all_data_len[%llu]", index, all_data_len);break;}if (index - 1 + frame.payload_length >= all_data_len){LOG_ERROR("index - 1 + frame.payload_length=[%llu] >= all_data_len[%llu]", index - 1 + frame.payload_length, all_data_len);break;}std::string payload_data(data + index, frame.payload_length);if (frame.mask){for (size_t i = 0; i < payload_data.size(); ++i){payload_data[i] ^= frame.masking_key[i % 4];}}frame.payload_data = std::move(payload_data);// broadcastsingleton<connection_mgr>::instance()->for_each([&frame](connection::connection &conn) -> void{websocket_connection *ptr_conn = static_cast<websocket_connection *>(&conn);websocket_app::send_packet(*ptr_conn, frame.payload_data.c_str(), frame.payload_length, false);});// websocket_app::send_packet(m_websocket_connection, frame.payload_data.c_str(), frame.payload_length, false);// frame.payload_data.push_back(0);// LOG_ERROR("%s", frame.payload_data.c_str());m_websocket_connection.m_recv_buffer.read_ptr_move_n(index - start_index + frame.payload_length);index += frame.payload_length;}delete[] data;
}void websocket_app::on_close_connection(tubekit::connection::websocket_connection &m_websocket_connection)
{LOG_ERROR("on_close_connection");
}void websocket_app::on_new_connection(tubekit::connection::websocket_connection &m_websocket_connection)
{LOG_ERROR("on_new_connection");
}bool websocket_app::send_packet(tubekit::connection::websocket_connection &m_websocket_connection, const char *data, size_t data_len, bool use_safe)
{if (!data){return false;}uint8_t opcode = 0x81;size_t message_length = data_len;std::vector<uint8_t> frame;frame.push_back(opcode);if (message_length <= 125){frame.push_back(static_cast<uint8_t>(message_length));}else if (message_length <= 0xFFFF){frame.push_back(126);frame.push_back((message_length >> 8) & 0xFF);frame.push_back(message_length & 0xFF);}else{frame.push_back(127);for (int i = 7; i >= 0; --i){frame.push_back((message_length >> (8 * i)) & 0xFF);}}frame.insert(frame.end(), data, data + data_len);if (!use_safe){return m_websocket_connection.send((const char *)frame.data(), frame.size());}return singleton<connection_mgr>::instance()->safe_send(m_websocket_connection.get_socket_ptr(), (const char *)frame.data(), frame.size());
}
相关文章:
C++服务器 支持http、tcp protobuf、websocket,linux开源框架 零依赖轻松编译部署 Reactor
开源地址: https://github.com/crust-hub/tubekit/tree/main Github:https://github.com/gaowanlu 诚招有兴趣的小伙伴加入开发维护 Tubekit The C TCP server framework based on the Reactor model continues to implement POSIX thread pool, Epoll, non blocking IO, obj…...

1688API接口系列,1688开放平台接口使用方案(商品详情数据+搜索商品列表+商家订单类)
1688商品详情接口是指1688平台提供的API接口,用于获取商品详情信息。通过该接口,您可以获取到商品的详细信息,包括商品标题、价格、库存、描述、图片等。 要使用1688商品详情接口,您需要先申请1688的API权限,并获取ac…...

CentOS服务器网页版Rstudio-server及R包批量安装最佳实践
CentOS服务器安装网页版Rstudio-server及R包批量安装 以下为CentOS 7/8的Rstudio-server安装、配置和R包安装操作 1. 软件包安装 Centos 7安装 # 下载安装包,大小115.14 MB wget -c https://download2.rstudio.org/server/centos7/x86_64/rstudio-server-rhel-…...

centos7内核升级(k8s基础篇)
1.查看系统内核版本信息 uname -r 2.升级内核 2.1更新yum源仓库 yum -y update更新完成后,启用 ELRepo 仓库并安装ELRepo仓库的yum源 ELRepo 仓库是基于社区的用于企业级 Linux 仓库,提供对 RedHat Enterprise (RHEL) 和 其他基于 RHEL的 Linux 发行…...
数据结构与算法设计分析——NP完全理论
目录 一、P类问题与NP类问题的定义二、常见的NP类问题(一)旅行商问题(TSP)(二)哈密尔顿回路问题(三)判断回路问题(四)图的着色问题(五)…...

AGNES层次聚类
已知数据集D中有9个数据点,分别是(1,2),(2,3),(2,1), (3,1),(2,4),(3,5),(4,3),(1,5),(4,2)。要求: (1)采用层次聚类的聚集算法进行聚类,k2。 (2)距离计算采用欧几里得距离。 (3)簇之间的距离采用单链接方…...

HCIP —— 双点重发布 + 路由策略 实验
目录 实验拓扑: 实验要求: 实验配置: 1.配置IP地址 2.配置动态路由协议 —— RIP 、 OSPF R1 RIP R4 OSPF R2 配置RIP、OSPF 双向重发布 R3配置RIP、OSPF 双向重发布 3.查询路由表学习情况 4.使用路由策略控制选路 R2 R3 5.检…...

Python标准库:datetime模块【侯小啾python领航班系列(二十五)】
Python标准库:datetime模块【侯小啾python领航班系列(二十五)】 大家好,我是博主侯小啾, 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ…...

新版idea如何开启多台JVM虚拟机
1.看看自己的项目 2.可能开始的时候啥也没有,就点Run Configuration Type 3.再点击Edit Configurations... 4.点击号添加SpringBoot 5.主类选择一下,一般就一个,点他选了就行。 6.然后点击Modify Options 选择添加add VM Options 7.点击appl…...

软件工程单选多选补充
2. 4. 5. 6. 7. 8. 9. 10. 12。 13....

6-66.时间
本题要求输入小时、分钟和秒数,并将其输出。针对时间表示中出现的异常进行处理。例如小时数不应超过23,分钟不应超过59,秒数不应超过59。此外,以上三个变量均应大于等于0。 输入样例: 在这里给出三组输入。例如&…...

面试多线程八股文十问十答第一期
面试多线程八股文十问十答第一期 作者:程序员小白条,个人博客 相信看了本文后,对你的面试是有一定帮助的! ⭐点赞⭐收藏⭐不迷路!⭐ 1.ThreadLocal如何实现线程安全 Java的ThreadLocal是一个线程本地变量࿰…...

Mybatis 操作续集(结合上文)
当我们增加一个数据之后,如果我们想要获取它的 Id 进行别的操作,我们该如何获取 Id 呢? 用那个Options package com.example.mybatisdemo.mapper;import com.example.mybatisdemo.model.UserInfo; import org.apache.ibatis.annotations.*;import java.util.List;Mapper pub…...

JVM基础篇:垃圾回收
目录 1.前言 1.1C/C的内存管理 1.2Java的内存管理 2.方法区的回收 3.堆回收 3.1引用计数法和可达性分析法 3.2五种对象引用 强引用 软引用 弱引用 虚引用 终结器引用 3.3垃圾回收算法评价标准 ①吞吐量 ②最大暂停时间 ③堆使用效率 3.4垃圾回收算法 ①标记清…...
蓝桥杯ACwing习题
题目 :https://www.acwing.com/problem/content/4409/ 解析 :根据题目我们可以知道 问的是方案数 那么首先就想到了 dp 仔细想一下 发现类似于蒙德里安的梦想那道状态压缩的题 , 所以我们先考虑怎么定义 f[i][j] f[i][j] 表示的是 已经放了…...

vue发送请求携带token,拼接url地址下载文件
封装请求 ,该请求为普通的get请求 该请求返回值为: 请求成功之后拼接URL地址下载文件 代码块 downTemplateRequest(activeKeys.value).then((res) > {let url http://47.169.168.99:18888/media/${res.data.name};var elink document.createElemen…...
【PTA-C语言】编程练习3 - 循环结构Ⅱ
如果代码存在问题,麻烦大家指正 ~ ~有帮助麻烦点个赞 ~ ~ 编程练习3 - 循环结构(9~15) 7-9 特殊a串数列求和(分数 15)7-10 穷举法搬运砖块问题(分数 15)7-11 数字金字塔(分数 15&…...

Google Chrome 下载 (离线版)
1 访问网址 Google Chrome 网络浏览器 2 点击 下载Chrome 3 直接运行 ChromeStandaloneSetup64.exe 其他: ####################### 谷歌浏览器 (Google Chrome) 最新版离线安装包下载 https://www.iplaysoft.com/tools/chrome/#google_vignette Google Chrome …...

2023年GopherChina大会-核心PPT资料下载
一、峰会简介 自 Go 语言诞生以来,中国便是其应用最早和最广的国家之一,根据 Jetbrains 在 2021 年初做的调查报告,总体来说目前大概有 110 万专业的开发者 选择 Go 作为其主要开发语言。就其全球分布而言, 居住在亚洲的开发者最多ÿ…...

从源代码出发,Jenkins 任务排队时间过长问题的解决过程
最近开发了一个部署相关的工具,使用 Jenkins 来构建应用。Jenkins 的任务从模板中创建而来。每次部署时,通过 Jenkins API 来触发构建任务。在线上运行时发现,通过 API 触发的 Jenkins 任务总是会时不时在队列中等待较长的时间。某些情况下的…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...