SRS流媒体服务器(5)源码分析之RTMP握手
1.概述
学习 RTMP 握手逻辑前,需明确两个核心问题:
- rtmp协议连接流程阶段
- rtmp简单握手和复杂握手区别
具体可以学习往期博客:
RTMP协议分析_rtmp与264的关系-CSDN博客
2.rtmp握手源码分析
2.1 握手入口
根据SRS流媒体服务器(4)可知,服务启动SrsServer → 初始化 SrsBufferListener → 每个 SrsBufferListener 管理一个 SrsTcpListener → SrsTcpListener 通过协程循环接受新连接 → on_tcp_client 回调到上层SrsServer
→ SrsServer::accept_client 接收新 TCP 连接 → 创建SrsRtmpConn
连接对象→SrsRtmpConn::do_cycle()协程驱动cycle()
主循环→完成握手、应用连接、媒体流传输→连接断开清理。
srs_error_t SrsRtmpConn::do_cycle()
{srs_error_t err = srs_success;// 打印RTMP客户端的IP地址和端口srs_trace("RTMP client ip=%s:%d, fd=%d", ip.c_str(), port, srs_netfd_fileno(stfd));// 设置RTMP的接收和发送超时时间rtmp->set_recv_timeout(SRS_CONSTS_RTMP_TIMEOUT);rtmp->set_send_timeout(SRS_CONSTS_RTMP_TIMEOUT);// 执行RTMP握手if ((err = rtmp->handshake()) != srs_success) {return srs_error_wrap(err, "rtmp handshake");}// 获取RTMP代理的真实客户端IP地址uint32_t rip = rtmp->proxy_real_ip();// 获取请求信息SrsRequest* req = info->req;if ((err = rtmp->connect_app(req)) != srs_success) {return srs_error_wrap(err, "rtmp connect tcUrl");}// 执行服务循环if ((err = service_cycle()) != srs_success) {err = srs_error_wrap(err, "service cycle");}srs_error_t r0 = srs_success;if ((r0 = on_disconnect()) != srs_success) {err = srs_error_wrap(err, "on disconnect %s", srs_error_desc(r0).c_str());srs_freep(r0);}// 如果客户端被重定向到其他服务器,则已经记录了该事件// If client is redirect to other servers, we already logged the event.if (srs_error_code(err) == ERROR_CONTROL_REDIRECT) {srs_error_reset(err);}return err;
}
2.2 简单和复杂握手
主要是优先尝试复杂握手,随后解析客户端发来的C0C1(并解析是否是代理,Schema1模式等)并返回S0S1S2给客户端,最后再接收C2。
Schema0是一种特殊的握手验证方式,主要为了兼容Adobe Flash Player。在 Schema0 中,Digest 固定位于 C1/S1 的第 8-71 字节(共 64 字节),剩余的 1464 字节为随机数据。这种固定位置的设计简化了验证逻辑,但安全性较低。
Schema1是更安全的握手验证方式,主要用于现代客户端(如 OBS、FFmpeg)Schema1 中,Digest 的位置由 C1 的前 4 字节(时间戳)计算得出,这种方式使得 Digest 位置不固定,提高了安全性。公式为:
digest_offset = (timestamp[0] + timestamp[1] + timestamp[2] + timestamp[3]) % 728 + 12
2.2.1 复杂握手代码示例
SrsRtmpServer::handshake() 复杂握手或简单握手
SrsComplexHandshake::handshake_with_client 读取客户端发送的c0c1数据,解析c1,
生成并发送s0s1s2数据,然后接收客户端发送的c2数据。
c1s1::parse(char* _c1s1, int size, srs_schema_type schema) 根据握手消息的schema类型,解析c1s1握手消息
c1s1_strategy_schema1::parse(char* _c1s1, int size) Schema1解密
/*** @brief 与客户端进行 RTMP 握手** 此函数用于与 RTMP 客户端进行握手,以建立连接。首先尝试复杂握手,如果失败且错误码为 ERROR_RTMP_TRY_SIMPLE_HS,则尝试简单握手。** @return srs_error_t 握手结果,成功返回 srs_success,失败返回错误码并附加中文注释。*/
srs_error_t SrsRtmpServer::handshake()
{srs_error_t err = srs_success;srs_assert(hs_bytes); SrsComplexHandshake complex_hs;// 尝试与客户端进行复杂握手,如果握手失败 则尝试简单握手 //SrsRtmpConn(xxx) -> skt = new SrsTcpConnection(c); -> io = skt;if ((err = complex_hs.handshake_with_client(hs_bytes, io)) != srs_success) {if (srs_error_code(err) == ERROR_RTMP_TRY_SIMPLE_HS) {srs_freep(err); SrsSimpleHandshake simple_hs;if ((err = simple_hs.handshake_with_client(hs_bytes, io)) != srs_success) {// 如果简单握手失败,返回错误并添加中文注释return srs_error_wrap(err, "simple handshake");}} else {// 如果复杂握手失败且错误码不是 ERROR_RTMP_TRY_SIMPLE_HS,返回错误并添加中文注释return srs_error_wrap(err, "complex handshake");}}hs_bytes->dispose(); // 释放 hs_bytes 占用的资源return err; // 返回错误码
}/*** @brief 与客户端进行复杂握手** 该函数用于与客户端进行复杂握手协议。握手过程包括读取客户端发送的c0c1数据,解析c1,* 生成并发送s0s1s2数据,然后接收客户端发送的c2数据。** @param hs_bytes 存储握手字节数据的对象指针* @param io 读写接口指针** @return 错误码,成功时返回srs_success*/
srs_error_t SrsComplexHandshake::handshake_with_client(SrsHandshakeBytes* hs_bytes, ISrsProtocolReadWriter* io)
{srs_error_t err = srs_success;ssize_t nsize;// 读取客户端发送的c0c1数据if ((err = hs_bytes->read_c0c1(io)) != srs_success) {return srs_error_wrap(err, "read c0c1");}// decode c1c1s1 c1;// 尝试使用schema0进行解析// @remark, 使用schema0是为了让Flash播放器满意if ((err = c1.parse(hs_bytes->c0c1 + 1, 1536, srs_schema0)) != srs_success) {return srs_error_wrap(err, "parse c1, schema=%d", srs_schema0);}// 尝试使用schema1进行解析if ((err = c1.c1_validate_digest(is_valid)) != srs_success || !is_valid) {}// encode s1c1s1 s1;if ((err = s1.s1_create(&c1)) != srs_success) {return srs_error_wrap(err, "create s1 from c1");}// 验证s1if ((err = s1.s1_validate_digest(is_valid)) != srs_success || !is_valid) {srs_freep(err);return srs_error_new(ERROR_RTMP_TRY_SIMPLE_HS, "verify s1 failed, try simple handshake");}c2s2 s2;if ((err = s2.s2_create(&c1)) != srs_success) {return srs_error_wrap(err, "create s2 from c1");}// 验证s2if ((err = s2.s2_validate(&c1, is_valid)) != srs_success || !is_valid) {srs_freep(err);return srs_error_new(ERROR_RTMP_TRY_SIMPLE_HS, "verify s2 failed, try simple handshake");}// 发送s0s1s2数据if ((err = hs_bytes->create_s0s1s2()) != srs_success) {return srs_error_wrap(err, "create s0s1s2");}if ((err = s1.dump(hs_bytes->s0s1s2 + 1, 1536)) != srs_success) {return srs_error_wrap(err, "dump s1");}if ((err = s2.dump(hs_bytes->s0s1s2 + 1537, 1536)) != srs_success) {return srs_error_wrap(err, "dump s2");}if ((err = io->write(hs_bytes->s0s1s2, 3073, &nsize)) != srs_success) {return srs_error_wrap(err, "write s0s1s2");}// 接收客户端发送的c2数据if ((err = hs_bytes->read_c2(io)) != srs_success) {return srs_error_wrap(err, "read c2");}c2s2 c2;if ((err = c2.parse(hs_bytes->c2, 1536)) != srs_success) {return srs_error_wrap(err, "parse c2");}// verify c2// 不验证c2,因为ffmpeg会失败// Flash播放器可以正常工作srs_trace("complex handshake success");return err;
}
/*** @brief 读取RTMP握手过程中的C0C1包* * 该函数负责从给定的协议读取器中读取C0C1包数据,并进行rtmp代理处理。*/
srs_error_t SrsHandshakeBytes::read_c0c1(ISrsProtocolReader* io)
{c0c1 = new char[1537];if ((err = io->read_fully(c0c1, 1537, &nsize)) != srs_success) {return srs_error_wrap(err, "read c0c1");}// Whether RTMP proxy, @see https://github.com/ossrs/go-oryx/wiki/RtmpProxy//如果是一个通过 RTMP 代理传输的数据包。if (uint8_t(c0c1[0]) == 0xF3) {//表示代理数据头部之后额外数据的长度。uint16_t nn = uint16_t(c0c1[1])<<8 | uint16_t(c0c1[2]);ssize_t nn_consumed = 3 + nn;// 4B client real IP.if (nn >= 4) {//提取出客户端的真实 IP 地址。proxy_real_ip = uint32_t(c0c1[3])<<24 | uint32_t(c0c1[4])<<16 | uint32_t(c0c1[5])<<8 | uint32_t(c0c1[6]);nn -= 4;}// 移除代理头部,确保后续处理时只考虑原始的 RTMP 数据。memmove(c0c1, c0c1 + nn_consumed, 1537 - nn_consumed);//从 io 中读取被移除部分的数据,填补到 c0c1 缓冲区的末尾,确保总长度仍为 1537 字节。if ((err = io->read_fully(c0c1 + 1537 - nn_consumed, nn_consumed, &nsize)) != srs_success) {return srs_error_wrap(err, "read c0c1");}}return err;
}
/*** @brief 解析c1s1握手消息** 该函数用于解析c1s1握手消息,并根据指定的schema类型选择相应的解析策略。** @param _c1s1 指向握手消息的指针* @param size 握手消息的大小,应为1536字节* @param schema 握手消息的schema类型,应为srs_schema0或srs_schema1** @return 如果解析成功,返回srs_success;否则返回相应的错误码和错误信息*/
srs_error_t c1s1::parse(char* _c1s1, int size, srs_schema_type schema)
{srs_assert(size == 1536);// 检查schema类型是否有效if (schema != srs_schema0 && schema != srs_schema1) {return srs_error_new(ERROR_RTMP_CH_SCHEMA, "parse c1 failed. invalid schema=%d", schema);}// 创建SrsBuffer对象,用于读取数据SrsBuffer stream(_c1s1, size);// 读取时间戳time = stream.read_4bytes();// 读取版本号version = stream.read_4bytes(); // client c1 version// 释放旧的payload指针srs_freep(payload);// 根据schema类型选择不同的解析策略if (schema == srs_schema0) {//schema0 是一种特定的解析方式,它针对旧版 Flash 播放器的特性进行了优化。payload = new c1s1_strategy_schema0();} else {//Schema1是更安全的握手验证方式,主要用于现代客户端(如 OBS、FFmpeg)payload = new c1s1_strategy_schema1();}// 复杂握手解析明文和密文 传入原始数据和解析后的数据大小return payload->parse(_c1s1, size);
}
/*** @brief 解析c1s1策略模式schema1** 该函数用于解析c1s1策略模式schema1的数据结构。** @param _c1s1 输入的c1s1数据指针* @param size 输入数据的大小,必须为1536字节** @return srs_error_t 类型的错误码。成功时返回 srs_success,失败时返回相应的错误码。*/
srs_error_t c1s1_strategy_schema1::parse(char* _c1s1, int size)
{srs_error_t err = srs_success;srs_assert(size == 1536);if (true) {SrsBuffer stream(_c1s1 + 8, 764);//密文if ((err = digest.parse(&stream)) != srs_success) {return srs_error_wrap(err, "parse c1 digest");}}if (true) {SrsBuffer stream(_c1s1 + 8 + 764, 764);//明文if ((err = key.parse(&stream)) != srs_success) {return srs_error_wrap(err, "parse c1 key");}}return err;
}
2.2.2 简单握手代码示例
简单握手中C1和S1从第9个字节开始都是随机数。S2是C1的复制。C2是S1的复制。S0是空包,S012回复包组成是参考C1和S2独立数据包。
/*** @brief 与客户端进行简单握手** 该函数用于与RTMP客户端进行简单握手。** @param hs_bytes 握手字节数据* @param io 读写接口** @return 返回握手结果的状态码,如果成功则返回srs_success,否则返回相应的错误状态码。*/
srs_error_t SrsSimpleHandshake::handshake_with_client(SrsHandshakeBytes* hs_bytes, ISrsProtocolReadWriter* io)
{srs_error_t err = srs_success;ssize_t nsize;// 读取客户端的C0C1if ((err = hs_bytes->read_c0c1(io)) != srs_success) {return srs_error_wrap(err, "read c0c1");}// 检查版本号,if (hs_bytes->c0c1[0] != 0x03) {return srs_error_new(ERROR_RTMP_PLAIN_REQUIRED, "only support rtmp plain text, version=%X", (uint8_t)hs_bytes->c0c1[0]);}// 创建S0S1S2if ((err = hs_bytes->create_s0s1s2(hs_bytes->c0c1 + 1)) != srs_success) {return srs_error_wrap(err, "create s0s1s2");}// 向客户端发送S0S1S2if ((err = io->write(hs_bytes->s0s1s2, 3073, &nsize)) != srs_success) {return srs_error_wrap(err, "write s0s1s2");}// 读取客户端的C2if ((err = hs_bytes->read_c2(io)) != srs_success) {return srs_error_wrap(err, "read c2");}// 打印握手成功日志srs_trace("simple handshake success.");return err;
}/*** @brief 创建S0S1S2握手字节** 该函数创建一个长度为3073字节的握手字节数组,并将其赋值给成员变量s0s1s2。** @param c1 用于生成S2部分的输入字符串* @return srs_error_t 成功时返回srs_success,失败时返回相应的错误码*/
srs_error_t SrsHandshakeBytes::create_s0s1s2(const char* c1)
{srs_error_t err = srs_success;// 如果s0s1s2已经存在,则直接返回成功if (s0s1s2) {return err;}// 为s0s1s2分配内存s0s1s2 = new char[3073];srs_random_generate(s0s1s2, 3073);// 创建一个缓冲区,用于写入s0s1s2的前9个字节// plain text required.SrsBuffer stream(s0s1s2, 9);// 向缓冲区写入第一个字节stream.write_1bytes(0x03);// 向缓冲区写入当前时间戳(4个字节)stream.write_4bytes((int32_t)::time(NULL));// 如果c0c1存在,则将c0c1的后4个字节写入缓冲区// s1 time2 copy from c1if (c0c1) {stream.write_bytes(c0c1 + 1, 4);}// 如果c1存在,则将c1复制到s0s1s2的1537到3072字节位置// if c1 specified, copy c1 to s2.// @see: https://github.com/ossrs/srs/issues/46if (c1) {memcpy(s0s1s2 + 1537, c1, 1536);}return err;
}
学习资料分享
0voice · GitHub
相关文章:
SRS流媒体服务器(5)源码分析之RTMP握手
1.概述 学习 RTMP 握手逻辑前,需明确两个核心问题: rtmp协议连接流程阶段rtmp简单握手和复杂握手区别 具体可以学习往期博客: RTMP协议分析_rtmp与264的关系-CSDN博客 2.rtmp握手源码分析 2.1 握手入口 根据SRS流媒体服务器(4)可知&am…...
线程池(ThreadPoolExecutor)实现原理和源码细节是Java高并发面试和实战开发的重点
一、线程池核心流程图 ----------------- | 提交任务 | submit/execute -----------------|v ----------------- | 判断核心线程数 | < corePoolSize? -----------------|Yes |Nov v [创建新线程] -----------------| 队列是否满&a…...
C# DataGridView 选中所有复选框
问题描述 在程序中尝试选中所有复选框,但出现错误。如果单击顶部的完整选中/释放复选框,同时选中包含复选框的列,则选定区域不会改变。该如何解决? 上面的图片是点击完整版本之后的。 下面是本文的测试代码,函数 dat…...
linux 服务器安装jira-8.22.0和confluence-8.5.21
前提: 下载资源包 z_atlassian-agent-v1.3.1.zip z_atlassian-confluence-8.5.21-x64.zip z_atlassian-jira-software-8.22.0-x64.zip z_jdk-8u131-linux-x64.tar.gz z_postgresql-12.0.tar.gz 可通过作者本身资源库下载 一:服务器构建文件夹 mkdir /z …...
【计算机网络】HTTP/1.0,HTTP/1.1,HTTP/2,HTTP/3汇总讲解,清晰表格整理面试重点对比
表格汇总 对比维度HTTP/1.0HTTP/1.1HTTP/2HTTP/3传输协议TCPTCPTCP/TLS(默认加密)UDP(基于 QUIC 协议)连接方式短连接(每次请求/响应后断开)引入持久连接(Persistent Connection)&a…...
Go语言之路————并发
Go语言之路————并发 前言协程管道SelectsyncWaitGroup锁 前言 我是一名多年Java开发人员,因为工作需要现在要学习go语言,Go语言之路是一个系列,记录着我从0开始接触Go,到后面能正常完成工作上的业务开发的过程,如…...
python的家教课程管理系统
目录 技术栈介绍具体实现截图系统设计研究方法:设计步骤设计流程核心代码部分展示研究方法详细视频演示试验方案论文大纲源码获取/详细视频演示 技术栈介绍 Django-SpringBoot-php-Node.js-flask 本课题的研究方法和研究步骤基本合理,难度适中…...
0x08.Redis 支持事务吗?如何实现?
回答重点 Redis 支持事务,但它的事务与 MySQL 等关系型数据库的事务有着本质区别。MySQL 中的事务严格遵循 ACID 特性,而 Redis 中的事务主要保证的是命令执行的原子性和隔离性,即所有命令在一个不可分割的操作中顺序执行,不会被其他客户端的命令请求所打断。 最关键的区…...
互联网应用的安全防线-身份证实名认证api-身份证三要素验证
随着联网技术的普及,互联网应用已深度渗透人们的生活,从购物下单到社交互动,从金融理财到在线教育,每一次的联网互动都隐藏着一个关乎安全与信任的“隐形卫士”-身份证实名认证接口功能。它如同数字世界的“电子身份证”ÿ…...
本地跑通vue-element-admin项目
GitHub - PanJiaChen/vue-element-admin: :tada: A magical vue admin https://panjiachen.github.io/vue-element-admin 通过加速clone到本地 git clone https://gitclone.com/github.com/PanJiaChen/vue-element-admin.git # 进入项目目录 cd vue-element-admin # 安装依赖…...
el-table表格列宽度自适应
需求:表格错误描述列 要求按照内容最大值设置宽度;如果没有值 则设置最小宽度 <el-table-columnv-else-if"item.prop errorDescription":key"item.code":width"flexColumnWidth(errorDescription, tableConfigA.tableDataA…...
Mysql存储过程(附案例)
文章目录 存储过程概述1、基本语法2、变量①、系统变量②、用户自定义变量③、局部变量 3、流程控制语句①、if语句②、参数③、case语句④、while语句⑤、repeat语句⑥、loop语句⑦、cursor游标⑧、handler 4、存储函数 存储过程概述 存储过程是事先经过编译并存储在数据…...
宇树科技申请 “机器人牌照” 商标,剑指机器人领域新高度
近日,据天眼查信息显示,杭州宇树科技有限公司有了一项重大举动,其申请注册了 “机器人牌照”“机牌”“Robot license”“Robot plate” 等商标,国际分类涉及科学仪器、运输工具、广告销售等多个领域,当前商标状态均为…...
计算机图形学基础--Games101笔记(一)数学基础与光栅化
数学基础 向量 点乘,叉乘和投影: 插值 三角形插值 **重心坐标:**我们通过任意点的重心坐标来插值。 V α V A β V B γ V C V\alpha V_A\beta V_B\gamma V_C VαVAβVBγVC。注意重心坐标没有投影不变性,如果插值三…...
Chrome拓展(Chrome Extension)开发定时任务插件
Chrome扩展定时任务插件开发指南 核心实现原理 使用Chrome Alarms API实现定时触发通过Service Worker保持后台运行本地存储保存任务配置 开发步骤 创建manifest文件 (manifest.json) {"manifest_version": 3,"name": "定时任务助手","…...
100G QSFP28 BIDI光模块一览:100G单纤高速传输方案|易天光通信
目录 前言 一、易天光通信100G QSFP28 BIDI光模块是什么? 二、易天光通信100G QSFP28 BIDI光模块采用的关键技术 三、100G QSFP28 BIDI光模块的优势 四、以“易天光通信100G BIDI 40km ER1光模块”为例 五、总结:高效组网,从“减”开始 关于…...
每日Prompt:迷你 3D 建筑
提示词 3D Q版迷你风格,一个充满奇趣的迷你星巴克咖啡馆,外观就像一个巨大的外带咖啡杯,还有盖子和吸管。建筑共两层,大大的玻璃窗清晰地展示出内部温馨而精致的设计:木质的家具、温暖的灯光以及忙碌的咖啡师们。街道…...
从另一个视角理解TCP握手、挥手与可靠传输
本文将深入探讨 TCP 协议中三次握手、四次挥手的原理,以及其保证可靠传输的机制。 一、三次握手:为何是三次,而非两次? 建立 TCP 连接的过程犹如一场严谨的 “对话”,需要经过三次握手才能确保通信双方的可靠连接。 三…...
SearxNG本地搜索引擎
SearxNG 是一个强大、开源的 元搜索引擎(meta search engine),它不会存储用户信息,注重隐私保护,并支持从多个搜索引擎聚合结果,用户可以自建部署,打造一个无广告、可定制的搜索平台。 🔍 什么是 SearxNG? SearxNG 是 Searx 的一个积极维护的分支(fork),意在改进…...
基于支持向量机(SVM)的P300检测分类
基于支持向量机(SVM)的P300检测分类MATLAB实现,包含数据预处理、特征提取和分类评估流程: %% P300检测分类完整流程(SVM实现) clc; clear; close all;%% 1. 数据加载与模拟生成(实际应用需替换…...
Oracle学习日记--Oracle中使用单个inert语句实现插入多行记录
目录 前言: 问题现象: 问题分析: 解决方法: 1、insert into ... union all句式 2、insert all into ...select 1 from dual句式 总结: 前言: 最近项目中使用到了Oracle数据库,由于Oracle数…...
利用边缘计算和工业计算机实现智能视频分析
在人工智能和物联网取得重大进步的时代,智能视频分析(IVA)正在通过整合先进的人工智能技术来改变视频监控和分析。这项革命性的技术增强了视觉智能,是关键行业创新解决方案的驱动因素。在本文中,我们将介绍IVA的好处、…...
tomcat一闪而过,按任意键继续以及控制台中文乱码问题
问题描述 今天在打开tomcat,启动startup.bat程序时 tomcat直接闪退,后面查找资料后发现,可以通过编辑startup.bat文件内容,在最后一行加入pause即可让程序不会因为异常而终止退出 这样方便查看tomcat所爆出的错误: 然后,我明确看到我的tomcat启动程序显示如下的内容,没有明确…...
go 集成base64Captcha 支持多种验证码
base64Captcha 是一个基于 Go 语言开发的验证码生成库,主要用于在 Web 应用中集成验证码功能,以增强系统的安全性。以下是其主要特点和简介: base64Captcha主要功能 验证码类型丰富:支持生成多种类型的验证码,包括纯…...
【数据结构】线性表--栈
【数据结构】线性表--栈 一.什么是栈二.栈的实现1.栈结构定义:2.栈的初始化:3.栈的销毁:4.插入函数:5.删除函数:6.求栈顶元素:7.求数据个数:8.判空: 三.总结 一.什么是栈 栈是一种重…...
基于亚博K210开发板——麦克风录播测试
开发板 亚博K210开发板 实验目的 本次测试主要学习 K210 通过 I2S 接收和发送的功能,麦克风录音,扬声器播放。 实验准备 实验元件 扬声器、麦克风 元件特性 K210 开发板的麦克风同样是使用 I2S 传输数据,只不过麦克风使用的是 I2S…...
操作系统-锁/内存/中断/IO
文章目录 锁自旋锁互斥锁悲观锁和乐观锁 内存管理物理/虚拟内存页表段表虚拟内存布局写时复制copy on writebrk,mmap页面置换算法 中断中断分类中断流程 网络I/OI/O模型服务器处理并发请求 锁 自旋锁 自旋锁是一种基于忙等待(Busy-Waiting)…...
割点与其例题
割点 定义: 若一个点在图中被去掉后,图的连通块个数增加,那么这个点就被称为“割点”。如下图所示红点。 定义说白了就是若去掉一个点,图被“断开”的点称为割点。 朴素算法: 枚举每个点 u。遍历图,如果…...
CSS实现文本自动平衡text-wrap: balance
不再有排版孤行和寡行 我们都知道那些标题,最后一个单词换行并单独站在新行上,破坏了视觉效果,看起来很奇怪。当然,有老式的 手动换行或将内容分成不同部分。但您听说过text-wrap: balance吗? 通过应用text-wrap: bal…...
【未完】【GNN笔记】EvolveGCN:Evolving Graph Convolutional Networks for Dynamics Graphs
Evolving Graph Convolutional Networks for Dynamics Graphs 视频链接:《图神经网络》 相关系列: 《Dynamic Graph的分类》《动态图网络之Dynamic Self-Attention Network》 文章目录 Evolving Graph Convolutional Networks for Dynamics Graphs一、…...
【愚公系列】《Manus极简入门》042-投资策略分析师:“投资智慧导航”
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! …...
《AI大模型应知应会100篇》第65篇:基于大模型的文档问答系统实现
第65篇:基于大模型的文档问答系统实现 📚 摘要:本文详解如何构建一个基于大语言模型(LLM)的文档问答系统,支持用户上传 PDF 或 Word 文档,并根据其内容进行智能问答。从文档解析、向量化、存储到…...
P1439 【模板】最长公共子序列
P1439 【模板】最长公共子序列 - 洛谷 题目描述 给出1, 2, …, n的两个排列P1和P2,求它们的最长公共子序列。 输入格式 第一行是一个数n。 接下来两行,每行为n个数,为自然数1, 2, …, n的一个排列。 输出格式 一个数,即…...
LLaMA-Factory:环境准备
一、硬件和系统 操作系统: Ubuntu 24.04.2 LTS(64位)GPU: NVIDIA RTX 4090 笔记本 GPU,16GB显存CPU: 建议高性能多核 CPU(如 Intel i7/i9 或 AMD Ryzen 7/9)以支持数据预处理,我的是32核。RAM: 至少 32GB&…...
polarctf-web-[rce1]
考点: (1)RCE(exec函数) (2)空格绕过 (3)执行函数(exec函数) (4)闭合(ping命令闭合) 题目来源:Polarctf-web-[rce1] 解题: 这段代码实现了一个简单的 Ping 测试工具,用户可以通过表单提交一个 IP 地址,服务器会执…...
AI实践用例---日程规划(通用日程管理文件ICS)灵感踩坑日常
我是一位践行独立开发者之路的菜鸟开发者。 由于执行力较差,常常有很多想法但是很多时候没有去践行。 所以我有了让大模型为我生成日程安排的想法,这确实可以,很简单。只需要将你的想法告诉ai就行了。 例如: 发给AI的提示词: 我想你帮我对,嗯,未来的一年做一个嗯,大…...
MySQL 8.0 OCP 1Z0-908 121-130题
Q121.Examine these statements and output: mysql> GRANT PROXY ON accountinglocalhost TO ’ ‘ ‘%’; mysql> SELECT USER(), CURRENT_USER(), proxy_user; --------------------------------------------------------- |USER() | CURRENT_USER() | proxy_user I | …...
InfluxDB 2.7 连续查询实战指南:Task 替代方案详解
InfluxDB 2.7 引入了 Task 功能,作为连续查询(CQ)的现代替代方案。本文详细介绍了如何使用 Task 实现传统 CQ 的功能,包括语法解析、示例代码、参数对比以及典型应用场景。通过实际案例和最佳实践,帮助开发者高效迁移并…...
计算机网络 : Socket编程
计算机网络 : Socket编程 目录 计算机网络 : Socket编程引言1.UDP网络编程1.1 网络地址与端口转换函数1.2 本地环回1.3 EchoServer1.4 DictServer1.5 DictServer封装版1.6 简单聊天室 2.TCP网络编程2.1 TCP Socket API详解2.2 Echo Server2.3 Echo Serve…...
C++动态内存分配
内存管理 1.代码段(Code Segment / Text Segment)常量区2. 数据段(Data Segment)静态区3. BSS 段(未初始化数据段)4. 堆(Heap)5. 栈(Stack)6. 内存映射段&…...
git版本控制学习
1.环境搭配 2.Linux常用指令 cd:更改目录cd..回到上一级目录pow:显示当前所在的目录路径li(||):列出当前目录所有文件,只不过||例出的内容更为详细touch:新建一个文件夹如touch index.js就会在当前目录下新建一个index.js文件rm:删除一个文件,rm index.js 就会把in…...
在MYSQL中导入cookbook.sql文件
参考资料: GitHub 项目:svetasmirnova/mysqlcookbook CSDN 博客:https://blog.csdn.net/u011868279/category_11645577.html 建库: mysql> use mysql Reading table information for completion of table and column names …...
安科瑞AcrelEMS3.0企业微电网智慧能源平台-安科瑞 蒋静
1、平台介绍 Acrel-EMS3.0 智慧能源平台是针对企业微电网的能效管理平台,满足江苏省《新型电力负荷管理系统数据接入规范》的技术架构,可通过云云互联方式接受电力平台的调度指令和策略下发,支持作为微网调度子系统或子平台。 2、解决方案 …...
5G-A和未来6G技术下的操作系统与移动设备变革:云端化与轻量化的发展趋势
目录 5G技术带来的革命性变革 云端化操作系统的实现路径 完全云端化模式 过渡性解决方案 未来操作系统的发展方向 功能架构演进 安全机制强化 移动设备的形态变革 终端设备轻量化 物联网设备简化 实施挑战与应对策略 技术挑战 商业模式创新 总结与展望 5G技术作为…...
TensorFlow/Keras实现知识蒸馏案例
创建一个“教师”模型(一个稍微复杂点的网络)。创建一个“学生”模型(一个更简单的网络)。使用“软标签”(教师模型的输出概率)和“硬标签”(真实标签)来训练学生模型。 import tens…...
能源数字化转型关键引擎:Profinet转Modbus TCP网关驱动设备协同升级
在工业自动化的世界中,ModbusTCP和Profinet是两个非常重要的通讯协议。ModbusTCP以其开放性和易用性,被广泛应用于各种工业设备中;而Profinet则以其高效性和实时性,成为了众多高端设备的首选。然而,由于这两种协议的差…...
大模型的实践应用43-基于Qwen3(32B)+LangChain框架+MCP+RAG+传统算法的旅游行程规划系统
大家好,我是微学AI,今天给大家介绍一下大模型的实践应用43-基于Qwen3(32B)+LangChain框架+MCP+RAG+传统算法的旅游行程规划系统。本报告将阐述基于大模型Qwen3(32B)、LangChain框架、MCP协议、RAG技术以及传统算法构建的智能旅游行程规划系统。该系统通过整合多种技术优势,实…...
【Unity】用事件广播的方式实现游戏暂停,简单且实用!
1.前言 在做Unity项目的时候,要考虑到“游戏暂停”的功能,最直接的办法是修改游戏的Time.TimeScale 0f。但是这种方式的影响也比较大,因为它会导致游戏中很多程序无法正常运行。 于是我就有了一个想法,在游戏中想要暂停的对象&…...
二维数组以及C99中的变长数组(如何在VS2022中使用苹果的clang编译器)
一、二维数组的创建 1.1 二维数组的概念 在上一篇文章中所写的称为一维数组,数组的元素都是内置类型的,如果我们把一维数组作为数组的元素,这时候就是二维数组,二维数组作为数组元素的数组被称为三维数组,二维数组以…...
影楼精修-肤色统一算法解析
注意:本文样例图片为了避免侵权,均使用AIGC生成; 本文介绍影楼精修中肤色统一算法的实现方案,并以像素蛋糕为例,进行分析说明。 肤色统一就是将人像照片中皮肤区域的颜色进行统一,看起来颜色均匀一致&…...