首页/新闻资讯/正文详情

C语言实现Ping:ICMP协议与原始套接字实战详解

发布时间:2026/9/24 18:31:20 来源:云帆数科 栏目:资讯中心
C语言实现Ping:ICMP协议与原始套接字实战详解
简介这份压缩包提供了一套用C语言实现Ping功能的完整示例面向网络编程初学者以及需要理解ICMP协议的开发者解决如何在C/C环境中通过原始套接字构造、发送并接收ICMP回显报文的问题。包体十分精简共包含2个文件1个cpp源码文件承载核心实现逻辑1个txt说明文档补充了关键步骤与参考说明压缩后大小仅为2KB。资源已有414人浏览学习是网络编程入门阶段值得反复研读的小型范例。通过阅读源码读者可掌握ICMP报头类型/代码/校验和的填充方法、原始套接字的创建流程、sendto与recvfrom的收发机制、基于gettimeofday计算往返时间RTT的思路以及常见网络错误的处理方式。整体代码量小、结构清晰适合用作课堂实验、课程设计或网络故障排查的技能拓展参考。1. 用C语言实现Ping功能先分清命令和协议再做套接字当网络不通时很多人第一反应就是打开终端敲ping。可一旦想在自己的C语言程序里实现同样的能力会发现根本没有一个叫ping()的函数可以直接调用。Ping不是命令本身而是ICMP协议里的回显请求与回显应答。实现它的核心是构造ICMP报文、创建原始套接字、计算校验和、然后解析回包并计算RTT。这篇笔记适合两类人一类是想搞懂ping命令原理、准备深入网络编程的C语言开发者另一类是需要在嵌入式系统或受限环境里做连通性探测、不想依赖外部命令的工具开发者。从协议结构到可编译代码再到权限、校验和、超时、偏移量这些坑我都按实操顺序展开新手可以照着敲老手可以直接跳到避坑章节看边界条件。2. ICMP回显与原始套接字为什么非要用SOCK_RAW以及三处协议细节2.1 ICMP回显请求的结构类型、代码、校验和与数据区ICMP是IP协议的一部分它承载在IP数据报里没有TCP或UDP那样的端口概念。Ping用的报文只有核心两步类型8回显请求类型0回显应答。一个标准IPv4的ICMP Echo报文可以拆成三个部分IP头部、ICMP头部、数据区。ICMP头部固定有8字节类型和代码各占1字节校验和占2字节标识符和序列号各占2字节后面跟着的是数据部分。在实际收到的应答包中IP头部在前ICMP头部紧随其后。常见的一个误区是直接把整个缓冲区的头部当ICMP处理这在发送侧不会出错因为发送时不需要自己构造IP头内核会帮你封装但在接收侧必须注意Linux的原始套接字收到的包是包含IP头的Windows上则存在版本差异。为此在代码里定义一个自己的ICMP结构体是更可控的方式。字段长度说明type1字节8表示回显请求0表示回显应答code1字节Echo请求和应答时固定为0checksum2字节覆盖ICMP头部和数据区的校验和id2字节发起方生成的标识用于匹配回包seq2字节序列号用于判断丢包和乱序2.2 普通UDP为什么做不了Ping网络层载荷的归属问题有人会问ICMP的Echo请求能不能用SOCK_DGRAM加UDP来实现答案是做不到。UDP属于传输层虽然它也会封装IP头但UDP头里有源端口和目的端口ICMP没有端口概念。如果只是单纯发一个UDP包到某个端口对方主机的协议栈会认为这是一个未连接的UDP包大概率返回一个ICMP端口不可达而不是你想要的Echo Reply。真正通用的做法是创建原始套接字并指定协议为IPPROTO_ICMPint sock socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sock 0) { perror(socket); return -1; }这段代码在Linux下运行时普通用户往往直接返回Operation not permitted。原因是原始套接字允许你构造网络层甚至链路层数据包属于高权限操作。解决方式有两种用root或sudo运行或者给二进制文件单独加网络原始套接字权限。后者更适合部署场景命令是setcap cap_net_rawep ./ping。Windows下则要求程序以管理员身份启动并且需要先调用WSAStartup初始化Winsock库。创建SOCK_RAW套接字后发送数据时不需要构造IP头内核会根据目标地址自动封装IP头。这一点很重要。它意味着你在用户态写的是ICMP协议头但抓包时看到的是一个完整的IP数据报。2.3 Linux与Windows的套接字差异接收缓冲里的IP头偏移问题跨平台实现Ping时最容易翻车的不是构造包而是读取回包。在Linux下用recvfrom接收数据返回的缓冲区起点是IP头需要先解析IP头长度字段再向后偏移到ICMP头。Windows的原始套接字行为与Linux不完全一致它有时会过滤掉IP头有时返回的地址信息也不包含完整的IP头内容。我一般在代码里不做复杂的平台判断而是统一走IP头偏移逻辑。如果运行环境中接收缓冲区不包含IP头只需要将偏移设为0就能兼容两种模式。关键在于不能在解析ICMP字段前默认buf[0]就是type否则收到的应答类型可能是IP头里的版本号和首部长度组合看起来像乱码导致校验失败后把正常回包当废包丢掉了。3. 代码骨架ICMP头部填充、校验和算法和构造请求包3.1 自定义ICMP头部结构体为了使代码不依赖特定平台的系统头文件可以自己定义一个精简的ICMP头部。下面这个结构体按1字节对齐避免编译器在字段间插入填充字节否则包长度算错校验和也会跟着错。#include stdint.h #include stdio.h #include string.h #include sys/time.h #include arpa/inet.h #define ICMP_ECHO_REQUEST 8 #define ICMP_ECHO_REPLY 0 #pragma pack(push, 1) typedef struct icmp_header { uint8_t type; uint8_t code; uint16_t checksum; uint16_t id; uint16_t seq; int64_t timestamp; /* 用时间戳做RTT计算也是回包验据的辅助字段 */ } icmp_hdr_t; #pragma pack(pop)这里把时间戳直接放进ICMP头部虽然标准Ping的数据域更常用但放到头部方便接收侧一次性读取也省得多一步payload偏移。注意#pragma pack(1)只对结构体内部对齐生效不改变网络字节序规则。发送时id和seq需要转成网络字节序时间戳则按64位原样填充校验和按网络字节序计算。代码里的timestamp类型选int64_t在Linux和Windows的常用编译器下长度一致。如果还希望兼容老版本32位编译器可以拆成两个32位字段再拼起来不过现在基本不需要如此保守。3.2 校验和算法16位累加、进位回卷与取反ICMP的校验和算法和IP头校验和是同一套逻辑并不复杂但网上能找到的版本很多有的把len当字节数有的把指针转换成uint16_t*时没考虑奇数长度最后计算结构都是错的。正确算法是将整个ICMP报文按16位一组累加如果发生进位就将高16位加回低16位最后对结果取反。static uint16_t in_cksum(uint16_t *addr, size_t len) { uint32_t sum 0; while (len 1) { sum *addr; len - 2; } if (len 1) { sum *(uint8_t *)addr; } while (sum 16) { sum (sum 0xffff) (sum 16); } return (uint16_t)~sum; }调用前两个硬性要求一是校验和字段必须先清零二是计算范围必须覆盖整个ICMP报文包括你塞进去的数据区。常见现象是收包方一直不回包抓包一看报文没丢问题往往出在计算长度时只算了头部8字节漏掉了timestamp或payload。校验和结果直接赋值给结构体里的checksum字段不要在赋值时再调一次htons因为这个算法本身已经按网络字节序语义计算再转换反而会错。3.3 构造一个带时间戳的请求包构造请求包时先清零整个缓冲区再填充固定字段。id一般取进程号这样多个ping实例同时跑也不会互相认领回包。seq是本次会话的自增序号初始值随意但发送端和收包端必须用同一个序号匹配。时间戳建议用微秒甚至纳秒否则同网段RTT小于1毫秒时会因为精度不够统计出很多0毫秒结果。static void build_icmp_packet(icmp_hdr_t *icmp, uint16_t id, uint16_t seq, struct timeval *tv) { memset(icmp, 0, sizeof(*icmp)); icmp-type ICMP_ECHO_REQUEST; icmp-code 0; icmp-checksum 0; icmp-id htons(id); icmp-seq htons(seq); icmp-timestamp (int64_t)tv-tv_sec * 1000000 tv-tv_usec; icmp-checksum in_cksum((uint16_t *)icmp, sizeof(*icmp)); }这里把时间戳单位统一成微秒。收到应答后取出时间戳与当前时间相减就得到RTT。timestamp字段放在ICMP头部里相当于同时充当了校验和数据区所以计算checksum时长度直接取结构体大小。如果把timestamp当作数据区放在结构体后面计算长度则必须加起来。3.4 地址解析与TTL控制目标地址支持域名和IP地址。不要直接写死inet_addr因为现在很多环境会通过DNS解析域名使用getaddrinfo是更稳的做法。也能顺带处理IPv6的扩展需求。目标地址可以是点分十进制字符串也可以是主机名统一转换成struct sockaddr_in后传给发送接口。static int resolve_target(const char *host, struct sockaddr_in *dst) { struct addrinfo hints; struct addrinfo *res; memset(hints, 0, sizeof(hints)); hints.ai_family AF_INET; hints.ai_socktype SOCK_RAW; int rc getaddrinfo(host, NULL, hints, res); if (rc ! 0) { fprintf(stderr, resolve failed: %s\n, gai_strerror(rc)); return -1; } *dst *(struct sockaddr_in *)res-ai_addr; freeaddrinfo(res); return 0; }如果只想探测单个IP也可以用inet_pton(AF_INET, host, dst-sin_addr)但不适用于域名。TTL控制比较直接在socket上设置IP_TTL选项即可。默认值通常是64部分系统会显示不同的TTL初始值。TTL的意义不仅是防止环路更是排查网络路径长度的一个参考。int ttl 64; setsockopt(sock, IPPROTO_IP, IP_TTL, ttl, sizeof(ttl));TTL设置后收到的回包中IP头里的ttl字段是目标主机回包时的剩余TTL而不是你发出的TTL。所以想要看网络跳数要解析回包IP头里的ttl而不是本地的发送选项。4. 发送接收循环RTT计算、统计输出和超时处理4.1 sendto与recvfrom的主循环Ping程序的核心循环不复杂但顺序不能乱先记录发送时间再sendto然后recvfrom等待回包收到后再计算RTT。如果在sendto之前就设置接收超时只是为了让超时不阻塞太久但真正的计时起点应该放在sendto之前。int seq 0; int sent 0; int received 0; struct sockaddr_in dest; resolve_target(argv[1], dest); dest.sin_family AF_INET; dest.sin_port 0; /* ICMP没有端口概念 */ struct timeval timeout {1, 0}; setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, timeout, sizeof(timeout)); for (seq 0; seq count; seq) { struct timeval t_start, t_end; gettimeofday(t_start, NULL); icmp_hdr_t pkt; build_icmp_packet(pkt, getpid() 0xffff, seq, t_start); ssize_t snd sendto(sock, pkt, sizeof(pkt), 0, (struct sockaddr *)dest, sizeof(dest)); if (snd 0) { perror(sendto); continue; } sent; uint8_t buf[512]; struct sockaddr_in from; socklen_t fromlen sizeof(from); ssize_t rcv recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)from, fromlen); if (rcv 0) { printf(seq%d timeout\n, seq); continue; } gettimeofday(t_end, NULL); process_reply(buf, rcv, pkt, t_start, t_end); sleep(1); }这段代码里的sleep控制的是发送频率间隔太短会被系统限速并且乱序回包会越来越多。dest.sin_port 0不是随便写因为sendto要求必须有端口字段但ICMP不使用端口内核会忽略这个值。注意发送缓冲区长度只用sizeof(pkt)不需要包括IP头。4.2 接收侧解析必须跳过IP头再读ICMP这是整段代码里最容易出错的地方。前面提到Linux的原始套接字在接收时返回的buf里第一个字节是IP协议头。如果你直接把它转成icmp_hdr_t*取到的type大概率是0x45对应IPv4头部的版本号和头部长度而不是ICMP类型。所以要用IP头长度字段确定偏移量。static void process_reply(uint8_t *buf, ssize_t len, icmp_hdr_t *sent_pkt, struct timeval *t_start, struct timeval *t_end) { struct iphdr *ip (struct iphdr *)buf; int iphdrlen ip-ihl * 4; icmp_hdr_t *reply (icmp_hdr_t *)(buf iphdrlen); if (reply-type ! ICMP_ECHO_REPLY) { return; } if (reply-id ! sent_pkt-id) { return; } if (reply-seq ! sent_pkt-seq) { return; } double rtt_ms (t_end-tv_sec - t_start-tv_sec) * 1000.0 (t_end-tv_usec - t_start-tv_usec) / 1000.0; int ttl ip-ttl; printf(seq%d ttl%d time%.3f ms\n, ntohs(reply-seq), ttl, rtt_ms); }这段代码里struct iphdr在Linux下定义在netinet/ip.h。在Windows上需要换成自己的IP头结构体或者通过宏判断平台后偏移。id和seq在发送时用htons做了转换所以接收侧拿到的网络序值需要ntohs再与发送值比较。如果没有转换id和seq的匹配会失败正常情况下也能收到包但统计会全部当成无效包丢弃。4.3 RTT统计与丢包率计算统计不复杂但要区分“发出的包总数”和“收到有效回包的次数”。很多粗糙实现把两个都算在sent上导致超时的包也被计入成功。标准ping定义的丢包率是(sent - received) / sent * 100%其中received表示与当前seq匹配的Echo Reply数量。继续沿用前面的循环可以维护三个变量int total_sent 0; int total_recv 0; double min_rtt 0.0, max_rtt 0.0, sum_rtt 0.0;每次收到有效回包时更新if (rtt_ms min_rtt || min_rtt 0.0) min_rtt rtt_ms; if (rtt_ms max_rtt) max_rtt rtt_ms; sum_rtt rtt_ms; total_recv;最后输出时平均RTT是sum_rtt / total_recv不要用总次数去除否则会把超时时间混进平均值里拉高数据。超时时间也不应该参与RTT统计因为超时只代表没有响应不意味着RTT等于超时值。4.4 超时报错与DUP重复包识别recvfrom设置超时后超过1秒没有数据会返回-1并置errno为EAGAIN或EWOULDBLOCK。这时只是这一个seq超时程序要继续发送下一个包而不是退出。这里我习惯在代码里区分“网络不可达”的sendto错误和“接收超时”前者说明目标IP本身有问题后者只代表这个包没有回包。还有一种情况是回包到达较晚比如第一个请求超时第二个请求发出后收到了第一个的回包。此时用seq匹配会丢弃这个晚到包但这不算重复。真正的DUP是同一个seq被应答多次常见于某些负载均衡设备或环路路径。标准ping输出中会显示DUP!实现时可以用一个小的缓存记录最近几个seq收到的次数超过1就打印重复标识。5. 避坑手册权限、校验和、超时偏移和重复应答5.1 socket返回Operation not permitted现象程序在普通用户下运行socket调用直接返回-1错误信息是Operation not permitted。原因创建SOCK_RAW原始套接字需要特权普通用户没有CAP_NET_RAW能力。解决调试阶段用sudo运行。要部署到生产环境时用setcap cap_net_rawep ./ping给二进制文件单独加权限这样不用root也能起原始套接字。注意setcap对部分容器环境和NFS文件系统会失效运行时仍然会报EPERM。5.2 收不到回包但tcpdump能看到对端回了现象程序一直输出timeout但用tcpdump在本地网卡抓包能看到发出的请求和收到的应答。原因多半是接收缓冲区解析偏移出错。Linuxrecvfrom返回的数据以IP头开头程序却直接按ICMP头解析导致type校验不过把有效回包当异常包丢弃了。解决先打印buf前20字节的十六进制确认第一个字节是0x45还是0x08。如果是0x45必须解析IP头首部长度字段按偏移量跳转后再读ICMP。这个偏移值不是固定20因为IP头可能带有可选字段。5.3 校验和填错对端直接丢包现象程序sendto成功抓包也能看到请求报文但目标主机没有回包。原因ICMP校验和计算错误。常见错误包括没有把checksum字段清零就参与计算计算长度少算或多算对大端小端问题做了多余的htons或者结构体存在对齐填充实际发送长度和计算长度不一致。解决把校验和算法单独拎出来测试先用一组已知输入和标准结果对照。我习惯在打印调试信息时把整个报文按十六进制输出确认type08 code00再比对checksum字段值。只要校验和正确绝大多数防火墙不会丢这样的小包。5.4 窗口系统下初始化顺序不对现象Windows上编译通过运行后sendto不报错但recvfrom一直阻塞或返回错误。原因创建socket之前没有调用WSAStartup或者初始化版本没写对。Windows的原始套接字还受防火墙影响有时需要管理员权限才能创建。解决在main最开头调用WSAStartup(MAKEWORD(2,2), wsaData)程序退出时调用WSACleanup。创建socket或设置SO_RCVTIMEO失败后用WSAGetLastError()查看错误码不要照搬Linux的perror判断。5.5 seq复用导致匹配到了旧回包现象连续长时间运行后偶尔出现RTT异常偏大或者输出一个来自很老seq的应答。原因进程id没变id字段始终相同。seq循环回到曾经用过的值后如果网络中有延迟到达的旧回包就会被误判为当前seq的应答。解决seq在每次启动时设置不同的初始值比如seq getpid() 0xffff这样每次运行的起始点不同。同时在解析回包时校验源地址确认回包来自目标IP而不是某个中间设备。这个校验在程序里只需要多一次memcmp或inet_ntoa对比。6. 验证与进阶抓包对照、带源地址Ping和可扩展思路6.1 用tcpdump对照程序发包写完代码后不要急着跑通业务逻辑先用抓包工具验证报文字节。这样可以确认三个关键点发出的请求类型是8、校验和正确、回包类型是0。下面命令在Linux上运行假定网卡是eth0目标地址是10.0.0.2sudo tcpdump -i eth0 icmp and host 10.0.0.2 -XX抓包输出里可以直观看到ICMP头的八个字节。对照自己的代码确认id和seq的值与发送端一致。如果ttl解析出来是64或128说明IP头偏移写对了如果解析出来的ttl恒定显示为1大概率是拿IP头里的第一个字节当ttl了。抓包不仅是验证手段也是排查“为什么对方不回包”的关键工具。很多网络设备会丢弃校验和错误的报文抓包软件可以显示错误校验和。如果验证后报文没问题再考虑防火墙策略。6.2 带源地址的Ping实现与TTL读取标准ping命令支持-I指定源IP或网卡C语言实现时用bind给原始套接字绑定本地地址即可。先获取本机网卡地址再用该地址构造sockaddr_in然后调用bindstruct sockaddr_in local; local.sin_family AF_INET; local.sin_addr.s_addr inet_addr(192.168.1.10); local.sin_port 0; if (bind(sock, (struct sockaddr *)local, sizeof(local)) 0) { perror(bind); return -1; }bind之后系统会自动从这个源IP发出ICMP请求。多网卡服务器上这种方式很实用能绕过默认路由选择排查特定链路的连通性。接收侧的TTL字段从IP头读取也就是前面代码里的ip-ttl。通过对比本地发出的TTL和回包TTL可以大致判断中间设备的数量但不一定准确因为不同设备对TTL的处理策略不完全一致。6.3 后续可以怎么改完成基础Ping后还可以做三个低成本改造把固定count改成输入参数支持每秒发送频率控制增加IP头选项字段解析识别来自哪个源IP把UDP载荷和ICMP类型扩展到自定义探针。如果要做并发探测每增加一个目标就创建一个独立socket不要改变socket上的目标地址避免发送和接收匹配混乱。我曾经为一套内部监控系统写Ping探针校验和算法用了带结构体对齐的版本结果在目标机旁抓包看到checksum是错的对方主机一直不回包排查了一整晚才定位到问题。从那以后我每次改动报文结构都会强制用tcpdump抓一次包做对照再把统计逻辑接到正式流程里。这个习惯帮我避免了很多只有到生产环境才会暴露的坑希望你也能把这段代码跑通后再继续往里面加自己的功能。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

SpringBoot+Vue前后端分离公司资产管理系统实战全解析
SpringBoot+Vue前后端分离公司资产管理系统实战全解析

这套“前后端分离公司资产网站系统”是我最近在公司从零到一搭完的一个完整项目,技术栈就是标题里那套:SpringBoot Vue MyBatis MySQL。前后端分离这个词快被说烂了,但真正把一个资产管理系统从数据库设计、后端接口、前端页面一直做到服务… · 2026/9/24 18:31:20

OpenClaw搭建实战:WSL2环境、千问接入与飞书Channel排坑
OpenClaw搭建实战:WSL2环境、千问接入与飞书Channel排坑

OpenClaw 最近的热度确实离谱,群里天天有人问怎么装、怎么配、为什么跑不起来。我前前后后帮朋友远程排查过好几轮,从 Windows 到 Linux 到 WSL2 都踩过一遍,踩坑记录都快攒成一本小册子了。这篇我就把整套搭建流程拆开揉碎,从环境… · 2026/9/24 18:31:14

vscode-copilot-chat 中的 Visualization Runner:为 `[visualizable]` 测试一键接入 VS Code 可视化调试
vscode-copilot-chat 中的 Visualization Runner:为 `[visualizable]` 测试一键接入 VS Code 可视化调试

人工智能AI 应用AI Agent代码智能体交互助手工具调用MCP Clients 【免费下载链接】vscode-copilot-chat Copilot Chat extension for VS Code 项目地址: https://gitcode.com/gh_mirrors/vs/vscode-copilot-chat 点击查看 免费下载 在微软官方 Copilot Chat 扩展仓… · 2026/9/24 18:31:08

MySQL聚合函数与窗口函数实战:从分组统计到排名计算
MySQL聚合函数与窗口函数实战:从分组统计到排名计算

1. 聚合函数先打个底 1.1 五个最常用的聚合函数,你真的用对了吗 MySQL 里的聚合函数,说白了就是“把多行数据揉成一行结果”的函数。平时工作中最常见的无非是 COUNT、SUM、AVG、MIN、MAX 这五个,但它们各自的细节坑并不少。 先说 COUNT。很… · 2026/9/24 19:38:55

C++链表实现栈:从原理到代码,彻底搞懂后进先出
C++链表实现栈:从原理到代码,彻底搞懂后进先出

说实话,很多人学数据结构的第一个坎,就卡在栈这个看似简单的结构上。上课听老师讲"后进先出"觉得什么都懂了,真让自己用代码实现,却连节点怎么定义、指针怎么指都理不清。我自己当年学 C 链表实现栈的时候,也… · 2026/9/24 19:38:55

密码管理器迁移指南:从浏览器记住密码到 Bitwarden
密码管理器迁移指南:从浏览器记住密码到 Bitwarden

密码管理器迁移指南:从浏览器记住密码到 Bitwarden 「所有网站用同一个密码」是数字时代最危险的习惯。Bitwarden(GPL-3)是迁移成本最低的密码管理方案:全平台、端到端加密、免费版够用。 一、为什么浏览器记住密码不够用 不跨… · 2026/9/24 19:38:55

C++实现链表栈:从原理到完整代码与内存管理实战
C++实现链表栈:从原理到完整代码与内存管理实战

栈大概是数据结构里最“老实”的一个结构了——你放进去一叠元素,它只按完全相反的顺序给你吐出来。后进先出的规则听起来简单,但真要在C里用链表把它实现出来,却能把指针、内存管理、拷贝控制这些C核心基本功全都串一遍。尤其是很多同学数组… · 2026/9/24 19:38:55

网盘直链解析原理与工具选型:从扩展注入到自建服务
网盘直链解析原理与工具选型:从扩展注入到自建服务

1. 网盘直链解析到底在解决什么问题 1.1 从一次真实的下载崩溃说起 上个月帮一个做嵌入式的朋友处理一套开发板资料,对方发来一个网盘分享链接,里面是几个G的镜像文件和工具链压缩包。我点开网页端,下载按钮灰着,提示要装客户端&… · 2026/9/24 19:38:55

零基础学MySQL:从安装配置到建表查询入门指南
零基础学MySQL:从安装配置到建表查询入门指南

1. 零基础学MySQL,先搞清楚这几个核心概念 1.1 数据库到底在解决什么问题 很多零基础的朋友一上来就急着装MySQL、敲命令,结果装到一半发现完全不知道自己在干什么。我的建议是先花十分钟把几个底层概念捋清楚,后面所有操作都会变得顺理成章… · 2026/9/24 19:38:45

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码