用Go syscall读取网卡路由与网关实现最长前缀匹配选路

碰到一个很实际的需求:设备上插了多块网卡,程序需要知道每块网卡都配了什么IP、谁是对外默认网关,并且在访问某个目标IP时,能自己决定走哪条出口。Go语言里第一反应是翻net包,但翻完会发现它只管给你 IP 和掩码,路由表和网关一概不提供。真正能拿到完整信息的地方,只有syscall这一层直接和内核交互。

这篇文章我用自己的实现思路聊一遍:在Linux下用Go的syscall读取本机网卡IP、网关,并实现一段按目标IP做最长前缀匹配的选路逻辑。内容面向的是需要做多网卡路由策略、边缘网关设备选路、局域网资源探测这类场景的程序员,看完可以直接把方案搬进自己的工程里。

1. 为什么非要用syscall:标准库的边界与痛点

1.1 net包能给你什么,不能给你什么

Go标准库的net包提供了比较方便的接口枚举能力,net.Interfaces()可以列出所有网卡,net.InterfaceAddrs()可以取到每块网卡的地址列表,net.InterfaceByName("eth0")可以按名字单独查。桌面开发、简单运维脚本基本用这些就够了。

但它的能力边界很明显:它拿不到路由表,拿不到每张路由表项对应的网关,也拿不到内核视角下“访问某个目标IP到底该走哪块网卡”的决策结果。你拿着net.Interface的结构体看半天,里面只有索引、名字、MTU、硬件地址和标记位,没有路由信息。

这还不是最难受的。更麻烦的是在多网卡场景下,默认路由可能指向任意一块网卡,也可能不存在。程序想判断“目标IP是内网还是外网”“该走有线还是无线”“该走专线还是走备份链路”,只靠标准库答案永远是零。

1.2 syscall在这里补上哪块拼图

syscall翻译成人话,就是直接向操作系统内核发起调用,绕开标准库的封装。在Linux上,获取网关和路由表最稳的途径是读/proc/net/route,这个文件由内核实时生成,内容就是系统当前的路由表快照。获取网卡细节则可以用ioctl发给内核,比如SIOCGIFADDR拿IP地址、SIOCGIFNETMASK拿子网掩码、SIOCGIFHWADDR拿MAC地址。

用Go的syscall做这些事,本质就是在标准库的舒适区之外,接管一部分“问内核要情报”的工作。这件事值得做,是因为它把选路的主动权完全拿到应用层:网关设备里的程序,往往有比内核默认策略更复杂的分流需求,比如指定某条业务流量必须走特定出口,而其他流量走默认路由。

1.3 方案选型:什么时候需要这套东西

先说清楚,不是所有项目都需要这么折腾。如果你只是临时查一下本机IP,ip addr、ifconfig命令足够。但下面几种情况,syscall方案几乎是绕不开的:

  • 需要无外部依赖地获取默认网关,程序要部署在裁剪版Linux或嵌入式环境中,系统里不一定有ip命令。
  • 需要在进程内做多条链路的健康探测和自动切换,读取路由表是为了拿到“当前到底哪个是默认出口”。
  • 需要根据目标IP前缀把请求分到不同网卡,比如访问A网段走有线口,访问B网段走无线口,且不想依赖内核策略路由。

总结一下:标准库负责“看见网卡”,syscall负责“看见路由和网关”,二者配合才构成完整选路能力。

注意:以下代码面向Linux实现,目标架构按amd64和arm64的常规路径编写。Windows和macOS的系统调用接口不一样,后半部分单独说差异。

2. 环境准备与底层数据结构

2.1 先弄清楚/proc/net/route里到底是什么

先花点时间把信息源搞明白,后面代码才不会写歪。/proc/net/route是内核输出的路由表文本,每一行是一条路由,第一行是表头。看一个典型输出:

Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT eth0 0000A8C0 00000000 0001 0 0 100 00FFFFFF 1500 0 0 eth0 00000000 0100A8C0 0003 0 0 100 00000000 1500 0 0

逐列解读:

  • Iface:出接口名,比如eth0、wlan0。
  • Destination:目标网段,十六进制小端表示。比如0000A8C0翻转字节顺序后是C0 A8 00 00,也就是192.168.0.0。
  • Gateway:下一跳网关,同样是十六进制小端。0100A8C0翻转后是C0 A8 00 01,即192.168.0.1。
  • Flags:路由标志位,0001表示UP,0003表示UP且GATEWAY,0002表示GATEWAY。重点记住掩码。
  • Metric:路由优先级,数值小的优先。
  • Mask:网段掩码,也是小端十六进制。00FFFFFF翻转后是255.255.255.0。

专业提示:这个文件里所有的IP都是小端存储的,直接拿来和net.IP比较会出大问题。我在第一次写解析器时就在这里踩了坑,后面单独讲。

2.2 ioctl与网络接口相关的常量和结构体

获取单个网卡信息时,syscall里最常配合的是ioctl函数。对网卡发起SIOCGIFADDR(拿IPv4)、SIOCGIFNETMASK(拿掩码)、SIOCGIFHWADDR(拿MAC),内核会通过一个struct ifreq结构把数据写回来,Go里就需要自己定义对应的结构体。

Linux下的ifreq通常是40字节:前16字节是接口名,后面24字节是数据区。好的地方在于,接口名的16字节是固定偏移的,数据区我们只需要关心的那几段,不必完整复刻C语言里的union。

定义完结构体之后,还需要把常量写出来。Linux下的值比较固定:

  • SIOCGIFADDR = 0x8915
  • SIOCGIFNETMASK = 0x891B
  • SIOCGIFHWADDR = 0x8927

这些常量可以从系统头文件里查到,Go的syscall包没有直接暴露,需要自己声明。使用的时候,配合syscall.Socket打开一个AF_INET的UDP套接字作为ioctl的句柄,然后发起调用。

2.3 指针操作与小端转换的先决知识

用syscall不可避免要接触unsafe.Pointer。ioctl的调用签名里需要传入uintptr(unsafe.Pointer(&ifr)),也就是说把ifreq结构体的内存地址传给内核,内核通过这个指针写回数据。Go的内存管理在这个环节是“睁一只眼闭一只眼”的,所以调用前要保证ifreq对象是局部栈变量,且调用过程中不会被GC顺手搬走。

小端转换是另一个必须提前掌握的技能。/proc/net/route给出的十六进制看起来是一串整数,实际含义是“按内存里的字节顺序倒着读”。最稳妥的转换方式是用位运算逐字节拆出来,而不要依赖binary.LittleEndian以外的方式。后面代码里你会看到,我写了一个hexToIP,它做的事就是把一个uint32拆成四个字节,按“低字节在前”的规则还原成点分十进制。

有了这些前置知识,接下来就可以写实际的代码了。

3. 获取本机网卡IP与网卡列表的完整实现

3.1 用标准库遍历接口

获取网卡列表这部分,我选择先用标准库拿到所有接口,再用syscall补充细节。这样最稳,因为接口的枚举逻辑标准库已经很成熟,不必从零解析/sys/class/net。

func listInterfaces() ([]net.Interface, error) { ifaces, err := net.Interfaces() if err != nil { return nil, err } return ifaces, nil }

net.Interfaces()返回的每个net.Interface里面已经有Index、Name、MTU、HardwareAddr、Flags这些字段,多网卡场景下通常足够区分物理网卡和虚拟接口。判断一块网卡是不是“活着且能用的”,标准做法是检查Flags&net.FlagUp != 0以及Flags&net.FlagLoopback == 0。

拿到接口名之后,调用iface.Addrs()理论上可以拿到IP,但有些环境里返回的地址列表可能包含IPv6、链路本地地址等,我一般会把IPv4单独过滤出来:

func getIPv4ByIface(iface net.Interface) (net.IP, net.IPMask, error) { addrs, err := iface.Addrs() if err != nil { return nil, nil, err } for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet) if !ok { continue } ip4 := ipNet.IP.To4() if ip4 == nil { continue } return ip4, ipNet.Mask, nil } return nil, nil, fmt.Errorf("no ipv4 address on %s", iface.Name) }

这里To4()很关键。IPv4-mapped IPv6地址长得很像IPv4,但直接拿来做路由选路可能会带出多余的字节,调用To4()能把真正的4字节IP取出来,后续所有匹配逻辑才干净。

3.2 用ioctl补齐硬件信息

标准库已经能拿IP和掩码,为什么还要单独写一个用ioctl的函数?我给出的理由是:标准库拿不到MAC地址偏移量稳定的接口信息;而且既然本篇文章的主题是syscall,就一并演示如何通过SIOCGIFHWADDR补全网卡的物理地址。

const ( SIOCGIFHWADDR = 0x8927 ) type ifreq struct { Name [16]byte Data [24]byte } func getHardwareAddr(name string) (net.HardwareAddr, error) { fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0) if err != nil { return nil, err } defer syscall.Close(fd) var ifr ifreq copy(ifr.Name[:], name) _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), SIOCGIFHWADDR, uintptr(unsafe.Pointer(&ifr))) if errno != 0 { return nil, errno } // 在Linux的ifreq中,MAC地址位于Data[0:6] hw := make(net.HardwareAddr, 6) copy(hw, ifr.Data[:6]) return hw, nil }

值得留意的是syscall.Syscall的返回值。第三个返回值是errno,只有它不为0时才说明调用出错,前两个返回值在ioctl场景下基本用不上。很多初学者在这里只判断第一个返回值,结果在某些系统上拿到一个正常返回的错误码,容易误导排查方向。

3.3 完整枚举函数

把上面的片段组合成一个能输出所有网卡基本信息的函数:

type InterfaceInfo struct { Name string IP net.IP Mask net.IPMask Gateway net.IP MAC net.HardwareAddr Up bool } func gatherInterfaces() ([]InterfaceInfo, error) { ifaces, err := net.Interfaces() if err != nil { return nil, err } var result []InterfaceInfo for _, iface := range ifaces { info := InterfaceInfo{ Name: iface.Name, Up: iface.Flags&net.FlagUp != 0, MAC: iface.HardwareAddr, } ip, mask, err := getIPv4ByIface(iface) if err == nil { info.IP = ip info.Mask = mask } result = append(result, info) } return result, nil }

实际项目里,这个函数返回的数据可以用来做后续的“网关归属”判断。典型的例子是:默认路由的出口是eth1,但eth1却已经Down了,此时程序就应该放弃默认路由,转向其他仍然存活且有默认路由的接口。这类逻辑完全依赖上面的接口状态信息。

4. 读取路由表和默认网关的完整实现

4.1 /proc/net/route逐字段解析

路由表的解析是整篇文章的核心之一。我设计了一个结构体保存一条路由的完整信息:

type RouteEntry struct { Interface string Destination net.IP Gateway net.IP Mask net.IPMask Flags uint16 Metric int }

解析时按行拆分,跳过表头:字段之间用空白分隔。特别要强调,每一行必须至少11个字段,不能只取前4个就完事,因为Mask和Metric都在后面。

func parseProcNetRoute() ([]RouteEntry, error) { data, err := os.ReadFile("/proc/net/route") if err != nil { return nil, err } lines := strings.Split(strings.TrimSpace(string(data)), "\n") if len(lines) < 2 { return nil, fmt.Errorf("empty route table") } var routes []RouteEntry for _, line := range lines[1:] { fields := strings.Fields(line) if len(fields) < 11 { continue } dest := hexToIP(fields[1]) gw := hexToIP(fields[2]) mask := hexToMask(fields[7]) flags, _ := strconv.ParseUint(fields[3], 16, 32) metric, _ := strconv.Atoi(fields[6]) routes = append(routes, RouteEntry{ Interface: fields[0], Destination: dest, Gateway: gw, Mask: mask, Flags: uint16(flags), Metric: metric, }) } return routes, nil }

这段代码里隐藏着一个容易忽略的规则:Destination的值是网段起始地址,Mask是它对应的掩码。判断一条路由是否匹配某个目标IP,不能只比较目标IP等于Destination,而是要“目标IP与掩码相与”再和“Destination与掩码相与”比较。

4.2 十六进制字符串转IP和掩码

小端转换写起来看着简单,却是最容易出错的地方。我给出一个可以放进工具库的版本:

func hexToIP(hex string) net.IP { n, err := strconv.ParseUint(hex, 16, 32) if err != nil { return nil } return net.IPv4(byte(n), byte(n>>8), byte(n>>16), byte(n>>24)) } func hexToMask(hex string) net.IPMask { n, err := strconv.ParseUint(hex, 16, 32) if err != nil { return nil } return net.IPv4Mask(byte(n), byte(n>>8), byte(n>>16), byte(n>>24)) }

解释一下为什么这样转换是正确的:内核在输出路由表时,把32位的IP按主机字节序打包成一个整数,而这个整数的内存布局在小端机器上是“低位字节在前”。byte(n)取的是最低8位,恰好是网络序里的第一个字节。四句组合起来,就完成了从内核格式到net.IP的还原。

同样,hexToMask返回net.IPMask,后续做最长前缀匹配时可以直接调用Mask.Size()拿到前缀长度,不需要自己数掩码里有几个1。这是Go标准库给我们省下的功夫。

4.3 从路由表里挑默认网关

有了完整的路由表,挑默认网关就很简单了。默认网关在路由表里通常表现为目标地址0.0.0.0、掩码0.0.0.0、且Flags里带有GATEWAY标记的一行。

func findDefaultGateway(routes []RouteEntry) *RouteEntry { for i := range routes { if r := &routes[i]; r.Gateway != nil && r.Destination.Equal(net.IPv4zero) && r.Mask.String() == "ffffffff" { return r } } return nil }

注意这里Mask的判断:0.0.0.0/0的掩码在net.IPMask里是4个零字节,但net.IPMask.String()会输出ffffffff?实际上net.IPv4Mask(0,0,0,0).String()输出ffffffff是掩码的十六进制字符串表示,零掩码应该输出00000000。更严谨的写法是直接比较掩码长度:

func isDefaultRoute(r RouteEntry) bool { ones, bits := r.Mask.Size() return bits == 32 && ones == 0 && r.Destination.Equal(net.IPv4zero) }

这点很关键:Mask.Size()返回两个值,ones是前缀中1的个数,bits是总位数。默认路由的前缀长度是0,也就是ones == 0。判断目标地址是否全零,用net.IPv4zero会更直观。

实际上,绝大多数情况下判断默认网关并不需要那么严格。只要掩码全0且目标是全0,不管Gateway是不是0.0.0.0,这一行都可以作为兜底出口。有些直连路由的Gateway是0.0.0.0,但真正的默认路由必然带一个非零网关。

4.4 为什么不用exec调用ip route命令

我遇到不少同学在实现网关获取时第一反应是exec.Command("ip", "route")。这个思路的优点是简单,缺点是脆弱。

ip命令不是所有系统都自带,裁剪版Linux和嵌入式设备上尤其如此;每次执行都要fork一个子进程,在高频探测场景下开销可观;输出格式随ip版本和系统配置略有差异,解析时容易出边界问题。相比之下,读/proc/net/route是内核直接暴露的文件,格式稳定、无额外进程开销、权限要求低,是更符合“程序化”的做法。

顺便说一句,如果你看内核文档,会发现/proc/net/route只支持IPv4路由。IPv6路由在/proc/net/ipv6_route里,格式完全不同,本文不展开,但知道有这么个边界很重要。

5. 按目标IP做路由选择:最长前缀匹配实战

5.1 核心逻辑与为什么是最长前缀

目标IP选路的原理可以这样理解:路由表里每条规则都是一个“网段范围”,比如192.168.1.0/24和192.168.1.128/25都包含了目标IP 192.168.1.200,但后者范围更小、更精确,内核会优先走后者。这种“范围越小越优先”的规则,在网络里就叫最长前缀匹配。

生活里类比一下快递分拣:送某个小区门口的快递,系统先查“这个城市有没有?!”全城都能送,但送到小区门口这层更具体,所以优先按更具体的地址派送。路由表也是一样,默认路由是城市级别兜底,具体网段是街道级别优先。

代码实现我采用线性扫描。对绝大多数设备,路由表条目数量是几十到几百,线性扫描的性能完全够用。如果真的遇到几万条BGP全量路由表的场景,再考虑用基数树之类的结构。

5.2 完整实现代码

func selectRoute(routes []RouteEntry, target net.IP) (RouteEntry, net.IP) { var best *RouteEntry bestPrefixLen := -1 target4 := target.To4() if target4 == nil { return RouteEntry{}, nil } for i := range routes { r := &routes[i] // 只考虑UP且GATEWAY或直连的路由 if r.Destination == nil || r.Mask == nil { continue } ones, bits := r.Mask.Size() if bits != 32 { continue } if !target4.Mask(r.Mask).Equal(r.Destination.To4()) { continue } if ones > bestPrefixLen { best = r bestPrefixLen = ones } } if best == nil { return RouteEntry{}, nil } return *best, best.Gateway }

这个函数输入是解析好的路由表和目标IP,输出是命中的路由项以及网关。注意我的判断条件包含bits != 32,这是为了过滤掉IPv6掩码,避免下面对32位IP做掩码操作时产生类型混乱。另外target4.Mask(r.Mask)返回的是一个IP,参与比较时用net.IP.Equal方法,不能直接==。

5.3 将接口信息与选路结果合并

选路最终不但要返回网关,还要告诉上层“从哪块网卡出去”。这一步把接口IP和路由表的Interface字段合并起来即可:

func ResolveRoute(target net.IP) (ifaceName string, localIP net.IP, gateway net.IP, err error) { routes, err := parseProcNetRoute() if err != nil { return "", nil, nil, err } route, gw := selectRoute(routes, target) if route.Interface != "" { ifaces, _ := net.Interfaces() for _, iface := range ifaces { if iface.Name == route.Interface { ip, _, err := getIPv4ByIface(iface) if err == nil { return route.Interface, ip, gw, nil } } } } return "", nil, nil, fmt.Errorf("no route to %s", target) }

这里有三个返回值:出接口名、本机在该接口上的源IP、下一跳网关。很多网络编程场景需要“绑定源IP发起连接”,这三样恰好就是关键参数。

5.4 边界情况:多网卡出口决策

多网卡场景下,最长前缀匹配帮你决定的是“匹配哪条路由”,但哪块网卡做出口,还跟路由的Flags有关:

  • 命中一条带GATEWAY的路由,说明目标网段不是直连的,要经过网关转发,源IP应使用接口的IP。
  • 命中一条没有GATEWAY的路由,说明目标网段和本机直连,通常意味着目标和你在同一个二层网络,网关字段会是0.0.0.0。
  • 一条路由都没有命中,理论上应该走默认路由,默认路由就是0.0.0.0/0,前缀长度为0,所以匹配兜底依然有效。
  • 默认路由也不存在,程序只能返回错误,这时候连本机自己都访问不了外网,没法强行造路。

值得注意的是,多网卡设备上经常出现“默认路由在eth0,但eth0的链路已经断了”的情况。解析出的路由表里可能会残留这条不健康的默认路由。判断接口是否Up,可以在合并阶段做二次校验:如果命中项的接口不是Up状态,就退而求其次选其他前缀稍短但仍然匹配的路由。这个策略我建议按业务场景决定,不能一概而论。

6. 常见问题与排查心得

6.1 /proc/net/route读出来是空的

排查顺序按经验从高到低:容器环境里通常看不到宿主机的路由表,读到的是容器自己的network namespace,里面可能只有一条默认路由或干脆为空;权限不足时文件也可能读不了,但实际出现概率较低;系统/proc没有正确挂载时也会出类似问题。

核实的方法很简单,先在命令行执行cat /proc/net/route,看看有没有内容。如果命令行有、程序里没有,大概率是路径拼错或者权限问题。如果命令行里也没有,就要检查程序是否运行在容器或者无特权模式下。

6.2 拿到的是IPv6或者链路本地地址

很多同学的初版程序会把fe80::...这种链路本地IPv6地址当成网卡IP,导致后续连接失败。过滤标准有三条:必须是IPv4(To4() != nil);不能是链路本地地址(ip.IsLinkLocalUnicast()为false);不能是回环地址(ip.IsLoopback()为false)。

另外,DHCP客户端未完成时,接口可能短暂持有169.254.x.x这种APIPA地址,这种地址通常在选路时要跳过。判断方法是ip.IsLinkLocalUnicast()在IPv4下会返回true。

6.3 大端小端问题导致路由匹配错误

这是我在实际项目中踩过最深的一道坑。第一次解析/proc/net/route时,我以为十六进制串直接和点分十进制的十六进制表示一致就行了,结果用192.168.0.0去匹配路由,永远匹配不上。原因就是文件里的小端表示需要翻转字节顺序。

排查技巧:先写一个单元测试,解析0000A8C0,断言它等于192.168.0.0;解析00FFFFFF,断言它等于255.255.255.0。只要这两个断言过了,基本可以认定转换函数没有错。

6.4 跨平台差异一瞥

这套实现默认Linux。Windows没有/proc,获取路由表通常要靠GetIpForwardTable或GetBestRoute,对应的Go封装在golang.org/x/sys/windows里;macOS基于BSD,可以用sysctl读路由信息,但数据格式跟Linux完全不同。

如果你需要一个跨平台方案,建议把接口枚举、路由读取、选路决策三个模块抽象成接口,然后为每个平台单独实现。核心的最长前缀匹配逻辑可以共用。这个抽象很值得做,因为无论底层怎么拿数据,选路算法本身是平台无关的。

最后再分享一个实测体会:程序在普通笔记本上编译运行一切正常,不代表在你的网关设备上也正常。嵌入式Linux经常没有/proc/net/route的标准结构,或者接口命名完全不是你想象的eth0/wlan0;路由器上往往还有多条策略路由和规则表,/proc/net/route只显示main表,看不到策略路由的分流效果。所以,真正上线前,一定要把程序放到目标设备上,手工构造一个多网卡场景跑一遍验证。检查项目是否要兼容IPv6、是否要用策略路由,如果答案都是“是”,那这篇文章的代码只能作为第一版起点,还需要结合平台能力继续扩展。

相关新闻

振动信号转灰度图+LBP+CNN故障诊断:完整代码与对比实验

振动信号转灰度图+LBP+CNN故障诊断:完整代码与对比实验

做故障诊断这几年&#xff0c;我越来越觉得一维振动信号直接扔给模型是个挺亏的事。波形虽然信息完整&#xff0c;但人看着费劲&#xff0c;模型学起来也容易把噪声和故障混在一起。我最近完成的一个小项目&#xff0c;思路很直接&#xff1a;先把一维振动信号变成二维灰度图&a…

2026/10/20 14:43:57 阅读更多 →
Python桌面宠物项目拆解:Tkinter透明窗口与帧动画实现

Python桌面宠物项目拆解:Tkinter透明窗口与帧动画实现

简介&#xff1a;一款基于Python 3.7与PyQt5的桌面宠物程序&#xff0c;面向对桌面美化或Python GUI应用开发感兴趣的初学者与进阶学习者。程序默认每10秒自动播放一个动作&#xff0c;鼠标双击左键可激活行走模式&#xff0c;再次单击取消&#xff0c;右键则提供简洁的系统功能…

2026/10/20 14:43:57 阅读更多 →
Python桌面宠物开发:tkinter实现透明无边框动画窗口

Python桌面宠物开发:tkinter实现透明无边框动画窗口

简介&#xff1a;桌面宠物Python实现项目&#xff0c;基于Python 3.7与PyQt5开发&#xff0c;适合希望学习桌面应用开发、GUI交互或宠物动画机制的初中级开发者。程序内置多套动作帧&#xff0c;默认每10秒播放一个动作&#xff0c;支持双击左键激活行走、右键呼出系统功能&…

2026/10/20 14:43:57 阅读更多 →

最新新闻

大模型入门必读:从深度学习原理到工程实战的系统书单

大模型入门必读:从深度学习原理到工程实战的系统书单

最近这半年&#xff0c;总有人在后台问我&#xff1a;“我想入门大模型&#xff0c;该看什么书&#xff1f;”老实说&#xff0c;每次被问到这个问题&#xff0c;我都挺为难的。现在网上关于大模型的书单五花八门&#xff0c;有讲算法的、有讲应用的、有讲产业趋势的&#xff0…

2026/10/20 17:54:48 阅读更多 →
拆解DeepSeek Agent框架:插件化架构的设计与实战踩坑记录

拆解DeepSeek Agent框架:插件化架构的设计与实战踩坑记录

我一直有个习惯&#xff1a;拿到一个开源项目&#xff0c;先不看 README 也不看官方博客&#xff0c;直接把仓库 clone 到本地&#xff0c;先翻目录结构、再读接口定义&#xff0c;最后才回去对照文档验证自己的猜想。这次拆 DeepSeek 开源的 Agent 框架&#xff0c;起初只是好…

2026/10/20 17:54:48 阅读更多 →
AI助理代登录的权限设计:Poppy草案的四层授权关卡与落地实践

AI助理代登录的权限设计:Poppy草案的四层授权关卡与落地实践

你有没有想过&#xff0c;有一天你的AI助理会替你登录各种账号&#xff1f;不是那种简单的“帮我查一下天气”&#xff0c;而是真的拿着你的身份&#xff0c;去另一个平台做事情——比如帮你把昨天的报销单提交到财务系统&#xff0c;帮你把一个长邮件回复整理好并发送出去&…

2026/10/20 17:54:48 阅读更多 →
腾讯云AI语音识别:自动生成会议纪要的实践与调优

腾讯云AI语音识别:自动生成会议纪要的实践与调优

我们每次开会一小时&#xff0c;纪要却要整理半小时&#xff0c;这句话我从不止一个团队嘴里听到过。会议内容总要有人记&#xff0c;记完还要提炼待办、确认负责人&#xff0c;稍微复杂一点的讨论&#xff0c;光靠人工回忆很容易漏掉关键信息。所以这两年我一直建议有条件的团…

2026/10/20 17:54:48 阅读更多 →
Kubernetes资源清单实战:从YAML编写到Pod、Service与ConfigMap

Kubernetes资源清单实战:从YAML编写到Pod、Service与ConfigMap

1. 资源清单是什么&#xff0c;为什么是Kubernetes的第一道门槛很多刚接触Kubernetes的开发者&#xff0c;第一个卡住的地方往往不是Pod、Deployment这些概念本身&#xff0c;而是搞不懂"资源清单"到底是个什么东西、为什么要写这么一长串YAML。我最早学K8s的时候也踩…

2026/10/20 17:54:48 阅读更多 →
Qt课程进度导航栏G03:从数据模型到自绘渲染的完整实践

Qt课程进度导航栏G03:从数据模型到自绘渲染的完整实践

做教育类桌面客户端的时候&#xff0c;课程导航一直是个绕不开的活儿。用户要在一个窗口里看到章、节、课时的层级关系&#xff0c;要清楚自己学到哪了&#xff0c;还得能一键跳回上次退出的位置。市面上的现成组件大多只解决“展示目录”这一层&#xff0c;进度状态、学习记忆…

2026/10/20 17:53:47 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →