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

传输层 udptcp

内核版本采用4.9.88

套接字分析
  • 系统入口要求:每个操作系统都必须提供网络子系统入口及API,Linux内核网络子系统提供标准的POSIX套接字API接口

  • 用户空间特性:在Linux中传输层之上的一切都属于用户空间,遵循Unix"一切皆为文件"范式

  • 文件关联性:套接字与文件相关联,使用统一API使应用程序移植更容易

1)套接字类型详解
  • SOCK_STREAM流套接字

    • 通信特性:提供可靠的字节流通信信道

    • 典型协议:TCP套接字属于流套接字类型

    • 可靠性表现:保证数据按顺序到达且不重复

  • SOCK_DGRAM数据报套接字

    • 消息交换:支持以数据包为单位的消息交换

    • 不可靠特性:通信信道不可靠,可能出现丢包、乱序或重复

    • 典型协议:UDP套接字属于此类型

    • 风险说明:数据可能被丢弃、不按序到达或重复传输

  • SOCK_RAW原始套接字

    • 访问层级:直接访问IP层,绕过传输层协议

    • 协议无关性:支持使用协议无关的传输层格式收发数据流

    • 特殊用途:常用于网络协议开发和分析

  • SOCK_RDM可靠消息套接字

    • 核心特性:提供可靠传输的消息服务

    • 应用场景:主要用于透明进程间通信(TIPC)

    • 开发背景:最初由爱立信公司开发用于执行应用程序

  • SOCK_SEQPACKET顺序数据包

    • 连接特性:面向连接,类似SOCK_STREAM

    • 边界维护:独特之处在于维护记录边界

    • 识别方式:接收方可通过MSG_EOR标志确定边界

  • SOCK_DCCP数据报拥塞控制

    • 协议定位:传输层协议,兼具TCP和UDP特点

    • 核心功能:提供不可靠数据报的拥塞控制流

    • 设计目标:解决数据报传输中的拥塞控制问题

1)套接字API与内核方法 
  • 内核映射关系:应用层套接字API在内核中对应net/socket.c文件中的系统调用实现

  • 调用机制:通过SYSCALL_DEFINE2宏定义系统调用,如socketcall处理所有套接字相关操作

  • 参数传递:使用unsigned long数组存储用户空间传递的参数,通过copy_from_user安全拷贝

2)socket.c文件与内核函数定义 
  • 文件位置:内核源码中位于net/socket.c目录下

  • 函数前缀:所有套接字系统调用函数都以sys_开头,如sys_socket、sys_bind等

  • 调用流程:通过switch-case结构分发不同的套接字操作请求

  • 需阅读源码部分:

3)套接字类型与功能描述 
  • SOCK_STREAM:提供可靠的字节流通信,TCP套接字属于此类型

  • SOCK_DGRAM:支持消息交换但不可靠,UDP套接字属于此类型

  • SOCK_RAW:直接访问IP层,支持协议无关的传输层数据收发

  • SOCK_RDM:用于透明进程间通信(TIPC)

  • SOCK_SEQPACKET:面向连接的顺序数据包流,类似SOCK_STREAM

  • SOCK_DCCP:数据报拥塞控制协议,提供不可靠数据报拥塞控制流

4)套接字操作函数 
  • socket():创建套接字,类型由参数决定

  • bind():将套接字与本地端口和IP地址关联

  • connect():建立到对等套接字的连接,适用于SOCK_STREAM和SOCK_SEQPACKET类型

  • listen():使套接字能够接收连接请求,不适用于数据报套接字

  • accept():接收套接字连接请求,仅适用于基于连接的套接字类型

  • send()/recv():分别对应消息发送和接收,内核实现为sys_send和sys_recv

  • 实现机制:每个应用层API调用对应内核中的sys_前缀函数

  • 参数处理:通过a0,a1等寄存器传递参数,进行安全检查后调用具体实现

  • 错误处理:返回负值表示错误,如-EINVAL表示无效参数

3. 传输控制协议 
1)内核中的套接字结构 
  • 双结构表示:内核中有两个表示套接字的结构体,分别是socket和sock

  • 源码位置:位于include/linux/net.h文件中,从第104行开始定义

  • 功能分工:

    • socket:面向用户空间提供接口

    • sock:面向网络层(L3)提供接口

2)socket结构的主要成员 
  • 核心成员:

    • state:套接字状态(如SS_CONNECTED等)

    • type:套接字类型(如SOCK_STREAM等)

    • flags:套接字标志(如SOCK_NOSPACE等)

    • ops:协议特定的套接字操作,包含套接字回调函数的proto_ops结构

    • file:文件反向指针用于垃圾回收,关联的文件结构指针

    • sk:内部网络协议无关的套接字表示

    • wq:多种用途的等待队列

    • 源码阅读:

3)sock结构的网络层位置 
  • 协议无关性:sock结构位于网络层,是与协议无关的结构

  • 创建关联:创建套接字时会同时创建关联的sock对象

  • 示例:在IPv4中,调用inet_create方法时会分配sock对象并关联到对应套接字

4)socket与sock的功能区别 
  • socket功能:

    • 面向用户空间提供接口

    • 由sys_socket()方法创建

  • sock功能:

    • 面向网络层(L3)提供接口

    • 协议无关的结构体

    • 包含网络层相关操作和数据

5)socket状态及其取值 
  • 状态枚举:

    • SS_FREE = 0:未分配状态

    • SS_UNCONNECTED:未连接任何套接字

    • SS_CONNECTING:正在连接过程中

    • SS_CONNECTED:已连接到套接字

    • SS_DISCONNECTING:正在断开连接

    • 源码阅读:

  • 典型场景:

    • 刚创建的INET套接字状态为SS_UNCONNECTED

    • 流套接字成功连接后状态变为SS_CONNECTED

6)socket类型及其枚举类型 
  • 类型定义:

    • SOCK_STREAM:流式套接字

    • SOCK_RAW:原始套接字

  • 类型特性:

    • 使用kmemcheck_bitfield_begin/end宏进行类型检查

    • 类型值存储在short类型的变量中

7)socket标志与分配方式 
  • 标志作用:表示套接字的特殊属性和状态

  • 分配方式:

    • 非系统调用socket分配时直接设置核心标志

    • 可通过通用try_open方法进行打开操作

8)socket相关对象与文件 
  • 关联文件:

    • file指针:与套接字关联的文件对象

  • 关联对象:

    • sk指针:与套接字关联的sock对象

    • 提供网络层接口功能

9)socket操作与回调函数 
  • 操作集合:

    • 包含connect、listen、sendmsg、recvmsg等回调函数

    • 实现用户空间接口的多个库级例程

  • 协议特定:

    • 每种协议需要自定义proto_ops结构

    • 不是随意定义的固定结构

  • 典型回调:

    • sendmsg实现send、sendto、sendmsg等功能

    • recvmsg实现read、recv、recvfrom、recvmsg等功能

4. 套接字在网络层中的表示 
1)套接字结构 
  • 核心结构:sock结构体是套接字的网络层表示,位于内核源码的include/net/sock.h文件中

  • 源码阅读位置:建议整个结构体内容都有所了解

  • 重要成员:

    • sk_common:包含套接字的基础公共成员

    • sk_lock:套接字锁

    • sk_drops:数据包丢弃计数器

    • sk_rcvlowat:接收低水位标记

    • sk_error_queue:错误队列头

    • sk_receive_queue:接收队列头

  • 实现特点:

    • 结构体实现代码相当长,包含套接字的所有网络层属性

    • 与socket结构体相关联,通过struct sock *sk指针连接

    • 包含数据包队列存储、接收缓冲区大小、各种标志等关键信息

2)回调函数 
  • 核心成员:

    • sk_receive_queue:存储入站数据包的队列

    • sk_write_queue:存储出站数据包的队列

    • sk_rcvbuf:接收缓冲区大小(单位:字节)

    • sk_sndbuf:发送缓冲区大小(单位:字节)

    • sk_protocol:协议标识符(8位)

    • sk_type:套接字类型(16位)

  • 关键回调:

    • sk_data_ready:通知套接字有新数据到达的回调函数

    • sk_write_space:指出可用内存处理数据传输的回调函数

  • 其他特性:

    • sk_no_check_tx:禁用发送校验和标志

    • sk_no_check_rx:禁用接收校验和标志

4)系统调用socket参数与返回值 
  • 参数说明:

    • socket_family:地址族(AF_INET/AF_INET6)

    • socket_type:套接字类型(SOCK_STREAM/SOCK_DGRAM/SOCK_RAW)

    • protocol:协议类型(IPPROTO_TCP/IPPROTO_UDP)

  • 返回值:

    • 返回文件描述符sockfd,用于后续套接字操作

    • 内核内部调用sys_socket进行处理

5)msghdr结构体 
  • 核心成员:

    • msg_name:目标套接字地址指针,可转换为sockaddr_in结构

    • msg_namelen:地址长度

    • msg_iter:数据块迭代器

    • msg_control:控制信息(辅助数据)指针

    • msg_controllen:控制信息长度

    • msg_flags:接收消息的标志位

  • 源码阅读位置:

  • 使用场景:

    • 用于用户空间套接字发送/接收数据

    • 通过sendmsg/recvmsg系统调用处理数据传输

    • 包含完整的数据块和控制信息

用户数据包协议 
1. UDP报头内核源码
  • msg_name字段:指向目标套接字地址的指针,可转换为指向结构sockaddr_in的指针

  • msg_namelen字段:表示地址长度

  • msg_control字段:用于存储控制信息(辅助数据)

  • msg_flags字段:接收到的消息标志位

  • cmsg_len字段:数据字节计数,包括头部信息

  • cmsg_level字段:标识原始协议

  • cmsg_type字段:协议特定类型

1)UDP数据包协议 
  • source字段:16位源端口号,取值范围1-65535

  • dest字段:16位目的端口号,取值范围1-65535

  • len字段:表示有效负载和UDP报头的总长度,单位为字节

  • check字段:数据包的校验和,用于验证数据完整性

  • 源码阅读:

2)UDP初始化操作 
  • 初始化操作定义对象并使用方法

    • 初始化对象:通过定义udp_protocol(net_protocol对象)实现

    • 添加方法:使用inet_add_protocol()函数进行注册

    • 关键成员:

      • early_demux:设置为udp_v4_early_demux

      • handler:设置为udp_rcv处理函数

      • err_handler:设置为udp_err错误处理函数

      • no_policy:设置为1表示无策略

      • netns_ok:设置为1表示支持网络命名空间

      • 源码阅读:

2. 套接字的网络层表示
  • 数据结构:内核中使用struct sock结构体表示套接字

    • 接收队列:sk_receive_queue存储入站数据包

    • 缓冲区大小:sk_rcvbuf表示接收缓冲区大小(字节),sk_sndbuf表示发送缓冲区大小

    • 发送队列:sk_write_queue存储出站数据包

    • 协议标识:sk_protocol字段标识协议类型

    • 回调函数:sk_data_ready通知新数据到达,sk_write_space指示可用内存

  • 系统调用:

    • socket()参数:

      • socket_family:AF_INET/AF_INET6

      • socket_type:SOCK_STREAM/SOCK_DGRAM/SOCK_RAW

      • protocol:IPPROTO_TCP/IPPROTO_UDP

    • 返回值:返回文件描述符sockfd用于后续操作

    • 数据传输:通过sendmsg()/recvmsg()处理,使用msghdr对象封装数据

3. UDP初始化机制
1)协议注册
  • 核心结构:通过struct net_protocol udp_protocol注册

    • 处理函数:handler=udp_rcv处理接收数据

    • 错误处理:err_handler=udp_err处理错误

    • 特性标志:no_policy=1表示无策略检查,netns_ok=1支持网络命名空间

  • 初始化流程:

    • 注册时机:系统启动时通过inet_init_net()初始化

    • 端口范围:默认本地端口范围32768-60999

    • 统计信息:为CPU分配统计数据结构(ip_statistics等)

2)网络层初始化
  • 关键函数:inet_init_net()执行初始化

    • 端口锁定:使用seqlock_init初始化ip_local_ports锁

    • Ping控制:设置ping_group_range限制ping权限

    • 默认参数:

      • TTL值:IPDEFTTL

      • 动态地址:sysctl_ip_dynaddr=0

      • 早期解复用:对TCP/UDP启用(值=1)

    • 源码阅读:

  • 实现细节:

    • 多协议支持:通过inet_add_protocol()注册ICMP/UDP/TCP

    • 错误处理:失败时输出pr_crit级别日志

    • 回调机制:如udp_sendmsg()处理UDP数据发送

    • 源码阅读:这个挺重要的,略微重点阅读

用户数据包协议内核源码 
1)接收L3的UDP数据包 
  • 方法udp_rcv()介绍

    • 主处理程序:udp_rcv()是Linux内核中负责接收来自网络层(L3)的UDP数据包的核心函数

    • 调用位置:位于net/ipv4/udp.c文件中,函数签名为int udp_rcv(struct sk_buff *skb)

    • 嵌套调用:实际工作通过调用udp4_lib_rcv(skb, &udp_table, IPPROTO_UDP)完成,传入UDP协议表和处理协议类型

  • udp_rcv()处理流程

    • 初始化阶段:

      • 获取socket结构体指针struct sock *sk

      • 解析UDP头部struct udphdr *uh获取报文长度ulen

      • 获取路由表项struct rtable *rt = skb_rtable(skb)

    • 数据包验证:

      • 检查是否有足够空间存放UDP头部:pskb_may_pull(skb, sizeof(struct udphdr))

      • 验证报文长度是否合法:ulen > skb->len时跳转到short_packet处理

      • 初始化校验和:udp4_csum_init(skb, uh, proto)

    • 套接字查找:

      • 使用skb_steal_sock(skb)尝试获取关联的socket

      • 通过__udp4_lib_lookup_skb()在UDP哈希表中查找匹配的套接字

  • 数据包验证与 处理

    • 匹配处理:

      • 找到匹配套接字时:调用udp_queue_rcv_skb(sk, skb)将数据包加入接收队列

      • 未找到匹配时:检查校验和是否正确,错误则直接丢弃数据包

    • 异常处理:

      • 广播/组播数据包:调用__udp4_lib_mcast_deliver()处理

      • 端口不可达情况:发送ICMP"目的不可达"响应,更新SNMP计数器

    • 资源释放:

      • 最终通过kfree_skb()释放SKB缓冲区

      • 更新内存统计信息:atomic_sub(size, &sk->sk_rmem_alloc)

    • 完整流程:

      • 入口函数udp_rcv()接收L3数据包

      • 调用__udp4_lib_rcv()进行核心处理

      • 检查是否为组播数据(是则特殊处理)

      • 在UDP哈希表中查找匹配套接字

      • 找到匹配则入队,否则校验后发送ICMP响应

      • 最终释放数据包资源

TCP分析 
1. TCP报头结构主要成员 
  • 源端口与目的端口:

    • source:均为16位长度,取值范围1-65535

    • dest:源端口对应应用层写的源端口,目的端口标识目标服务

  • 序列号与确认号:

    • seq:序列号(seq)为32位,用于数据包排序

    • ack_seq:确认号(ack_seq)为32位,当设置ACK标志时,表示期望收到的下一个数据包序列号

  • 保留字段:

    • res1:4位长度,必须设置为0,为未来协议扩展保留

  • 数据偏移量:

    • doff:4位长度,以4字节为单位表示TCP头长度

    • 最小值为5(20字节),最大为15(60字节)

  • 控制标志位:

    • FIN(1位):发送方数据发送完毕,用于关闭连接

    • SYN(1位):用于三次握手建立连接

    • RST(1位):收到非当前连接数据时使用

    • PSH(1位):要求尽快将数据交付用户空间

    • ACK(1位):确认号字段有效

    • URG(1位):紧急指针字段有效

    • ECE(1位):显式拥塞通知,提供网络拥塞反馈

    • CWR(1位):拥塞窗口缩小标志

  • 窗口大小:

    • window:16位长度,表示接收窗口大小(字节单位)

  • 校验和:

    • check:包含TCP头和数据的校验值

  • 紧急指针:

    • urg_ptr:16位长度,仅当URG标志设置时有效

    • 表示紧急数据相对于序列号的偏移量

  • 源码阅读:

TCP初始化操作 
  • 核心结构体:

    • struct net_protocol:定义传输层协议

      • 包含ICMP/IGMP协议处理

      • 桥接网络层和传输层的报文接收流程

    • 源码阅读:

  • 关键成员:

    • handler:数据包接收处理函数指针

    • err_handler:错误报文处理函数

    • no_policy:安全策略标志

  • 初始化流程:

1.定义tcp_protocol对象

2.通过inet_add_protocol()注册协议

3.内核根据协议类型(TCP/UDP/ICMP)调用对应处理函数

4.返回值<0表示注册失败

  • 源码位置:

    • 位于net/ipv4/目录下

    • TCP实现文件为tcp_ipv4.c

  • 错误处理:

    • 当inet_add_protocol()返回错误时

    • 内核会输出协议添加失败信息

TCP定时器
1)定时器实现位置
  • 文件路径: 位于Linux内核的net/ipv4/tcptimer.c文件中

  • 查找方法: 通过内核源码目录结构可快速定位,IPv4相关实现都在net/ipv4/目录下

2)重传定时器
  • 核心功能: 负责在指定时间内未得到确认的数据包重传

  • 触发条件:

    • 数据包丢失或损坏

    • 每次数据段发送后自动启动

  • 终止机制: 定时器到期后若仍未收到确认则取消定时器

3)延迟确认定时器
  • 作用原理: 推迟发送确认数据包

  • 适用场景: 当TCP收到需要确认但无需立即确认的数据时启用

4)存活定时器
  • 检测功能: 检查连接是否已断开

  • 应用场景: 连接长时间空闲时检测对方状态

  • 处理机制: 通过tcpsendactivereset()函数重置连接

5)零窗口探测定时器
  • 别名: 持续定时器(persist timer)

  • 工作流程:

    • 接收方缓冲区满时通告零窗口

    • 发送方停止发送数据

    • 若包含新窗口大小的数据段丢失,定时器定期探测窗口状态

  • 终止条件: 当探测到接收方窗口大小不为零时停止

TCP初始化
1)初始化入口
  • 用户空间调用: 创建SOCK_STREAM类型套接字

  • 内核处理函数:

    • 系统调用入口为sys_socket()

    • 实际回调函数为tcp_v4_init_sock()(IPv6对应tcp_v6_init_sock)

2)主要初始化任务
  • 状态设置

    • 初始状态: 设置为TCP_CLOSE状态

  • 定时器初始化

    • 调用函数:tcp_init_xmit_timers()

    • 作用: 初始化所有TCP定时器模块

  • 缓冲区设置

    • 发送缓冲区:sk−>sk_sndbuf默认16,384字节

    • 接收缓冲区:sk−>sk_rcvbuf默认87,380字节

  • 队列初始化

    • 无序队列: 处理非常规数据包

    • 有序队列: 维护正常数据流顺序

  • 参数初始化

    • 包含内容: TCP头部各字段默认值初始化

    • 典型值: 窗口大小初始化为10等基础参数

  • 源码阅读:

TCP的连接和拆除 
1)状态转换机制
  • 监听状态: 调用listen()后进入TCP_LISTEN状态

  • 状态标识: 通过sk−>skstate成员变量表示

2)三次握手过程
  • 第一次握手

    • 客户端动作: 发送SYN请求

    • 状态转换: 进入TCP_SYN_SENT状态

  • 第二次握手

    • 服务端响应:

      • 创建TCP_SYN_RECV状态套接字

      • 返回SYN_ACK应答

  • 第三次握手

    • 客户端确认:

      • 收到SYN_ACK后进入TCP_ESTABLISHED状态

      • 发送最终ACK确认

  • 连接建立

    • 服务端最终状态: 收到ACK后改为TCP_ESTABLISHED状态

    • 数据传输: 连接建立完成后即可开始数据传输

3)数据包接收处理
  • 核心函数:tcp_v4_rcv()

  • 源码阅读:

  • 处理流程:

    • 完整性检查(包类型、长度等)

    • 初始化工作

    • 调用__inet_lookup_skb()查找匹配套接字

  • 异常处理: 检查失败时直接丢弃数据包

初始化相关问题 
1. skb分析 
1)数据包丢弃条件
  • 非本地数据包处理:当数据包不是发送或发往本地的包时,系统会直接丢弃(discard)该数据包

  • 掩码执行位置:丢弃操作通过1612行的掩码进行处理

2)TCP头部验证
  • 长度校验:通过PS x/PS KB脉破函数检查包长是否大于TCP头长度

  • 头部提取:使用th指针取得TCP首部,通过1612行掩码处理

  • 偏移量验证:检查TCP首部长度与偏移量字段(offset)是否匹配

2. 接收TCP数据包过程 
1)数据包查找流程
  • socket查找:通过inet_look_up函数查找匹配的socket,若找不到则丢弃数据包

  • 状态检查:检查socket是否处于半关闭状态(half-closed)

  • 规则验证:依次检查IPsec规则、MD5哈希校验、BPF校验规则

2)用户态处理
  • 进程锁定:检查是否有用户态进程对socket进行锁定

  • 状态保护:当socket被锁定时,其状态不可更改

  • 队列处理:被锁定的数据包会进入后备处理队列,相关进程进入套接字后备等待队列

3. 发送TCP数据包过程 
1)发送初始化
  • 系统调用:通过send/sendto/sendmsg等系统调用触发

  • 核心函数:最终由tcp_sendmsg函数处理,将用户空间数据复制到内核空间

  • 状态检查:验证socket是否处于ESTABLISHED或CLOSE_WAIT状态

2)数据复制流程
  • 内存分配:分配SKB缓冲区对象存储用户数据

  • 循环拷贝:通过while循环控制所有用户数据块到内核空间的拷贝

  • 队列管理:操作发送队列(双向链表)的尾节点进行数据添加

3)错误处理机制
  • do_error处理:当部分数据已复制时,仍发送已复制的数据

  • wait_for处理:

    • snd_buff:发送队列数据达到缓冲区上限时触发

    • memory:系统内存不足时触发

  • 超时处理:内存分配超时跳转到错误处理流程

4)标志位说明
  • MSG_MORE:表示本次发送没有后续数据

  • TCP_NODELAY:禁用Nagle算法立即发送数据

  • SK_MEM_QUERY:内存配额检查标志

总结 
1. 套接字分析
  • 核心概念:套接字(Socket)是网络通信的基础,提供进程间通信的端点

  • 工作机制:通过IP地址和端口号的组合实现不同主机间的数据传输

  • 主要功能:建立连接、数据传输、连接释放等网络通信全过程管理

2. 用户数据包协议(UDP)
  • 协议特点:无连接、不可靠但高效的传输协议

  • 数据单元:以独立的数据包形式传输,每个包包含完整的目标地址信息

  • 适用场景:适用于实时性要求高但允许少量丢包的应用,如视频会议、在线游戏等

3. 传输控制协议(TCP)
  • 协议特点:面向连接、可靠传输的协议

  • 工作机制:通过三次握手建立连接,四次挥手释放连接

  • 可靠性保障:采用确认应答、超时重传、流量控制等机制确保数据完整有序传输

  • 适用场景:适用于要求数据完整性的应用,如文件传输、网页浏览等

http://www.dtcms.com/a/267224.html

相关文章:

  • 【性能优化与架构调优(二)】高性能数据库设计与优化
  • 【科普】Keil5软件使用教程、小技巧学习笔记:11个知识点。DIY机器人工房
  • 【数据结构】排序算法:归并与堆
  • Python入门Day4
  • Cortex-M 异常处理的 C 实现、栈帧以及 EXC_RETURN
  • 操作符详解(上)
  • 深入解析Redis 7.0中每种数据类型的底层实现
  • 【Qt】QStringLiteral 介绍
  • 2025最新Telegram快读助手:一款智能Telegram链接摘要机器人
  • 深入理解微服务中的服务注册与发现
  • 《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
  • 云原生微服务间的异步消息通信:最终一致性与系统容错的架构实战
  • 供应链管理学习笔记4-供应链网络设计
  • 前端-CSS-day1
  • QT中的网络通信
  • LLM:位置编码详解与实现
  • 深层神经网络:原理与传播机制详解
  • java的注解和反射
  • JVM的位置和JVM的结构体系
  • 交互式剖腹产手术模拟系统开发方案
  • 【openp2p】学习3:【专利分析】一种基于混合网络的自适应切换方法、装 置、设备及介质
  • C# 事件(事件访问器)
  • vue中添加原生右键菜单
  • [特殊字符]全面解锁远程运维新时代:CRaxsRat v7.4 工具实用指南(附推荐资源)
  • Oracle 高级 SQL 查询与函数详解:多表连接、子查询、聚合、分析函数
  • 冒泡和快速排序的区别
  • faster_lio 原理及代码
  • 【Oracle专栏】分区表增加分区
  • WPF学习笔记(25)MVVM框架与项目
  • spring-ai-alibaba 1.0.0.2 学习(十二)——聊天记忆扩展包