2025年1月

本文介绍了linux内核态EtherCAT主站igh 中EoE的具体实现,希望对你有所帮助。

1.IgH 框架概述

详见本博客博文
【原创】EtherCAT主站IgH解析(一)--主站初始化、状态机与EtherCAT报文
,不再赘述。

2. IgH EOE机制

【原创】浅谈EtherCAT主站EOE(上)-EOE网络
中已经对EOE有了整体的认识,EOE是与操作系统EtherNet相关的,这意味着不同的主站、在不同的操作系统下具体实现是不一样的,接下来我们看看linux内核态中的igh主站EOE是如何实现的。

2.1 EoE服务规范

按照ETG官方文档中对EoE应用服务的定义,EoE主站需要提供如下服务:

  • 初始化EoE请求(Initiate EoE),暂不清楚其作用和应用场合。

request:EOE frameType 0x02

response: EOE frameType 0x03

  • EoE帧传输请求(EoE Fragment),用于传输主站与从站的标准以太网数据,
    只有请求,没有响应。

request:EOE frameType 0x00

  • 设置IP参数请求(Set IP Parameter),设置从站的IP地址、网关等配置信息,请求-响应模式。

request:EOE frameType 0x02

response: EOE frameType 0x03

  • 设置MAC过滤器请求(Set MAC Filter),请求-响应模式。暂不清楚其作用和应用场合。

request:EOE frameType 0x04

response: EOE frameType 0x05

关于EOE服务的数据结构规范参考ETG100.6.

2.1 EoE虚拟网络设备

首先IgH主站运行在Linux内核态,所以上篇文章
【原创】浅谈EtherCAT主站EOE(上)-EOE网络
中的情况之一。

主站完成从站扫描后,EtherCAT主站根据SII信息中邮箱协议的支持情况,为支持EoE的从站创建虚拟网络设备,注册到linux网络设备驱动中,用于和EoE从站的数据交互,在Linux看来就是多了很多虚拟以太网网卡,每个网络接口对于一个ethercat eoe从站。

image-20210523100431887

2.1.1 EoE Virtual Network Interfaces

创建虚拟网络设备接口时,其接口名称规定如下:

eoe<MASTER>[as]<SLAVE>

[as]
a表示使用别名,
<SLAVE>
则为站点别名;s表示使用别名,
<SLAVE>
则为站点地址,
<MASTER>
表示主站index。

2.1.2 EoE Virtual Network MAC Address

Eoe Mac地址派生自唯一的mac。

现在,EoE MAC地址从可用网络接口的链接列表的第一个全局唯一MAC地址的NIC部分派生,或者从EtherCAT主站使用的MAC地址派生,或者linux随机生成。

EoE MAC地址将采用
02:NIC:NIC:NIC:RP:RP
的格式,其中
NIC
来自唯一的MAC地址,而RP是EoE从站的环位置。

image-202305220916361262

2.2.3 Linux网络数据处理流程

再介绍igh eoe具体实现之前,有必要先介绍Linux网络数据处理,以Linux 应用TCP通信为例,根据上图,简要说明一个网络数据如何到达网络设备驱动层的:

  1. VFS 层:应用通过write socket文件描述符发送数据,
    write
    系统调用找到
    struct file
    ,根据里面的
    file_operations
    的定义,调用
    sock_write_iter
    函数。
    sock_write_iter
    函数调
    sock_sendmsg
    函数。

  2. Socket 层:从
    struct file
    里面的
    private_data
    得到
    struct socket
    ,根据里面
    ops
    的定义,调用
    inet_sendmsg
    函数。

  3. Sock 层:从
    struct socket
    里面的
    sk
    得到
    struct sock
    ,根据里面
    sk_prot
    的定义,调用
    tcp_sendmsg
    函数。

  4. TCP 层:
    tcp_sendmsg
    函数会调用
    tcp_write_xmit
    函数,
    tcp_write_xmit
    函数会调用
    tcp_transmit_skb
    ,在这里实现了 TCP 层面向连接的逻辑;完成TCP头添加传递给IP层。

  5. IP 层:扩展
    struct sock
    ,得到
    struct inet_connection_sock
    ,根据里面
    icsk_af_ops
    的定义,调用
    ip_queue_xmit
    函数。

  6. IP 层:
    ip_route_output_ports
    函数里面会调用
    fib_lookup
    查找路由表。FIB 全称是 Forwarding Information Base,转发信息表,也就是路由表。填写 IP 层的头。还要做的一件事情就是通过
    iptables
    规则。完成IP头添加后,IP 层接着调用
    ip_finish_output
    进行 MAC 层处理。

  7. MAC 层需要 ARP 获得 MAC 地址,因而要调用
    ___neigh_lookup_noref
    查找属于同一个网段的邻居,它会调用
    neigh_probe
    发送 ARP。

  8. 有了 MAC 地址,添加MAC头后,就可以调用
    dev_queue_xmit
    发送二层网络包了,它会调用
    __dev_xmit_skb
    会将请求放入队列,最后到达设备层。

  9. 设备层:网络包的发送会触发一个软中断
    NET_TX_SOFTIRQ
    来处理队列中的数据。

  10. 在软中断处理函数中,会将网络包从队列上拿下来,调用网络设备的传输函数
    ndo_xmit_frame
    ,将网络包发到设备的队列上去。

到此上层应用发送的网络数据包已到达EOE虚拟网络设备驱动中,如何将上层应用的网络数据报通过EtherCAT EOE 发送给从站,这需要主站来实现,具体实现机制后面详细分析。接收从站应用网络数据包是一个相反的过程。

2.2 EoE request实现

对于
Set IP Parameter request

Set MAC Filter request
类似IgH
SDO upload/download
请求机制,简述如下。

  1. 通过
    ioctl()
    向应用提供
    Set IP Parameter

    Set MAC Filter
    请求的接口。
  2. 各参数通过
    ioctl
    传递到内核后,根据参数找到需要操作的从站对象,构建eoe request对象
    eoe_request_t
    ,插入到从站对象
    eoe_requests
    链表中,然后阻塞等待内核线程执行状态机处理完成。

image-20210523100708125

  1. 状态机方面,类似slave状态机下处理sdo下载请求的子状态机
    fsm_coe
    ,在slave状态机下新添加处理eoe请求的子状态机
    fsm_eoe

  2. 内核线程执行slave状态机过程中,slave状态机的READY状态函数判断从站对象的
    eoe_requests
    链表是否有挂起的请求需要处理(链表非空),有则初始化并执行子状态机
    fsm_eoe
    来处理链表上的request。

  3. fsm_eoe
    状态机执行,与从站邮箱通信完成
    Set IP Parameter
    后,唤醒阻塞的用户态应用。

  4. 内核线程继续执行slave状态机的下一个状态。

image-20210523100731954

Fsm_eoe状态机状态状态转换如下:

  • SET_IP_START

    设置Ip参数入口,检查邮箱EOE支持情况,构造设置 ip 请求箱帧,
    发送数据帧,SET_IP_REQUEST状态。

  • SET_IP_REQUEST
    : 设置IP参数请求帧已接收

    发送查询邮箱数据帧,检查邮箱是否有数据,转到SET_IP_CHECK状态。

  • SET_IP_CHECK
    :查询邮箱数据帧已接收,如果从站邮箱有数据,则发送取邮箱数据帧,转到SET_IP_RESPONSE状态。

  • SET_IP_RESPONSE

    邮箱数据帧已接收
    ,若邮箱数据是set IP request 响应数据,检查结果,转到结束或出错。

若不是响应数据,则继续发送查询邮箱数据帧,跳转到SET_IP_CHECK继续检查。

image-20210523100806879

Fsm_eoe状态转换、与从站邮箱数据交互如图所示:

image-20210523100813622

2.3 EoE Frament实现

5.2中,linux网络应用数据经过协议栈层层封装,调用网络设备的传输函数
ndo_xmit_frame
,将网络包发到EOE虚拟网络设备后,主站需要一种机制将EOE虚拟网络设备中的网络数据包通过EtherCAT数据帧发送到从站。

同时,主站需要查询邮箱,将从站TCP/IP应用需要发送的标准以太网数据通过邮箱协议读取,解析并提交给linux网络协议栈接收处理。

image-20210523100925825

2.3.1.EoE Handler

虚拟EoE接口、EOE帧请求状态机和相关功能封装在
ec_eoe_t
类中,称为EoE handler。每个EOE虚拟网卡对应一个EoE handler。主站扫描从站过程中,根据从站对邮箱协议支持情况,为EoE从站创建虚拟设备,同时创建EOE handler,并将EOE handler放到EoE虚拟设备的私有数据中。

主站使用链表
eoe_handlers
来管理主站下的EoE handler,创建后的EoE handler插入该链表中,方便后续内核线程遍历处理。

image-20210523100959641

2.3.2 EoE 状态机

每个EOE数据(以太网数据包)通过邮箱分段发送和接收,根据邮箱的交互特性,则每个EOE handler需要一个状态机来完成EOE数据收发工作。

  • RX_START
    :EOE状态机起始状态,
    发送邮箱查询数据帧
    查询从站邮箱是否新数据。发送后跳转
    RX_CHECK
    状态。

  • RX_CHECK

    邮箱查询数据帧已接收
    ,若从站邮箱没有数据,转到
    TX_START
    开始写邮箱数据。

若邮箱有新数据,
发送读取邮箱数据帧
读取邮箱数据,转到
RX_FETCH

  • RX_FETCH

    读取邮箱数据帧已接收,
    如果邮箱数据不是“EoE Fragment request”,结束读流程转到发送流程TX_START。

如果接受的邮箱数据是一个”EOE Fragment request”如果是一个起始帧,则分配一个新的socket_buffer,否则填充到已有的socket_buffer的正确的位置,转到
RX_START
继续接收。

如果是最后一个帧,将socket_buffer提交到linux网络协议栈处理,完成一个接收流程,开始发送流程,转到
TX_START
状态。

  • TX_START
    :开始一个发送序列,检查
    tx_ring
    是否有网络数据包需要发送,如果没有,转到
    RX_START
    开始一个新的接收序列。

检查一个skb是否发送完毕,若完全发送成功,将其从
tx_ring
删除,通知linux可以继续往网络设备下发数据包。开始发送一个新的skb的第一个EOE帧,转到
TX_SENT
.

  • TX_SENT:
    根据WKC检查第一个EOE帧是否发送成功,第一个EOE帧发送成则转到
    TX_SENT
    发送下一个帧

    直到整个skb发送完。

如果skb的最后一帧发送成功,转到RX_START 开始一个新的接收序列。


image-20210523101024212

2.3.3 EoE 线程

每个EOE虚拟网卡对应一个EoE handler,多个从站就需要多个handler,且Linux协议层下发的skb远大于邮箱大小,每个skb需要多次邮箱通信才能完成一个skb发送,若将所有handler放到
idle_thead

operat_thead
去处理,不仅严重影响其他数据交互的及时性,而且每几个周期只能发送或接收一个EoE片段,这导致数据速率非常低,因为在应用程序周期之间的时间内未执行EoE状态机,EoE数据速率将取决于应用程序任务的周期。

为解决这些问题,故在主站中单独使用一个内核线程
eoe_thead
来处理所有
EoE handler
,在从站邮箱空闲时进行EOE数据发送,保证了带宽的恒定。

image-20210523101124033

添加eoe线程后带来一些其他问题,如从站邮箱并发操作、xenomai实时应用与eoe并发访问主站发送等,需要一定机制解决,这在后面章节4.5介绍。

EOE线程每周期处理一个状态,所以EOE线程的运行周期决定EOE通讯延迟,即与操作系统调度强相关。

2.3.4 TX Ring

为提高各EOE数据吞吐量,充分利用EtherCAT剩余带宽,为每个虚拟网络设备添加
skb_buff
发送缓冲区队列
tx_ring
。每次内核通过接口的
hard_start_xmit()
回调进行发送一个skb时,将这个skb插入
tx_ring
中,再由
eoe_thead
执行EoE handler处理发送;EoE Handler 处理过程中根据
tx_ring
的容量情况通知linux协议栈暂停/开始向该EoE虚拟设备发送数据,以控制流量。

对于接收,EtherCAT总线带宽100Mbps,考虑到是多个从站共同使用、且带宽不完全由EoE占用,上层linux能及时处理,暂不需要。

2.3.5 EoE 处理流程简述

  1. 协议栈处理后的网络数据包skb,通过EOE虚拟设备驱动提供的回调函数
    ndo_xmit_frame()
    插入EoE handler的
    tx_ring
    中,如果该队列已满,则通过调用
    netif_stop_queue()
    来通知上层暂停新skb的下发。

image-20210523101230636

  1. EoE线程循环处理主站下处于激活状态的
    EoE Handler
    ,即执行状态机EoE状态机,解析或填充datagram。

image-20210523101241431

  1. 执行完所有的
    EoE handler
    一个状态后,状态机已构建好EoE通信数据报,调用eoe发送回调函数,将这些数据报对象,插入eoe发送链表
    ext_datagram_queue
    .

  2. 先将
    ext_datagram_queue
    上的数据报转移到主发送链表
    datagram_queue
    ,再调用主发送函数
    ecrt_master_send()
    发送,发送过程与2.4节数据报发送流程一致。

2.4. 共享资源访问控制

若Ether CAT主站仅使用邮箱的CoE功能,CoE功能相关状态机由内核线程
idle_thead
(或
operat_thead
)执行。在此基础上实现EOE功能需要添加内核线程eoe_thread来处理EoE数据,因为两个线程都需要访问主站总线、从站邮箱,所以总线和从站邮箱会成为两个内核线程的共享资源,对它们的访问必须按顺序进行。

2.4.1 主站

多任务下,主站总线是共享资源,必须按顺序对它进行访问。

IDLE阶段

主站IDLE阶段,内核EoE线程、
idle_thead
都需要调用主站发送函数
ecrt_master_send()
访问总线,这个阶段总线顺序访问由master对象中的信号量
io_sem
来保证。

Operation阶段

应用请求主站后,主站处于operation状态,此时
idle_thead
退出,所以总线便成为
EoE线程

周期应用程序
之间的共享资源,这时需要根据具体的操作系统来解决。

  1. 对于普通linux应用,使用普通内核信号量即可,

    ioctl
    系统调用发送的操作前后使用io_sem来保证

  2. 对于Xeomai和RTAI应用,需要区分内核态和用户态:

  • 对于
    用户态应用
    ,若用按普通linux的方式,使用普通内核信号量,会导致实时任务切换到非实时域,影响周期任务实时性。正确的解决方式为有两种:

方法一、将内核线创建为xenomai内核线程,且使用xenomai内核锁来避免域切换(整个协议栈使用xenomai实现,工作量较大)

方法二、将EOE处理循环通过ioctl导出到用户空间,由xenomai用户线程来驱动EOE的处理。

  • 对于
    内核态应用
    ,使用IgH已有的方式,
    内核应用必须负责提供适当的锁定机制和回调函数
    ,如果另一个实例想要访问主站,则它必须通过回调请求总线访问,如下图所示。

image-20210523101346293

风险:周期应用与EOE线程对主站总线的顺序访问,可能加大周期数据的抖动。

2.4.2 slave邮箱

对于邮箱,若不按顺访问,可能出现如下情况

假如主站先后成功对同一个从站下发COE和EoE请求,但从站先将COE响应还是EoE响应写到邮箱未知,若此时邮箱内为EoE响应数据可读,由于读取报文前后的不确定性,可能造成CoE 状态机读取了EoE响应数据,CoE状态机处理时检测到数据类型错误而丢弃。而EoE读取失败,两个状态机均无法正常执行。

为此解决办法是
只让一个状态机去取邮箱数据
,slave对象中添加一个标志表示邮箱有没有被使用,使用mutex或原子变量来保护该变量,当状态机要读邮箱时先判断该标识,若邮箱已被其他状态机使用,先跳转到下一个状态或重试。

image-20210523101417823

这只能保证只有一个线程中的状态机取数据,但还没解决邮箱数据与执行读的状态机不一致导致数据丢失的问题,所以还要解决数据丢失问题:

a. 抽象邮箱数据为ec_mbox_data_t,为每个从站状态机添加从站支持的协议对应的邮箱数据对象ec_mbox_data_t,如下。

image-20210523101428985

b. 主
站接收到
EtherCAT
数据帧解析子报文时,加入报文解析步骤

,解析报文,看是否符合邮箱数据格式,若符合根据邮箱数据类型分别拷贝到对应的ec_mbox_data_t对象中,如接收的报文满足COE邮箱数结构,则将报文数据保存到mbox_coe_data中。这样不论该数据是哪个状态机读取的,该数据都会得到保存。

image-20210523101439719

c. 修改邮箱相关状态机,状态机读邮箱数据时,若邮箱已被占用,直接跳转下一个状态,先检查对应的
ec_mbox_data_t
有无数据,有则处理,没有则回到上check状态重试。

风险:每个子报文数据都需要判断是否为邮箱数据,并解析,增加CPU资源、一周期内主站时间消耗增加;

接收过程中,目前从站对象、数据报之间的查找关系通过遍历链表来实现,随着从站数据量增加,时间复杂度呈线性增长,是否考虑通过其他数据结构优化。

3. EOE虚拟网络设备管理

3.1 Linux Bridge介绍

主站为每个EOE从站创建了虚拟网络设备,有了虚拟网卡,我们很自然就会联想到让网卡接入到交换机里,来实现多个容器间的相互连接。而Linux Bridge就是 Linux 系统下的虚拟化交换机,虽然它是以“网桥”(Bridge)而不是“交换机”(Switch)为名,但在使用过程中,你会发现 Linux Bridge 看起来像交换机,功能使用起来像交换机、程序实现起来也像交换机,所以它实际就是一台虚拟交换机。
Linux Bridge 是在 Linux Kernel 2.2 版本开始提供的二层转发工具,由brctl命令创建和管理。Linux Bridge 创建以后,就能够接入任何位于二层的网络设备,无论是真实的物理设备(比如 eth0),还是虚拟的设备(比如 veth 或者 tap),都能与 Linux Bridge 配合工作。当有二层数据包(以太帧)从网卡进入 Linux Bridge,它就会根据数据包的类型和目标 MAC 地址,按照如下规则转发处理:

  • 如果数据包是广播帧,转发给所有接入网桥的设备。如果数据包是单播帧,且 MAC 地址在地址转发表中不存在,则洪泛(Flooding)给所有接入网桥的设备,并把响应设备的接口与 MAC 地址学习(MAC Learning)到自己的 MAC 地址转发表中。
  • 如果数据包是单播帧,且 MAC 地址在地址转发表中已存在,则直接转发到地址表中指定的设备。

如果数据包是此前转发过的,又重新发回到此 Bridge,说明冗余链路产生了环路。由于以太帧不像 IP 报文那样有 TTL 来约束,所以一旦出现环路,如果没有额外措施来处理的话,就会永不停歇地转发下去。那么对于这种数据包,就需要交换机实现生成树协议(Spanning Tree Protocol,STP)来交换拓扑信息,生成唯一拓扑链路以切断环路。

刚刚提到的这些名词,比如二层转发、泛洪、STP、MAC 学习、地址转发表,等等,都是物理交换机中已经非常成熟的概念了,它们在 Linux Bridge 中都有对应的实现,所以我才说,Linux Bridge 不仅用起来像交换机,实现起来也像交换机。
不过,它与普通的物理交换机也还是有一点差别的,普通交换机只会单纯地做二层转发,
Linux Bridge 却还支持把发给它自身的数据包,接入到主机的三层协议栈中

对于通过brctl命令显式接入网桥的设备,Linux Bridge 与物理交换机的转发行为是完全一致的,它也不允许给接入的设备设置 IP 地址,因为网桥是根据 MAC 地址做二层转发的,就算设置了三层的 IP 地址也没有意义。然而,Linux Bridge 与普通交换机的区别是,除了显式接入的设备外,它自己也无可分割地连接着一台有着完整网络协议栈的 Linux 主机,因为 Linux Bridge 本身肯定是在某台 Linux 主机上创建的,我们可以看作是 Linux Bridge 有一个与自己名字相同的隐藏端口,隐式地连接了创建它的那台 Linux 主机。
因此,Linux Bridge 允许给自己设置 IP 地址,这样就比普通交换机多出了一种特殊的转发情况:如果数据包的目的 MAC 地址为网桥本身,并且网桥设置了 IP 地址的话,那该数据包就会被认为是收到发往创建网桥那台主机的数据包,这个数据包将不会转发到任何设备,而是直接交给上层(三层)协议栈去处理。这时,网桥就取代了物理网卡 eth0 设备来对接协议栈,进行三层协议的处理。

主站为每个EOE从站创建了虚拟网络设备,这些虚拟网络设备需要连接外网,否则上位机或其他网络设备无法与其连接,在linux下,其连接外网有
桥接

NAT
两种方式,两种方式如下图所示。

image-20210523101515791

image-20210523101522580

Linux中使用虚拟网桥实现图中虚拟交换机的功能,创建网桥
eoebr0
,eoe虚拟网卡都连到
eoebr0
上,与上图中不同的是,
物理网卡不连接到eoebr0,通过设置 net.ipv4.ip_forward = 1,开启物理机的转发功能,同时为网桥设置ip地址,作为所有EOE虚拟网卡的网关。

这样其中物理网卡eth0收到数据包,根据数据包的目的ip地址将数据包发往网桥
eoebr0

eoebr0
再根据路由表继续发送数据包到EOE虚拟网卡,最后有EtherCAT master 通过EOE完成数据包到从站的传输。从站发送的EOE数据包一个相反的过程。

注: Linux内核需启用相关模块,以便支持桥接、路由。

3.2 桥接

综上,创建一个包含所有EoE接口的网桥,网桥内从站EOE处于同一网段:


image-20210523101548671

  1. 创建网桥设备
    br0
$sudo brctl addbr br0
  1. 配置网桥设备
    br0
    IP
$sudo ip addr add 192.168.100.1/24 dev br0

$sudo ifconfig br0 192.168.57.1/24
  1. 向br0中添加网卡
$sudo brctl addif br0 eoe0s0
$sudo brctl addif br0 eoe0s1
  1. 设置eoe虚拟网卡IP并up
$sudo ifconfig eoe0s0 0.0.0.0 up
$sudo ifconfig eoe0s1 0.0.0.0 up
  1. 设置从站eoe Ip参数
$sudo ethercat ip addr 192.168.100.2/24 link 00:11:22:33:44:04 default 192.168.100.1 name xxx0 -p 0

$sudo ethercat ip addr 192.168.100.3/24 link 00:11:22:33:44:05 default 192.168.100.1 name xxx1 -p 1
  1. Up
    br0
$sudo ip link set br0 up
  1. Down
    br0
#ip link set br0 down

  1. br0
    中删除网卡
#brctl delif br0 eoe0s0
#brctl delif br0 eoe0s1
  1. 删除网桥
    br0
#brctl delbr br0

上面的示例允许使用通过EoE连接到EtherCAT总线的子网
192.168.100.0/24
访问IPv4节点。

但是,示例使用配置命令临时配置,如果总线拓扑发生更改,则会重新创建EoE接口,并且必须再次将其添加到网桥。 因此,需使用Linux网络配置文件来保存配置,或使用
udev
脚本、应用监测网络热拔插等动态配置方式,以便自动添加出现的EoE设备。

image-20230522091636126

3.3 路由

为每个EoE接口创建一个IP子网:

$ip addr add 192.168.200.1/24 dev eoe0s0
$ip addr add 192.168.201.1/24 dev eoe0s1
$echo 1 > /proc/sys/net/ipv4/ip_forward

如果它们能够在不同的EoE接口IP网络之间进行通信,则必须在连接到EoE从站的IP节点上正确设置默认网关。

如果必须设置从站IP地址和其他参数(而不是主站EoE接口),则可以通过命令行工具
ethercat ip
来实现。

ethercat ip addr 192.168.100.3/24 link 00:11:22:33:44:05 default 192.168.100.1 name xxxx -p 1

EOE虚拟网卡仅负责传输linux网络协议栈下发的数据包,可通过设置linux iptables来过滤不相关的数据包。

4. 总结

EtherCAT EoE协议与普通以太网EtherNet强相关,所以EtherCAT 主站EoE实现方式与具体的操作系统强相关,本文介绍了linux内核态igh的EoE实现方式。

一、通信协议概述

1.串行通信与并行通信

  • 串行通信
    (serial communication):数据通过单根数据线一位一位地传输;成本低但速度慢;适用于远距离传输,用于计算机与外设之间,如UART、
    \(I^2C\)
    、SPI
  • 并行通信
    (parallel communication):通过多根数据线同时将数据的所有位一次传输完成;成本高但传送速度快、效率高;适用于近距离传输,用于计算机内部,如CPU数据总线、存储器译码电路

2.同步通信和异步通信

  • 同步通信
    :发送方发送数据后,需要等待收到接收方发回的响应后再发送下一个数据包。同步通信是阻塞模式;
  • 异步通信
    :发送方发送数据后,不需要等待接收方发回的相应就可以发送下一个数据包。异步通信是非阻塞模式

3.数据流传输模式

  • 单工通信
    :只支持信号在
    一个方向
    上传输(正向或反向),任何时候不能改变信号的传输方向。此种方式适用于数据收集系统,例如计算机和打印机之间的通信是单工模式,因为只有计算机向打印机传输数据,而没有相反方向的数据传输。
  • 半双工通信
    :半双工通信允许信号在
    两个方向
    上传输,但某一时刻只允许信号在一个信道上单向传输,因此,半双工通信实际上是一种
    可切换方向的单工通信
    ,传统的对讲机使用的就是半双工通信方式,由于对讲机传送及接受使用相同的频率,不允许同时进行,通信双方需要通过一定的协议或者机制来协调通信方向
  • 全双工通信
    :允许数据同时在
    两个方向
    上传输,即有两个信道,因此允许同时进行双向传输,全双工通信是两个单工通信方式的结合,要求收发双方都有独立的接受和发送能力。全双工通信效率高,控制简单,但造价高。计算机之间的通信是全双工方式,一般的电话、手机也是全双工的系统,因为在讲话时可以听到对方的声音。

4.通信的基本组成

  • 发出起始信号;
  • 寻址;
  • 数据传输;
  • 发出终止信号

5.不同通信协议的特点

二、UART串口通信

1.概念

通用异步收发传输器
(Universal Asynchronous Receiver/Transmitter,UART):将数据由串行通信与并行通信之间做传输转换的异步全双工串行接口,作为并行输入转为串行输出的芯片,通常集成于其他通讯接口的连接上,其工作原理是将数据一位接一位地进行传输;
异步:没有共同的时钟

2.数据组成

  • 起始位
    :先发出一个逻辑
    “0”
    的信号,表示传输字符的开始。
  • 数据位
    :紧随起始位之后,数据位的个数可以是5、6、7、8等,构成一个字符,通常采用ASCII码,从
    最低位
    开始传送,靠时钟定位
  • 奇偶校验位
    :数据为加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来检验数据传输的正确性
  • 停止位
    :是一个字符数据的结束标志,可以是1位、1.5位、2位的高电平,由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步,因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。停止位的位数越多,不同始终同步的容忍程度越大,但是数据传输率同时也越慢。
  • 空闲位
    :处于逻辑“1”状态,表示当前线路上没有数据传送

3.工作原理

  • 发送数据过程
    :空闲状态,线路处于高电位;当收到发送数据指令后,拉低线路一个数据位的时间T,接着数据
    按低位到高位
    (小端传输)依次发送,数据发送完毕后,接着发送奇偶校验位和停止位,一帧数据发送结束;

Q:UART通信中,接收方如何识别数据位还是奇偶校验位?
A:在UART通信中,数据帧中数据为的长度是预先设置好的,这个长度在通信开始之前需要由发送方和接收方共同约定好。选择合适的数据位长度可以提高通信效率。例如,在只需传输ASCII字符的情况下,选择7位数据位可以节省带宽;而在需要传输复杂数据的情况下,选择8位数据位可以提供更大的数据传输能力

  • 接收数据过程
    :空闲状态,线路处于高电位;当检测到线路的下降沿(线路电位由高电位变为低电位)时说明线路有数据传输,按照约定的波特率从低位到高位接受数据,数据接受完毕后,开始接收并比较奇偶校验位是否正确,如果正确则通知后续设备准备接收数据或存入缓存

Q:如何区分1->0和11->00?
A:UART波特率发生器:
波特率
是衡量数据传输速率的指标,表示每秒传送数据的字符数,单位是bps,UART的接收和发送是按照相同的波特率进行收发的,发送方与接收方之间的波特率只能相差约10%

Q:发送方和接收方没有共同的时钟,如何保证数据传输的正确性?
A:波特率发生器产生的时钟频率不是波特率时钟频率,而是波特率时钟频率的
16倍
,即每个数据帧
有16个时钟采样
,取
中间的采样值

以传输8位数据为例,当检测到数据的下降沿时,表明线路上有数据进行传输,这时计数器CNT开始计数,当计数器为24=16(起始位)+8(第0位数据中间采样值)时,采样的值为第0位数据;当计数器的值为40时,采样的值为第1位数据,依此类推,进行后面6个数据的采样。如果需要进行奇偶校验,则当计数器的值为152时,采样的值即为奇偶校验位;当计数器的值为168时,采样的值为“1”表示停止位,一帧数据接收完成。

Q:为什么UART无需寻址?
A:UART是一种简单的点对点通信协议,通常用于两个设备之间的直接通信,在这种通信模式下,发送端和接收端是固定的,不需要识别多个设备或节点

三、
\(I^2C\)
串口通信

1.概念

\(I^2C\)
(Inter-Integrated Circuit)总线是PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准,他是同步通信的一种特殊形式,具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。

2.
\(I^2C\)
总线

\(I^2C\)
总线是一种
多主机总线
,连接在IIC总线上的器件分为主机和从机:主机有权发起和结束一次通信,而从及只能被主机呼叫;
当总线上有多个主机同时启用总线时,IIC也具备
冲突检测和仲裁
的功能来防止错误产生;
每个连接到IIC总线上的器件都有一个
唯一的地址(7bit)
,且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作
IIC总线在通信时总线上发送数据的器件为发送器,接受数据的器件为接收器;
\(I^2C\)
总线只有两根
双向
信号线,一根是
数据线SDA
,另一根是
时钟线SCL

IIC总线通过上拉电阻接正电源,当总线空闲时,两根线均为
高电平
,连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA以及SCL都是
线“与”关系

线与关系
:多个输出端连接到同一条线上。如果任何一个输出端被拉低,那么整条线的电压就会被拉低。只有当所有连接到这条线上的输出端都保持高电平时,整条线才会是高电平。

3.通信过程

  • 主机发送起始信号启用总线
  • 主机发送从机地址进行寻址
  • 被寻址的从机发送应答信号回应总机
  • 发送器发送一个字节数据
  • 接收器发送应答信号回应发送器
  • ......(循环步骤4、5)
  • 通信完成后主机发送停止信号释放总线

4.通信原理

4.1 起始信号/终止信号
  • SCL线为高电平期间,SDA线由

    电平向

    电平的变化表示
    起始信号
  • SCL线为高电平器件,SDA线由

    电平向

    电平的变化表述
    终止信号

起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号产生后,总线就处于空闲状态

4.2 寻址方式
  • IIC总线上传送的数据是广义的,既包括地址,又包括真正的数据
  • IIC总线通信时每个字节为8位长度,该数据的高7位是从机地址,最低位表示后续字节的传送方向:0表示主机发送数据,1表示主机接收数据
4.3 数据传输与应答
  • 数据发送
    :数据传送时,先传送最高位,后传送低位
  • 从机应答

    • 发送器发送完一个字节数据后,每个从机将主机发送的地址与其子集的地址进行比较
      • 如果地址匹配,则从机通过将SDA线
        拉低一位
        返回一个ACK位;
      • 如果主机的地址与从机的的地址不匹配,则从机将SDA线
        拉高
    • 若由于某种原因从机不对主机寻址信号应答时(如从机正在进行实时性的处理工作而无法接收总线上的数据),它必须将数据线置于
      高电平
      (高电平表示不应答主机的请求),而由主机产生一个终止信号以结束总线的数据传送
    • 接收器件收到一个完整的数据字节后,有可能需要完成一些其他工作,如处理内部中断服务等,这时接收器件可以将SCL线拉成低电平,从而使主机处于等待状态。直到接收器件准备好接收下一个字节时,再释放SCL线使之成为高电平,从而使数据传送可以继续进行
    • 如果从机对主机进行了应答,但在数据传送一段时间后无法继续接收更多的数据,从机可以通过对无法接收的第一个数据字节的“非应答”通知主机,主机则应发出终止信号以结束数据的继续传送
    • 当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号,这个信号是由对从机的“非应答”来实现的。然后,从机释放SDA线,以允许主机产生终止信号。
    • 传输完每个数据帧之后,接收设备将另一个ACK位返回给发送方,已确认已经成功接收到该数据帧;随后主机将SCL切换为高电平,然后再将SDA切换为高电平,从而向从机发送终止信号
  • 主机向从机发送数据,数据传送方向在整个传送过程中不变

主机在第一个字节后,立即从从机读取数据

在传送过程中,当需要改变传送方向时,
起始信号

从机地址
都被重复产生一次,但两次读/写方向位正好相反

4.4 信号同步

IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为
低电平期间
,数据线上的高电平或低电平才允许变化

四、SPI串口通信

1.概念

串行外围设备接口
(Serial Peripheral interface,SPI)是一种高速的,
全双工,同步
的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便。主要应用在EEPROM,FLASH,实时时钟,AD转换器还有数字信号处理器和数字信号解码器之间
SPI分为主、从两种模式,一个SPI通讯系统有且只有一个主设备,以及一个或多个从设备,
提供时钟的为主设备(Master),接收时钟的为从设备(Slave)
。在实际应用中,MCU一般作为主SPI设备,带SPI接口的外围器件作为从设备

2.SPI信号线

  • MOSI
    :SPI总线主机输出/从机输入(SPI Bus Master Output/Slave Input)该引脚在从模式下发送数据,在主模式下接收数据
  • MISO
    :SPI总线主机输入/从机输出(SPI Bus Master Input/Slave Output)该引脚在主模式下发送数据,在从模式下接收数据
  • SCLK
    :时钟信号,由主设备产生
  • SS/CS
    :从设备片选信号,由主设备控制,它的功能是用来作为”片选引脚“,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突

3.数据传输

SPI主机和从机都有一个串行移位寄存器
,主机通过向它的SPI串行寄存器写入一个字节来发起一次传输。

  • 首先拉低对应SS信号线,表示与该设备进行通信
  • 主机通过发送SCLK时钟信号,来告诉从机写数据或者读数据
  • 主机(Master)将要发送的数据写到发送数据缓存区(Menory),缓存区经过移位寄存器(0~7),串行移位寄存器通过MOSI信号线将字节一位一位的移出去传送给从机,,同时MISO接口接收到的数据经过移位寄存器一位一位的移到接收缓存区。
  • 从机(Slave)也将自己的串行移位寄存器(0~7)中的内容通过MISO信号线返回给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换。

一个从设备要想能够接收到主设备发过来的控制信号,必须在此之前能够被主设备进行访问。所以主设备必须首先通过SS/CS pin对从设备进行片选,把想要访问的从设备选上

4.SPI的4种模式

依据
时钟极性

时钟相位
可以分为四类

  • 时钟极性(CPOL)配置SCLK的电平处于什么状态时是空闲态
    • CPOL=0:空闲态时为低电平(上升沿触发工作状态)
    • CPOL=1:空闲态时为高电平(下降沿时触发工作状态)
  • 时钟相位(CPHA)配置数据采样是在第几个边沿
    • CPHA=0:在第一个边沿采样数据,第二个边沿交换数据
    • CPHA=1:在第一个边沿交换数据,第二个边沿采样数据

前言

LangEngine内源项目发起于阿里巴巴集团内部组织,LangEngine是类似LLM应用开发框架LangChain的纯Java版本。该框架现已正式对外开源:
https://github.com/AIDC-AI/ali-langengine

作为AI应用搭建平台核心架构师,这段时间一直专注于阿里国际APaaS平台以及AI基础设施建设,LangEngine框架也开始结合国际复杂电商业务做一些更加落地性的场景落地,让LangEngine有了新的活力。

在LangEngine框架提供LLM应用所需的原子化能力的基础上,针对阿里国际复杂电商系统的业务流程和多智能体的大模型自主规划能力,构建出了一套更贴近业务诉求的AgentFramework。

在7月底正式上线第一版面向生产环境的高可用AI应用开发框架,目前在内部钉钉群里有1200+的使用方和开发者,内部社区贡献群有40+核心贡献者。

本篇文章是AIDC AI AppBuilder平台建设之路的上篇,包括如何构建高可用的LLM应用程序、开源的LLM应用开发框架介绍、自研的阿里LLM应用开发介绍等等。而下一篇将介绍它可落地的应用场景。

AICon大会阿里国际Agent应用平台分享

AICon2024.12北京站前线带来的
《AI 在电商出海领域的应用落地实践》
分享,其中介绍了阿里国际AI Agent应用平台产品架构在海外电商业务上的落地,由底层驱动的阿里巴巴LangEngine的Java原生AI应用开发框架:
https://github.com/AIDC-AI/ali-langengine
。开发者们可以通过该框架也可以快速搭建AI业务应用,或者基于该框架搭建属于自己的AI应用开发平台。

如何构建高可用的LLM应用程序

要开发LLM应用程序还是需要有工程方面的构建策略和方法论,这里总结出五大步骤。

1. 专注解决一个问题。

问题的定义需要足够详细,这样你就可以快速迭代并取得结果;足够广泛,这样解决方案才能让用户信服。例如,做一个电商领域的智能客服的LLM应用,需要拆解到它的业务类型,售前方向(例如商品咨询、营销活动等等)、售中方向(例如物流咨询、修改地址等等)、售后方向(例如商品退款、商品退换等等);知识库类型(语雀、PDF、钉钉文档等等),背后可以使用的工具(例如一键换货、智能退款分析、消费者情绪分析等等),是否需要走意图识别(固定话术、知识库检索、工具调用等等)。只有问题定义的足够详细,才能进一步展开LLM应用的构建。

2. 选择合适的LLM大模型。

使用预先训练的模型构建LLM应用程序可以节省成本。如果LLM应用程序需要对外商业化,就需要使用具有获得商业使用许可的开源大模型或者API大模型。大模型的参数越多,其学习新知识、预测能力就越强,而小模型的性能越好,推理越快,价格越便宜。还是以电商客服为例,意图识别选择大模型还是小模型,取决于它的预测能力,从经验上来看,这里更多会选择大参数的LLM作为选择。

3. 定制专属的大模型。

可以针对大模型进行微调训练(SFT)或者强化学习(RLHF)。例如多模态的图片识别能力,具体可以阅读《阿里国际AIDC-AI发布新型多模态大模型架构Ovis》

https://mp.weixin.qq.com/s/D509j5mnePn36jrl1LHcy

4. 设置应用程序的架构。

用户输入,包括用户界面 (UI)、大语言模型和应用托管平台。

输入增强和提示构建工具,涵盖了数据源、嵌入模型、向量数据库、提示构建与优化工具以及数据过滤器。

AI工具,包括大语言模型的缓存、内容分类器或过滤器,以及一个用于评估LLM应用输出的评测服务。

LLM推理、Agent相关能力建设,更高效地完成业务的流程编排。

5. 对应用进行在线评估。

这里包括 主观评测、客观评测、人工评测 等等。因此,构建一套LLM应用的评测平台至关重要。

可以看到整个构建LLM应用程序的过程中,不管从解决问题、LLM大模型选择、LLM应用程序架构设计、在线评估等等方面,构建一套高效且可靠的LLM应用开发框架是非常重要的,合适的框架架构能够使得你构建AI业务应用变得十分快捷,当然也可以结合自身的研发能力,通过选择开源的应用开发框架或者自研的方式来构建自己的AI业务应用。接下来来看下,在开源生态方面都有哪些常用的LLM应用开发框架。

开源的常用LLM应用开发框架

LangChain

开源(90k stars,3000+ 贡献者):

https://github.com/langchain-ai/langchain

✦ 开发语言:Python、NodeJS

✦ 生态建设

✧ 社区与贡献十分活跃,基本每天发布1个版本甚至2个版本,可维护性强

✧配套扩展丰富,包括langsmith、langflow、flowise、dify、gptcache、fastapi、langchain-chatglm等等都是基于langchain开源构建。

✧ 文档十分完善,学习门槛低(普通开发人员)

✦ 主要功能

✧ 数据感知,将语言模型与其他数据源相连接。支持大模型和搜索引擎,本地数据源,企业数据源等连接。

✧ 代理能力,允许语言模型与工程系统化能力互动,可以与个人/企业的API进行连接。

✧ 支持扩展不同的大型语言模型,强调模块级开箱即用。

✧ 各个核心模块扩展性强,利于生态快速建设。

Semantic-Kernel

开源(21k stars,300+ 贡献者):

https://github.com/microsoft/semantic-kernel

✦ 开发语言:C#、Python、Java(Required:OpenJDK 17 or newer)

✦ 生态建设

✧ 社区与贡献较活跃,2~3天发布1个版本

✧文档比较完善,学习门槛较低(应用开发人员)

✦ 主要功能

✧ 轻量级SDK,可帮助开发人员将代码组织到内置于Planner中的技能、记忆和连接器中。

✧ 微软配套产品集成性强,GitHub Copilot, M365 Copilot, D365 Copilot 和Security Copilot。

✧ 支持扩展不同的大型语言模型,强调生成式Prompt(Semantic Function),开发灵活。

✧ 软绝对是接入OpenAI最早的公司,企业工程化经验丰富。

Llamma-Index

开源(34k,1100+贡献者)

https://github.com/run-llama/llama_index

✦ 开发语言:Python

✦ 生态建设

✧ 社区与贡献较活跃,2~3天发布1个版本

✧ 文档比较少,学习门槛比较高

✦ 主要功能

✧ 专注于数据框架,包括丰富的数据源和数据格式的读取和转换,利用LLM来做结构化,非结构化做索引。

Auto-GPT

AutoGPT 使用深度神经网络生成高质量、类似人类的文本(以及指令),而无需人工输入或监督。这意味着它可用于自动执行范围广泛的任务,从编写产品说明和新闻文章到撰写电子邮件和聊天机器人回复,或者编写类似俄罗斯方块的程序。最好的(或最令人担忧的)消息是它设置起来很简单。

✦开源(166k stars,330个贡献者)

https://github.com/Significant-Gravitas/AutoGPT

✦ 开发语言:Python

✦ 生态建设

✧ 社区较活跃,迭代较慢,1~2周更新1个版本

✧ 文档比较完善,学习门槛低(普通开发人员)

✦ 主要功能

✧ 最早的AI智能体雏形之一

✧ 高度依赖CoT思维链模式,由GPT驱动,强调自主实现目标。

✧ 内置读写文件、浏览网页、审查提示结果、互联网访问、长期和短期内存管理,使用GPT-3.5文件存储和生成摘要等。

✧ 功能级开箱即用,但基本仅适用于GPT系列,扩展性较弱。

围绕这些条件进行(目标、约束、命令、资源、评估、返回格式)

阿里巴巴LangEngine框架

框架背景

在阿里从事工程开发的主要编程语言还是以Java为主,并且阿里巴巴集团自身就已经具备成熟的Java中间件优势和相关的运维工具体系,从架构师角度上看,针对于企业自身构建适合自己的框架,最后决定构建以Java为语言基础的LLM开发框架。另外一方面,为了解决框架能够服务于多语言,需要专门去学习了下LangChain Python版本的代码工程和技术架构,做好技术储备,迎接AI未来,通过编写应用框架来拥抱和学习AI技术,并且能够真正在业务场景去落地。

工作原理

LangEngine 是一个基于Java用于由大语言模型支持的AI应用程序的开发框架,它也是当下最流行框架 LangChain 的Java版本。

它的特点:

✦ 阿里体系下基于 LangChain 原理的AI应用框架

✦ 引入java特色的工程模块化思路,可支持日志记录、业务监控、链式编排,实现了类流程持久化

✦ 面向阿里系Java工程开发同学,易学易用

✦ 借鉴 LangChain 生态特点,支持社区生态共建

它使应用程序能够:

✦ 具有上下文感知能力:将语言模型连接到上下文来源(提示说明、Few shot示例、响应的内容等)

✦ 代理以及Reason:依靠语言模型进行推理(关于如何根据提供的上下文进行回答、采取什么操作等)

LangEngine的核心框架分为六大模块:Retrieval、Model I/O、Memory、Chains、Agents、Callbacks。

✦ Retrieval

许多LLM应用程序需要特定于用户的数据,这些数据不属于模型训练集的一部分。实现这一目标的主要方法是通过检索增强生成(RAG)。在此过程中,将检索外部数据,然后在执行生成步骤时将其传递给 LLM。

LangEngine 提供了 RAG 应用程序的所有构建模块 - 从简单到复杂。文档的这一部分涵盖了与检索步骤相关的所有内容 - 例如 数据的获取。虽然这听起来很简单,但实际上可能非常复杂。这包含几个关键模块。

✦ Model I/O

任何语言模型应用程序的核心元素都是模型。LangEngine 为您提供了与任何语言模型交互的构建块。

✦ Memory

大多数LLM应用程序都有对话界面。对话的一个重要组成部分是能够引用对话中先前介绍的信息。至少,对话系统应该能够直接访问过去消息的某些窗口。更复杂的系统需要有一个不断更新的世界模型,这使得它能够执行诸如维护有关实体及其关系的信息之类的事情。

将这种存储过去交互信息的能力称为“记忆”。LangEngine 提供了许多用于向系统添加内存的应用程序。这些应用程序可以单独使用,也可以无缝地合并到链中。

内存系统需要支持两个基本操作:读和写。回想一下,每个链都定义了一些需要某些输入的核心执行逻辑。其中一些输入直接来自用户,但其中一些输入可以来自内存。在给定的运行中,一条链将与其内存系统交互两次。

1. 在收到初始用户输入之后但在执行核心逻辑之前,链将从其内存系统中读取并增加用户输入。

2. 在执行核心逻辑之后但在返回答案之前,链会将当前运行的输入和输出写入内存,以便在将来的运行中引用它们。

✦ Chains

✦ Agents

代理的核心思想是使用语言模型来选择要采取的一系列操作。在链中,一系列操作被硬编码(在代码中)。在代理中,语言模型被用作推理引擎来确定要采取哪些操作以及按什么顺序。

✦ Callbacks

LangEngine 提供了一个回调系统,允许您连接到 LLM 申请的各个阶段。这对于日志记录、监控、流传输和其他任务非常有用。

您可以使用整个 API 中可用的回调参数来订阅这些事件。该参数是处理程序对象的列表,这些对象预计将实现下面更详细描述的一个或多个方法

✦ LangRunnable

可以轻松地从基本组件构建复杂的链条。它通过提供以下功能来实现此目的:

1. 统一的接口:每个 LangRunnable 对象都实现 Runnable 接口,该接口定义一组通用的调用方法(invoke、batch、stream、ainvoke 等)。这使得 LangRunnable 对象链也可以自动支持这些调用。也就是说,每个 LangRunnable 对象链本身就是一个 LangRunnable 对象。

2. 组合原语:LangRunnable 提供了许多原语,可以轻松组合链、并行化组件、添加后备、动态配置链内部等。

为了尽可能轻松地创建自定义链,这里实现了“Runnable”协议。大多数组件都实现了 Runnable 协议。这是一个标准接口,可以轻松定义自定义链并以标准方式调用它们。标准接口包括:

✧ invoke:在输入上调用chain

✧ stream:流回响应块

✧ batch:批量在输入上调用chain

这些也有相应的异步方法:

✧ invokeAsync:在输入上异步调用chain

✧ streamAsync:异步流回响应块

✧ batchAsync:批量在输入上异步调用chain。

服务集成

1.RAG服务

LangEngine Retrieval模块包含了丰富的RAG所需要的元素,让你能够基于LangEngine轻松构建RAG服务,检索增强生成(Retrieval-augmented Generation)。通过文档加载器,加载不同文档的格式类型,常用的包括PDF、Text、Excel、CSV、Markdown、Html等文件,也包括各类常见编程语言加载器,还包括在线网页加载器等等,另外在LangEngine提供了基于阿里钉钉、语雀、ODPS、TDDL等等阿里集团内部常用的加载器,最大限度的支持阿里内部的文档场景。文本分割器,实现了常用的文本分割器,包括递归词句分割的能力。接着就是Embeddings和VectorStore,除了常用的能力之外,也集成了包括通义千问的嵌入服务或者各个阿里云上各种的向量数据库服务。另外也提供常用的Query改写、Rerank服务等支持。通过Retriever、DocumentChain、OnlineSearch构建出RAG的向量检索以及在线检索服务。

2. 意图识别服务

优秀的大模型的意图识别固然重要,如果能够进一步增强意图识别能力也是锦上添花的事情。LangEngine Chain中也提供了各种路由链、顺序链、专家链等等可以进一步提升意图识别效果的增强。

3. 工具代理调用服务

LangEngine Agent模块中内置的各种类型的Agent范式类,可以构建常见的Agent服务方式,通过LangRunnable模块可以更加灵活的自定义自己的Agent范式编排,可以构建出类似于FunctionCall、Planner、ReAct等服务方式,通过这些能力可以进行工具的代理调用。

4. GPT服务

LangEngine的GPT模块中提供了众多GPT服务,例如NL2SQL、NL2Query、NL2API、NL2Prompt。通过SQLDataChain实现了NL2SQL服务,底层也集成了阿里集团的TDDL技术进行数据库查询的支持。NL2Query在关系型数据库以及向量库中来生成Query语句,包括Field、Filter、Order等等常用query语法。通过APIChain来实现利用OpenAPI协议的标准规范,生成指定的API协议,并且最终完成API请求。NL2Prompt来实现Prompt生成的可控性以及优化后Prompt的效果。

5. 多智能体基础服务

LangEngine也汲取业内比较好的多智能体框架,例如MetaGPT、AutoGen、AutoGPT等优秀的开源框架,重新实现了一套Java版本的多智能体框架,利用该框架可以快速构建属于自己业务属性的多智能体应用。

阿里巴巴AgentFramework框架

框架背景

阿里LangEngine提供了LLM应用开发中所需要各种原子化的能力,从官方角度上看,在做AI业务项目或者AI平台建设实践中,大多数时候都是基于这些原子化的能力进行组装和编排,例如既需要做可生产化的RAG服务,也需要做Agent工作流,还需要做智能体相关建设。所以,为了满足普通的AI业务述求,考虑开始构建这套阿里AgentFramework框架。AgentFramework框架的目标是把LangEngine构建好的服务,再进行进一步的组装,通过工作流以及智能体应用的方式组织起来。

工作原理

阿里AgentFramework框架是基于阿里LangEngine框架之上衍生出来真正面向AI业务和平台的Agent构建框架。

✦ Core模块:AgentFramework框架最核心的模块,负责整个流程引擎调度和基础Service SPI的串联,这里你可以选择自己任何的一款工作流框架作为AgentFramework的工作流引擎集成。

✦ Model模块:Agent领域模型,包括基础服务接口。建立以Role/Knowledge/Tool为基础的BuiltInAgentModel(含Bean内置类和 HSF-SPI动态自定义扩展方式)以及FlowAgentModel模型支持。

✦ Engine模块:OpenAPI Pipeline实现,通过该模块可以完成类似于API网关全生命周期的管控,例如访问控制、限流策略、黑白名单、参数转换、参数映射、服务调用、服务打点等常见的API网关功能。

✦ Bundle模块:AgentFramework的业务实现模块。包括大模型调用、知识库检索、工具调用等等服务集成实现。

✦ Storage模块:AgentFramework持久化层。可插拔的持久化模块,可针对不同环境扩展各自持久化能力。

✦ Parse模块:工作流前端UI DSL转换到BpmnXml解析器,这里采用了开源的ReactFlow技术作为工作流UI的基座。这个模块主要解决工作流中的FlowSchema转换为可执行的文件。

服务集成

1. AI Business算法工作台的容器化部署

AgentFramework框架整体可以跟随宿主应用Docker镜像打包进Serverless容器中心镜像拉取,通过容器提供了去中心化的API Gateway对接,从而实现AI应用是容器部署隔离。另外一方面,随着应用、工具、模型整体纳入到容器化服务以后,容器内部任务调度会实时观察模型的吞吐情况以及空闲时段,自动化进行相应的资源(含GPU)的切换。

2. SDK集成与私有化部署

作为AI Business为巴黎奥运会提供的智能解说助手,通过该产品解说员可以了解奥运比赛项目、历届比赛以及运动员等相关信息,在自由问答模块中,可以通过APP Framework SDK集成方式,让应用基于已集成的通用组件、模型服务和FLow编排能力,快速构建自己的AI chat功能。

阿里巴巴LangEngine框架开源地址:
https://github.com/AIDC-AI/ali-langengine

用例是最简单的UML元素,用例图是最简单的UML图,但它也可能是UML中最有用的元素之一。尽管我们用包将工作分解为工作包、团队任务或单项任务,也就是说包是组织UML中的各种图及元素的工具。但是用例图可以帮助我们确定任务,以及应当如何将它们分组并确定工作范围。
每个用例都代表用户希望系统帮助实现的一个目的或目标。例如,对于银行ATM机,客户希望使用它来取款、存款、转账或者修改密码等,而银行则希望使用它可以获得存取款明细等。
要使系统具有实用性,它就必须为用户带来价值。例如银行ATM机,对于客户而言,可以省去或节约人工柜台排队等候的时间,对于银行而言可以降低人力资源成本。在用例的术语中,对于所有与系统产生交互的对象我们统称为“参与者(Actor)”。参与者涵盖了所有与系统产生交互的用户,例如客户、工作人员、管理人员、维护人员等。任何可以使用系统(主动或者被动)的人都可以被视为参与者。
对于图书馆系统,参与者包括读者、图书管理员、编目人员、出入库人员等;对于打车APP,参与者包含乘客、司机、管理人员、客服等;对于银行ATM,参与者包含客户、款箱管理员、运维人员等。
此外,与当前系统相关联的其他系统、设备、传感器等都可以视为参与者。
回到用例本身,用例必须为参与者提供价值。许多程序员对“用例为参与者提供价值”感到困惑,经常与函数或子程序返回一个值混淆。所谓提供价值意味着实现了某种期望的结果,它是比函数或子程序返回值更高层次的结果。例如对于银行ATM,“取款”是一个用例,它的价值在于实现了客户获得现金的目标,而为了实现这个目标,银行ATM识别卡片、客户输入密码及ATM判断密码是否正确等只是“取款”这个用例实现的步骤,它们都有返回值,但是这些并非客户的目标,它没有为客户带来价值或让客户受益,因此它们不是用例。
用例是行为模型的一部分。一个业务用例通常描述一个完整的业务,一个系统用例通常描述一个完整的系统功能。一个用例描述一个场景,它也可以表示一组具有相似目标的相关场景。通常使用结构化文本(用例规约)、故事板、序列图、状态机或活动图来详细描述这些场景。
在UML中使用椭圆表示用例,用例的名称写在椭圆内部,如下图所示。用例的名称表示参与者的目标。通常,用例名称应当使用“动宾”结构,例如存款、取款、修改密码等,而这个动宾结构的主语就是对应的参与者,即客户存款、客户取款、客户修改密码等。

关于用例命名,有一个简单实用的技巧,即在参与者目标前加上“系统,请帮我……”或类似的变体。例如:

  • 系统,请帮我取款。
  • 系统,请帮我修改密码。
  • ……

遵循上述形式获得的用例名称通常是正确的,并且很容易理解。然而有些特殊的用例不适用这种命名的方法。例如,在系统或参与者具备自主能力的场景下,一个机器人系统会自行进入休眠状态,一个时钟会自动计时,此时没有具有传统目标的传统参与者。
用例图简单易懂,几乎不需要经过专业训练就可以阅读和开发,而用例图又是描述需求的手段,故而通常它由需求分析人员与参与者的代表共同开发,或者由需求分析人员开发后,与参与者讨论修改而成。最终形成的用例图还须交给各参与者代表进行审查。参与者代表审查用例图非常有必要且有价值,因为参与者可以明确知道用例图是否包含了他们自己所有的目标,或者是否存在跟他们不相关的目标。也正是基于此,在为用例命名时应当使用参与者的术语,而避免使用IT术语或者实现时的概念,同时力求简单、明确,确保每个人都能理解。
系统开发人员倾向于围绕用例来组织项目,因为这样可以使项目更易于相关各方理解。通常一个项目应当生成需求或设计文档,可以考虑先按参与者再按用例来组织文档结构(如下图所示),同时也可以通过创建相关用例的包来构建包结构。

本文简单讨论了用例的概念及如何发现用例,关于用例与参与者更深入的概念与知识,请参阅博客下UML合集中的其他相关文章。

背景

最近半路接手了一个系统的优化需求,这个系统有个遗留问题还没解决,随着新需求的上线,系统正式开放使用,这个遗留问题也必须解决。

这个系统大概是下面这样的,支持录入各种数据源的信息(ip、端口、数据库种类、账号密码等):

image-20250104210901249

录入完成后,可以查看这些数据源中的表、表的ddl、表中的列(列名、类型及注释等),也可以查看各个表中的数据。

其中一个数据源,是sql server 2008版本,总是连接失败,更别提获取这个db中的表了。

错误堆栈如下:

image-20250104211502284

定位过程

1、前期处理

在我做新需求的时候,我之前的同事A已经处理过这个问题。这个问题只在线上出现,因为开发测试环境压根没有这么老的数据库版本,在开发测试环境申请一台windows服务器来安装一个这样的老版本数据库,也比较麻烦;所以,同事A在处理的时候,基本是网上查到修改的办法后,直接弄到线上去试试能不能解决。

之前改过两次,第一次是这样:

1、参考附件脚本《配置文件java.security》,修改/usr/local/jdk/bin/java/java.security中的配置项jdk.tls.disabledAlgorithms。
修改内容:删除jdk.tls.disabledAlgorithms配置项的“TLSv1, TLSv1.1”,替换成“DHE”

简单解释下这部分的修改,从前文中的错误堆栈来看,这个问题是和ssl有关系的,我之前猜想的就是,这个sql server和mysql一样,支持使用tls加密传输,保护数据安全;但是,可能sql server 2008版本太老了,不支持tls 1.2/1.3这些,只能使用tls1.1/tls1.0等,但是呢,jdk认为使用tls1.1/1.0不够安全,默认是禁用了的,所以,只要把这个禁用tls1.1/tls1.0的配置给改改,允许jdk使用tls1.1/tls1.0,不就可以连接sql server 2008了吗?

但是,遗憾的是,这个改动之前已经上线试过了,没有生效,还是报错。

另外,在我做新需求的时候,同事A又试了一个改动,把驱动版本升级了下,大家知道java都是使用jdbc去连接数据源的,各个厂商会实现jdbc,之前呢,使用的是sqlserver的4.0版本的驱动,这把,直接弄到了8.4.1,准备搞上去再试试:

image-20250104213141696

2、尝试修改配置禁用加密

我了解到这个情况后,因为需求也比较赶,就没想花大力气来搞这个bug,我们都是内网服务器之间调用,这个加密传输,我感觉不是必要,直接弄成不加密不就行了吗?

我找了下代码,里面有拼接jdbc url的代码:

image-20250104213746728

那直接去掉这个
encrypt=true;trustServerCertificate=true
,去掉后,本地测试了下连接sql server数据库(不是2008版本),用wireshark抓包看了下,发现客户端发给数据库(sql server常用1433端口)的报文里,还是说加密是开启的:

image-20250104214148592

行吧,我查了下,原来不指定encrypt属性,默认就是true,那我手动指定成false得了。

image-20250104214411744

又抓包看了下,这次有了变化,客户端发过去的报文是说,不使用加密:

image-20250104214503660

服务端返回报文也说,不加密:

image-20250104214556331

但是,我在后续的报文里,发现还是部分加密了的:

image-20250104214722668

这就有得难以理解了。

3、debug驱动代码

所以这时候的思路就是,看看为什么源码里还会加密传输,那只能debug了,看看是不是还有其他选项在控制这块,后面找到如下代码:

image-20250104215227828

在上图中,先是三次握手,再是prelogin(就是前文抓包看到的那部分,如:Encryption: Encryption is available but off (0)),再下来呢,有个if,如果满足这个if,就会开启SSL,此时,就会导致发出去的报文是ssl的,也就是说,只要走了这个if,我们就绕不开ssl,就规避不了这个bug。

那我们看看,怎么绕开这个if吧。

这个if中,左边是个常量,ENCRYPT_NOT_SUP表示不支持加密,
image-20250104215651186

右边是个变量,初始化的时候是:

private byte negotiatedEncryptionLevel = TDS.ENCRYPT_INVALID;

后续,什么地方会修改这个变量呢,是在prelogin部分,在处理数据库返回的prelogin响应报文时:

image-20250104215857867

这里,2812行,是直接取响应报文中的值,也就是说,以数据库服务端的为准。

还记得,服务端一般是返回如下值:0。

image-20250104220012549

那这样的话,就会导致那个if条件为true:

image-20250104220149407

这块就有点难办了,这个值是服务端返回的,除非数据库返回ENCRYPT_NOT_SUP,表示不支持加密,否则,这个加密是跑不掉了。但我没太想过要让数据库去改这个配置,毕竟这个库,说是客户端还不少,我不可能去动它,影响太大,可能到时候导致别的客户端要改造。

还有个方向,是通过客户端的传参,去影响服务端的返回值,比如客户端传一个不支持加密,看看服务端的返回值。

但,比较遗憾的是,客户端驱动定死了,只能传下面这两个值,要么ENCRYPT_ON,要么ENCRYPT_OFF:

image-20250104221111123

4、覆盖驱动源码,强行绕过enableSSL方法

当时,我的想法是,把这个if条件改一下,改成:

if (TDS.ENCRYPT_ON == negotiatedEncryptionLevel){
    tdsChannel.enableSSL(serverInfo.getServerName(), serverInfo.getPortNumber());
}

其实,按我这会的想法,改下面这个地方也不错,想办法传ENCRYPT_NOT_SUP给服务端:

requestedEncryptionLevel = isBooleanPropertyOn(sPropKey, sPropValue) ? TDS.ENCRYPT_ON : TDS.ENCRYPT_OFF;

如何才能修改驱动包的代码呢,改是改不了,但是可以想办法覆盖,方法就是在项目中建同包名同类名的java文件(内容直接从源码文件拷贝),然后修改其中的部分代码即可。

但这次有点意思的是,遇到个以前没见过的问题,报如下错误:

java.lang.SecurityException: signer information does not match signer information

最终在网上查了下,(
https://blog.csdn.net/weixin_44070655/article/details/129922513),错误的意思是,我们新建的java文件的一些签名信息不太匹配,这块没细看。最终是需要删除jar包中的如下两个文件:

image-20250104223152701

删除的方式,可以直接用压缩软件打开,删除里面的这两个文件即可,另存为即可。然后把改后的jar包发布到私服(可以修改下坐标),或者是使用maven的如下方式:

image-20250104223331747

最终成功绕过enableSSL了,抓包发现,客户端确实没对包进行加密了,但是,服务端不返回任何报文了。我理解的是,服务端当初在进行prelogin协商时,返回的加密选项是:ENCRYPT_OFF,这个按正常流程,后续就是需要加密的,我们现在强行改了客户端源码,导致服务端陷入了迷思:wtf,客户端怎么回事,怎么没加密,这个客户端有问题?行吧,我不返回了。

最终,我还是放弃了这条路。因为,我上网查了下这个encrypt选项。

https://learn.microsoft.com/zh-cn/sql/connect/jdbc/understanding-ssl-support?view=sql-server-ver16

image-20250104224440595

原来,加密分为两个部分,第一个部分是登录部分,建立连接时,会传输用户名密码,我当时发现:在我上面强行改了客户端驱动,收不到服务端响应时,进行了抓包,发现我可以看到明文密码,当时我也有点惊讶,也反应过来了,难怪要弄ssl加密呢;第二个部分是,后续的数据的加密,如传输的sql语句和执行结果。

当encrypt为true时,两个部分都会加密;而当encrypt为false时,登录部分还是会加密,而数据部分不会加密。

所以,不管怎么说,登录部分总是要加密的,所以我还是不要挑战这条路了,毕竟这是协议规定好了的。

5、增加ssl日志

最终,把修改源码部分,全都回退了。最终的jdbc url选项如下,驱动版本也保留着

;encrypt=false;trustServerCertificate=true;
<dependency>
    <groupId>com.microsoft.sqlserver</groupId>
    <artifactId>mssql-jdbc</artifactId>
    <version>8.4.1.jre8</version>
</dependency>

说白了,ssl问题依然会有(毕竟encrypt=false,登录部分还是要走ssl),但是,我们可以想办法把ssl过程中的日志打印出来:

System.setProperty("javax.net.debug","ssl:handshake:verbose");

image-20250104225440075

这个呢,会打印ssl过程中的细节(注意,是打印到标准输出的,日志文件里没有,要看看启动java进程时,把标准输出重定向到哪里去了,不能是 > /dev/null这种),类似下面这种,到时候我们上线了再看看日志情况吧:

image-20250104225659003

6、上线后检查ssl日志

这个问题,现在说白了,就是客户端发了ssl握手消息给服务端,正常来说,服务端是要响应的,像下面这样,返回server hello这个报文,其中包含选定的ssl加密套件、服务端证书链等信息:

image-20250105080602411

然后我预期的是,上线后,这个ssl日志能把服务端报错的原因打印出来,结果并没有。

直接就是说,服务端关闭了连接,终止握手:

image-20250105080917662

从后来我找运维抓的包也能看出来,服务端发了tcp关闭的报文:

image-20250105081152690

7、尝试更换客户端驱动版本

此时,有点陷入僵局了。客户端没日志,网络报文也看不出来,那意思是只能看看服务端有没有日志了吧。

然后去找了dba,我现场演示了下,他看了数据库端的日志文件:啥都没有。

他给了我两个方案,一个是这个库太老,后续会复制一个新库出来,这个要等;再一个是,这个库也有其他的项目在用,也是java客户端的,他说帮我问下相关同事。

然后后续我单独加了那个同事,了解了下,他们用的驱动版本是7.4.1,我们目前线上是8.4.1:

<dependency>
    <groupId>com.microsoft.sqlserver</groupId>
    <artifactId>mssql-jdbc</artifactId>
    <version>7.4.1.jre8</version>
</dependency>

然后,jdbc的url是这样:

image-20250105082000028

最终我们的方向是,换不同版本的驱动看一下,先试上面这个7.4.1.jre8版本。因为之前我也查到过资料,就是xx版本的数据库,需要xx版本的驱动。

https://learn.microsoft.com/zh-cn/sql/connect/jdbc/microsoft-jdbc-driver-for-sql-server-support-matrix?view=sql-server-ver16

image-20250105082313608

从图里能看出来,sql server 2008,需要7.2版本的驱动。我们之前的8.4.1,肯定是高了;其实看上图,7.4也高了,但不知道人家项目为啥能行,就也试试呗。

8、开发测试驱动版本工具类

写了个类来测试:

image-20250105082729476

执行方式就是把jar和class放到同一目录下执行:

[root@news-center-app ~]# ll DbConnectTest.class mssql-jdbc-7.4.1.jre8.jar 
-rw-rw-rw- 1 root root    1631 Dec 30 15:30 DbConnectTest.class
-rw-rw-rw- 1 root root 1209660 Dec 30 15:16 mssql-jdbc-7.4.1.jre8.jar
[root@news-center-app ~]#  java -classpath .:./mssql-jdbc-7.4.1.jre8.jar DbConnectTest "jdbc:sqlserver://1.1.1.1:1433;databaseName=xxx;encrypt=false;trustServerCertificate=true" zhangsan xxx

这样呢,方便我们替换驱动的jar包。

结果呢,运维说必须走流程才能这么玩,理由就是不能在生产上做测试,battle了半天,后面还是提流程了(正好有个小需求又要上,就把这个工具一起弄上去了)。

上线的时候,我们顺便就把之前的驱动版本从8.4.1.jre8改成了7.4.1.jre8,也包括这个小工具。

上线后,以为这次肯定能行,结果,还是报一样的错误,此时正值周五快下班的时候,我无语了:就不能早点解决了这个bug,好好过个周末,不然还牵挂着它。

结果我回家路上,运维在群里说,bug可以了,他上网找了下文章:

https://blog.csdn.net/zhujun300/article/details/141434867

还是修改jdk的java.security文件,这次又把另一个被禁用的给去掉了:3DES_EDE_CBC. (我在开头说,同事A之前就改过一次,但是去的是tls1.1/tls1.0,没去这个3DES_EDE_CBC)。

然后,就好了。

行吧,还是能好好过周末的。

9、为什么去掉3DES_EDE_CBC能好

网上翻了很多资料,没找到讲这块原理的。我自己本地试验了下,在去掉这个3DES_EDE_CBC前,记录了打印的ssl日志;在去掉后,又记录了下。

对比如下,可以看到,去掉后,握手消息中多了很多加密套件(其中都包含了3DES_EDE_CBC这个加密算法):

image-20250105084822878

那这样的话,我们可以认为,线上那个库,应该是不支持客户端发送出去的所有加密套件,才把ssl握手终止了。

而加上3DES_EDE_CBC后,多了一些加密套件,而这些套件,正好服务端就支持,所以就可以了。

当然,具体选择了哪个加密套件,可能得下周上班了再找运维看看日志或者抓个包瞧瞧才知道。

这次呢,我也学会了一个新技能,由于ssl握手消息是封装在其他协议(TDS)里面的,在wireshark中都没法看。

image-20250105085534815

上面蓝色部分就是握手消息,但看不了,要知道握手的具体细节,非得看ssl日志才行,这个让人有点不爽。

我上网找了下,还真找到个网站:

https://williamlieurance.com/tls-handshake-parser/

只要把十六进制流复制进去,就能解析ssl。

image-20250105085747745

image-20250105085817985

对我来说,算是不小的一个收获。

10、怎么查看sql server 2008支持的加密套件

一开始,对这块不理解,以为ssl加密相关能力是sql server 2008这个软件提供的(对windows服务器太不了解了),但后来查了些资料发现,ssl加密相关能力是操作系统提供的;像是linux呢,一般就是安装了openssl,其他软件都是复用openssl的能力。

而sql server 2008,当时查了下版本:

Microsoft SQL Server 2008 (SP1) - 10.0.2531.0 (X64)   Mar 29 2009 10:11:52   Copyright (c) 1988-2008 Microsoft Corporation  Enterprise Edition (64-bit) on Windows NT 5.2 <X64> (Build 3790: Service Pack 2) (VM) 

没细问windows服务器的版本,但我们从上述也能看出来:

windows服务器版本其实就是:Windows NT 5.2
(Build 3790: Service Pack 2)

这个服务器,搜索了下,其实是:win server 2003版本。

image-20250105100603157

难怪了,这个操作系统版本太低了,估计就是很多ssl套件都不支持。

那么,我们如何查看一个windows电脑,支持哪些加密套件呢?

有以下几种方式:

10.1 通过组策略管理器查看

  1. 按下 “Win + R” 键,输入 “gpedit.msc” 并回车,打开组策略编辑器2。
  2. 依次展开 “计算机配置”→“策略”→“管理模板”→“网络”→“SSL 配置设置”2。
  3. 双击右侧的 “SSL 密码套件顺序”,选择 “已启用”,在左下侧可以看到支持的 SSL 加密套件

10.2 通过命令查看

使用 PowerShell:以管理员身份运行 PowerShell,输入Get-TlsCipherSuite命令,可列出当前系统上配置的 TLS/SSL 加密套件以及它们的启用状态等信息。

image-20250105095527943

10.3 IISCrypto

这边下载了一个软件:
https://www.nartac.com/Products/IISCrypto/Download

可以大概看到ssl中涉及的几个部分:传输协议、加密算法、hash算法、秘钥交换算法。

image-20250105095223507

通过上述几个部分,组成了各种各样的ssl套件:

image-20250105095317399

官方参考链接

https://learn.microsoft.com/en-us/windows/win32/secauthn/schannel-cipher-suites-in-windows-vista

其他尝试过的定位手段

1、本地安装sql server 2008

我猜测这个问题应该是比较好复现的,只是苦于没有环境,然后就想着在本机装一个,没想到,就这也踩了好久的坑。

安装sql server 2008,依赖.net framework 3.5这个运行环境,我是win10系统。网上的方法分两类,在线安装和离线安装,整个.net framework 3.5包有100多m,在线安装,我这边反正不行,不只是网络的问题,好像在线安装是需要一个service正常运行才可以:windows update。

相信很多人,当初为了禁用windows的升级,可能把这个service都删掉了。

可能也正是因为这样,我甚至离线安装也是失败的。

我也附上几个链接吧,万一大家可以呢:

https://mp.weixin.qq.com/s/y0sZz8wtLtcPQM0sI8DHmQ

https://mp.weixin.qq.com/s/_unsXuBLH1JjtsprKYjSWw

https://mp.weixin.qq.com/s/4FtoTMF3L_hDAXGu_GgOdA

试这些的时候,也可以先看下官方帮助文档:

https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/enable-or-disable-windows-features-using-dism?view=windows-11

C:\WINDOWS\system32>dism /online /?
dism /online /Enable-Feature /?
dism /online /Add-Package /?

最终,我用上述这些方法也没成功,后来是按照如下文章来解决的:

https://blog.csdn.net/Roeluo/article/details/144692042

当然,这个.net framework 3.5装上了,并不影响我的sql server 2008安装失败,当然,现在bug都解决了,有空再弄吧。

参考链接

https://blog.csdn.net/wpf416533938/article/details/128573683

https://blog.csdn.net/tanhongwei1994/article/details/84957254

https://learn.microsoft.com/zh-cn/archive/blogs/jdbcteam/the-driver-could-not-establish-a-secure-connection-to-sql-server-by-using-secure-sockets-layer-ssl-encryption

https://stackoverflow.com/questions/32766114/sql-server-jdbc-error-on-java-8-the-driver-could-not-establish-a-secure-connect

https://stackoverflow.com/questions/79113822/java-1-8-sql-server-2008-r2-unable-to-run-query-when-encryption-is-activated

https://www.reddit.com/r/sysadmin/comments/u6grqv/very_legacy_ssl_problem_on_server_2003yep_it/