C++ UDP客户端编程实战:从socket到超时重传完整指南
1. 从一个实际需求说起为什么要手写UDP客户端很多人第一次接触网络编程都是从TCP开始的。三次握手、可靠传输、面向连接这些概念听起来就让人安心。但真正到了某些场景下你会发现TCP反而成了累赘。比如做局域网设备发现、实时游戏同步、音视频推流、DNS查询这些场景有一个共同特点宁可丢包也不要延迟。这时候UDP就成了首选。我最早写UDP客户端是为了做一个局域网内的设备状态广播工具。当时用TCP写了个版本设备离线后服务端要等很久才能感知到因为TCP的连接断开需要经过完整的挥手流程中间还有各种超时重传。换成UDP之后客户端每隔几秒发一个心跳包服务端超过一定时间没收到就标记离线逻辑简单响应也快。这篇文章要聊的就是C socket编程中的UDP客户端。我会从整体设计思路讲起把核心API的细节掰开揉碎再给出一份可以直接编译运行的完整代码最后把我这些年踩过的坑和排查经验整理出来。不管你是刚学网络编程的新手还是想从TCP转到UDP的老手应该都能从中找到有用的东西。需要提前说明的是本文基于Linux平台下的Berkeley socket接口来讲解Windows平台下需要额外处理Winsock的初始化和清理核心API的名字基本一致差异我会在对应位置标注出来。2. UDP客户端的整体设计与核心思路2.1 为什么UDP客户端比TCP客户端简单这么多先看一张对比表直观感受一下两者的差异对比维度TCP客户端UDP客户端连接建立需要connect三次握手无需握手直接sendtosocket类型SOCK_STREAMSOCK_DGRAM数据传输send/recv面向字节流sendto/recvfrom面向数据报可靠性内核保证可靠有序应用层自己保证连接状态有状态无状态服务端数量一对一可以一对多TCP客户端要经历socket、connect、send、recv、close这一整套流程而UDP客户端只需要socket、sendto、recvfrom、close。少了一个connect环节看起来简单了不少。但这里有个容易被忽略的点UDP的简单是表面的复杂性被转移到了应用层。TCP帮你处理了丢包重传、乱序重组、流量控制UDP把这些全部交还给你。所以写UDP客户端代码量可能少了但需要考虑的问题反而更多。2.2 一个UDP客户端最少需要哪几个步骤抛开所有花哨的功能一个能跑通的UDP客户端核心就四步创建socket调用socket()传入AF_INET和SOCK_DGRAM准备目标地址填充sockaddr_in结构体设置服务端IP和端口发送数据调用sendto()指定目标地址接收数据调用recvfrom()获取服务端响应这四步就是UDP客户端的骨架。剩下的工作比如设置超时、处理错误、重传机制、数据序列化都是在这个骨架上长出来的肉。2.3 关于connect的一个常见误解很多人以为UDP不能用connect其实不是。UDP的connect不会发起任何网络握手它只是在本地内核里记录了对端的地址信息。调用connect之后你可以直接用send和recv不用每次都写sendto和recvfrom。更重要的是connect后的UDP socket只会接收来自指定对端的数据其他地址发来的包会被内核直接丢弃这在一定程度上能过滤掉一些杂包。但connect也有代价一旦connect了这个socket就只能和那一个对端通信了。如果你的客户端需要同时和多个服务端交互就不能用connect。我的建议是如果确定只和一个服务端通信用connect简化代码如果需要一对多老老实实用sendto。3. 核心API细节与参数解析3.1 socket函数的三个参数怎么选socket()的原型是int socket(int domain, int family, int protocol);对于UDP客户端参数选择如下domain填AF_INET表示IPv4。如果要支持IPv6填AF_INET6。现在很多环境已经开始推IPv6了如果你的程序需要兼容双栈可以考虑用AF_INET6配合IPV6_V6ONLY选项或者干脆用getaddrinfo()来动态决定。type填SOCK_DGRAM这是UDP的标志。填成SOCK_STREAM就变成TCP了。protocol通常填0让系统根据type自动选择。也可以显式填IPPROTO_UDP效果一样。返回值是一个文件描述符失败返回-1。这里有个细节每次创建socket都要检查返回值我见过太多人写完不检查结果后面sendto一直失败排查半天才发现是socket创建就没成功。3.2 sockaddr_in结构体的填充要点sockaddr_in是IPv4地址结构体定义大致如下struct sockaddr_in { sa_family_t sin_family; // 地址族AF_INET in_port_t sin_port; // 端口号网络字节序 struct in_addr sin_addr; // IP地址网络字节序 char sin_zero[8]; // 填充字段全零 };填充时有三个关键点第一字节序问题。端口号必须用htons()转成网络字节序IP地址用inet_pton()或inet_addr()转换。我见过有人直接写serv_addr.sin_port 8080;在小端机器上实际发出去的端口号是0x1F90的反序服务端根本收不到。第二sin_zero字段。虽然现代系统不强制要求清零但用memset()把整个结构体清零是个好习惯能避免一些奇怪的兼容性问题。第三IP地址的转换。inet_addr()是老接口不支持IPv6而且对错误输入返回INADDR_NONE和广播地址255.255.255.255冲突。推荐用inet_pton()它明确区分成功和失败更安全。3.3 sendto和recvfrom的参数详解ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);sendto的关键点buf和len是你要发送的数据和长度。UDP是面向数据报的一次sendto对应一个完整的数据报。如果你发了100字节接收方要么收到完整的100字节要么什么都收不到不会出现收到50字节的情况。flags通常填0。如果需要非阻塞发送可以填MSG_DONTWAIT。dest_addr和addrlen指定目标地址。如果socket已经connect了这两个参数可以填NULL和0。返回值是实际发送的字节数。对于UDP来说要么等于len要么返回-1。如果返回值小于len说明发生了错误不要以为部分发送是正常的。recvfrom的关键点src_addr和addrlen是输出参数用来获取发送方的地址。如果不需要知道是谁发的可以填NULL。addrlen是值-结果参数调用前要设置成sizeof(struct sockaddr_in)调用后内核会修改为实际地址长度。返回值是接收到的字节数。如果返回0对于UDP来说是一个合法的空数据报不是连接关闭。这点和TCP完全不同TCP的recv返回0表示对端关闭了连接。如果缓冲区太小装不下整个数据报多余的部分会被丢弃recvfrom返回的是缓冲区大小并设置MSG_TRUNC标志如果flags参数支持的话。所以缓冲区一定要给够。3.4 数据报大小的限制UDP数据报的理论最大长度是65507字节65535减去IP头20字节和UDP头8字节。但实际上MTU最大传输单元通常是1500字节超过这个大小的数据报会在IP层被分片。分片会带来两个问题一是任何一个分片丢失整个数据报都要重传二是某些网络设备会直接丢弃分片包。我的经验是UDP数据报控制在1400字节以内比较稳妥这样能避免IP分片。如果需要传输更大的数据应该在应用层自己做分片和重组。4. 完整可运行的UDP客户端实现4.1 基础版本发一条收一条先给一个最简版本功能是向指定服务端发送一条消息然后等待响应#include iostream #include cstring #include sys/socket.h #include arpa/inet.h #include unistd.h int main() { // 1. 创建socket int sockfd socket(AF_INET, SOCK_DGRAM, 0); if (sockfd 0) { std::cerr socket创建失败: strerror(errno) std::endl; return -1; } // 2. 设置服务端地址 struct sockaddr_in serv_addr; memset(serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family AF_INET; serv_addr.sin_port htons(9090); if (inet_pton(AF_INET, 127.0.0.1, serv_addr.sin_addr) 0) { std::cerr IP地址转换失败 std::endl; close(sockfd); return -1; } // 3. 发送数据 const char* msg hello udp server; ssize_t sent sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)serv_addr, sizeof(serv_addr)); if (sent 0) { std::cerr sendto失败: strerror(errno) std::endl; close(sockfd); return -1; } std::cout 已发送 sent 字节 std::endl; // 4. 接收响应 char buf[1500]; struct sockaddr_in from_addr; socklen_t from_len sizeof(from_addr); ssize_t recvd recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)from_addr, from_len); if (recvd 0) { std::cerr recvfrom失败: strerror(errno) std::endl; } else { buf[recvd] \0; char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, from_addr.sin_addr, ip, sizeof(ip)); std::cout 收到来自 ip : ntohs(from_addr.sin_port) 的响应: buf std::endl; } close(sockfd); return 0; }编译命令g -o udp_client udp_client.cpp -stdc11这个版本能跑通但有几个明显的问题没有超时机制如果服务端不响应recvfrom会一直阻塞没有重传丢包了就丢了没有错误重试。下面逐个解决。4.2 加上超时让recvfrom不再无限等待给recvfrom设置超时有两种方式用setsockopt设置SO_RCVTIMEO或者用select/poll多路复用。前者简单后者灵活。方式一SO_RCVTIMEOstruct timeval tv; tv.tv_sec 3; // 3秒超时 tv.tv_usec 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, tv, sizeof(tv));设置之后recvfrom如果超过3秒没收到数据会返回-1并设置errno为EAGAIN或EWOULDBLOCK。注意这两个错误码在不同系统上可能不同判断时要两个都检查。方式二selectfd_set readfds; FD_ZERO(readfds); FD_SET(sockfd, readfds); struct timeval tv; tv.tv_sec 3; tv.tv_usec 0; int ret select(sockfd 1, readfds, nullptr, nullptr, tv); if (ret 0 FD_ISSET(sockfd, readfds)) { // 有数据可读调用recvfrom } else if (ret 0) { // 超时 } else { // 出错 }select的好处是可以在等待期间做其他事情比如检查退出标志。缺点是每次调用都要重新设置fd_set和timeval因为select会修改它们。我个人的习惯是简单的请求-响应模式用SO_RCVTIMEO需要同时处理多个socket或者需要精细控制超时逻辑的用select/poll。4.3 加上重传让丢包不再致命UDP不保证可靠但很多场景下我们需要一定的可靠性。最简单的做法是超时重传const int MAX_RETRY 3; const int TIMEOUT_SEC 2; for (int i 0; i MAX_RETRY; i) { ssize_t sent sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)serv_addr, sizeof(serv_addr)); if (sent 0) { std::cerr 第 i1 次发送失败 std::endl; continue; } ssize_t recvd recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)from_addr, from_len); if (recvd 0) { // 收到响应处理并退出循环 break; } else if (errno EAGAIN || errno EWOULDBLOCK) { std::cout 第 i1 次等待超时准备重传 std::endl; } else { std::cerr recvfrom错误: strerror(errno) std::endl; break; } }这里有个细节重传时要重新设置from_len因为recvfrom会修改它。我见过有人忘了重置第二次recvfrom时addrlen变成了0导致地址信息获取失败。重传次数和超时时间需要根据实际网络环境调整。局域网内可以设短一点比如1秒超时、2次重传广域网或者移动网络下超时时间要适当放长3到5秒比较合适。4.4 处理EINTR被信号打断怎么办在Linux下如果程序收到信号阻塞中的recvfrom可能会被中断返回-1并设置errno为EINTR。这不是真正的错误应该重新调用。处理方式ssize_t recvd; do { recvd recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)from_addr, from_len); } while (recvd 0 errno EINTR);这个细节在调试时经常被忽略导致程序莫名其妙地失败。尤其是当程序用了定时器信号或者被CtrlC中断时EINTR出现的概率不低。4.5 封装成一个可复用的类把上面的逻辑封装成一个类用起来会方便很多class UdpClient { public: UdpClient() : sockfd_(-1) {} ~UdpClient() { if (sockfd_ 0) close(sockfd_); } bool init(const std::string server_ip, uint16_t server_port, int timeout_sec 3) { sockfd_ socket(AF_INET, SOCK_DGRAM, 0); if (sockfd_ 0) return false; memset(serv_addr_, 0, sizeof(serv_addr_)); serv_addr_.sin_family AF_INET; serv_addr_.sin_port htons(server_port); if (inet_pton(AF_INET, server_ip.c_str(), serv_addr_.sin_addr) 0) { close(sockfd_); sockfd_ -1; return false; } struct timeval tv; tv.tv_sec timeout_sec; tv.tv_usec 0; setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO, tv, sizeof(tv)); return true; } bool sendAndReceive(const std::string request, std::string response, int max_retry 3) { char buf[1500]; for (int i 0; i max_retry; i) { ssize_t sent sendto(sockfd_, request.c_str(), request.size(), 0, (struct sockaddr*)serv_addr_, sizeof(serv_addr_)); if (sent 0) continue; struct sockaddr_in from_addr; socklen_t from_len sizeof(from_addr); ssize_t recvd; do { recvd recvfrom(sockfd_, buf, sizeof(buf) - 1, 0, (struct sockaddr*)from_addr, from_len); } while (recvd 0 errno EINTR); if (recvd 0) { response.assign(buf, recvd); return true; } if (errno ! EAGAIN errno ! EWOULDBLOCK) break; } return false; } private: int sockfd_; struct sockaddr_in serv_addr_; };用起来就三行UdpClient client; client.init(127.0.0.1, 9090); std::string resp; client.sendAndReceive(ping, resp);5. 常见问题与排查技巧实录5.1 发送成功但服务端收不到这是最常见的问题排查思路按顺序来第一步确认服务端在监听。用netstat -ulnp | grep 端口号看服务端是否真的绑定了UDP端口。注意UDP的netstat输出和TCP不一样State列显示的是UNCONN这是正常的。第二步确认地址和端口正确。检查inet_pton的返回值检查htons有没有漏掉。我遇到过一次端口号写的是htons(9090)但服务端监听的是9090的字符串形式实际转换后不一致。第三步检查防火墙。Linux下用iptables -L -n看规则云服务器还要检查安全组。UDP没有连接状态很多防火墙对UDP的处理和TCP不同容易漏配。第四步抓包确认。用tcpdump -i any udp port 9090 -nn在服务端抓包看数据包到底有没有到达。如果抓到了包但程序没收到说明是程序的问题如果没抓到说明是网络或防火墙的问题。5.2 recvfrom返回-1但errno是EAGAIN这说明超时了不是错误。但如果你的程序逻辑里把EAGAIN当成致命错误处理就会出问题。正确的做法是if (recvd 0) { if (errno EAGAIN || errno EWOULDBLOCK) { // 超时走重传或退出逻辑 } else if (errno EINTR) { // 被信号打断重试 } else { // 真正的错误 } }5.3 收到的数据不完整或乱码UDP是面向数据报的不会出现TCP那种半包问题。如果收到的数据不对通常是这几个原因发送方和接收方的字节序不一致。如果传输的是二进制数据要统一用网络字节序。缓冲区太小。前面说过缓冲区装不下整个数据报多余部分会被丢弃。数据报被分片后丢失了部分分片。这种情况在大包传输时容易出现解决办法是控制包大小在MTU以内。5.4 端口被占用导致bind失败客户端通常不需要bind内核会自动分配一个临时端口。但如果你确实需要bind一个固定端口可能会遇到EADDRINUSE。UDP的端口复用和TCP不同两个程序不能同时bind同一个UDP端口除非设置了SO_REUSEADDR或SO_REUSEPORT。排查方法netstat -ulnp | grep 端口号找到占用端口的进程要么换端口要么先停掉那个进程。5.5 常见问题速查表现象可能原因排查方法sendto返回-1socket未创建成功、地址错误检查socket返回值、inet_pton返回值服务端收不到包防火墙、地址端口错误tcpdump抓包、检查iptablesrecvfrom一直阻塞未设置超时、服务端未响应设置SO_RCVTIMEO、检查服务端日志recvfrom返回EAGAIN超时正常现象走重传逻辑收到乱码字节序问题、缓冲区太小统一网络字节序、增大缓冲区bind失败EADDRINUSE端口被占用netstat查找占用进程5.6 几个我踩过的坑坑一忘了设置SO_RCVTIMEO程序卡死。早期写的一个工具服务端挂了之后客户端一直阻塞在recvfrom整个程序假死。后来加了超时问题解决。坑二重传时没有重置addrlen。这个前面提过第二次recvfrom时addrlen变成0导致获取不到发送方地址。虽然不影响数据接收但如果你需要根据来源做判断就会出问题。坑三在信号处理函数里调用recvfrom。信号处理函数里只能调用异步信号安全的函数recvfrom不在其中。正确做法是在信号处理函数里设置一个标志主循环检查标志后退出。坑四忽略了sendto的返回值。UDP的sendto返回-1不代表数据一定没发出去可能是本地缓冲区满了。但无论如何检查返回值是好习惯。坑五用UDP传大文件。UDP不适合传大文件分片重组的开销和丢包概率都很大。如果非要传一定要在应用层做分片、编号、确认、重传工作量不比直接用TCP小。6. 进阶话题让UDP客户端更健壮6.1 非阻塞模式与IO多路复用如果客户端需要同时处理多个任务阻塞式的recvfrom就不合适了。可以把socket设置成非阻塞int flags fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);非阻塞模式下recvfrom如果没有数据会立即返回-1并设置errno为EAGAIN。配合epoll或poll可以同时管理多个socket适合做高并发的客户端。6.2 心跳与保活UDP没有连接状态服务端无法感知客户端是否还在线。常见的做法是客户端定期发送心跳包服务端维护一个最后活跃时间表。心跳间隔根据业务需求定一般5到30秒。心跳包要尽量小一个字节或者一个固定的魔数就够了。6.3 应用层协议设计UDP客户端和服务端之间的数据格式需要自己定义。最简单的就是纯文本复杂一点可以用TLVType-Length-Value或者protobuf。不管用什么格式一定要有长度字段或者结束标志否则接收方无法判断数据报的边界。虽然UDP本身保证了数据报的完整性但应用层协议还是要有自己的校验机制比如加一个magic number或者CRC校验。6.4 关于广播和组播UDP支持广播和组播这是TCP做不到的。广播地址通常是255.255.255.255或者子网广播地址需要设置SO_BROADCAST选项。组播需要加入组播组用IP_ADD_MEMBERSHIP。这两个话题展开讲又是一大篇这里只提一句广播和组播在局域网设备发现、服务通告等场景下非常有用值得单独花时间研究。7. 一些个人体会写UDP客户端这些年最大的感受是简单的东西往往最考验功底。TCP帮你屏蔽了很多细节UDP把这些细节全部暴露出来逼着你去理解网络协议的本质。字节序、MTU、超时、重传、错误码这些在TCP里可能一辈子都不用关心的东西在UDP里都是家常便饭。另一个体会是不要试图用UDP去实现TCP。我见过有人想在UDP上做可靠传输加了一堆确认、重传、拥塞控制最后代码复杂度比直接用TCP还高性能却没见得好多少。UDP的价值在于它的轻量和灵活用在合适的场景下才能发挥优势。如果你的场景需要可靠传输直接用TCP如果需要低延迟、能容忍丢包再用UDP。最后分享一个小技巧调试UDP程序时先在本机用127.0.0.1跑通再换到局域网最后再上公网。每换一个环境问题都会不一样。本机跑通说明代码逻辑没问题局域网跑通说明地址和防火墙配置没问题公网跑通说明NAT和路由没问题。按这个顺序排查能省下不少时间。

相关新闻

VS2019配置GLUT/freeglut完整指南:从原理到实战

VS2019配置GLUT/freeglut完整指南:从原理到实战

1. 为什么2024年还在用VS2019配GLUT先把话说在前头:如果你现在打开搜索引擎搜"OpenGL环境配置",大概率会看到两种声音。一种说GLUT早就停止维护了,应该用freeglut或者GLFW;另一种说直接用VS2019的NuGet包管理器一键安装…

2026/10/18 5:11:38 阅读更多 →
Claude Mods摸鱼神器:低感知高扩展的职场时间管理方案

Claude Mods摸鱼神器:低感知高扩展的职场时间管理方案

1. 摸鱼神器的核心思路与方案选型1.1 为什么选择 Claude Mods 作为切入点"摸鱼"这个词在职场语境里早就不是贬义了,它更多是一种对碎片时间的精细化管理。我观察到一个现象:很多人上班时并不是真的想偷懒,而是手头任务进入等待期—…

2026/10/18 5:10:38 阅读更多 →
蓝牙回连途中切换模式导致死机:根因分析、修复与防御方案

蓝牙回连途中切换模式导致死机:根因分析、修复与防御方案

回连手机的途中切换模式导致死机,这问题我盯了整整一周。最初以为是硬件复位异常,后来抓了完整的底层日志才发现,问题出在一个很多人都会忽略的时序细节上——回连流程还没走完,模式切换的命令就已经把状态机打乱了。这个标题里的…

2026/10/18 5:10:38 阅读更多 →

最新新闻

OpenClaw(Clawdbot)本地部署指南:从零构建24小时AI助理与自动化工作流

OpenClaw(Clawdbot)本地部署指南:从零构建24小时AI助理与自动化工作流

1. 从“一周只工作2天”说起:我为什么认真琢磨起了AI助理先别急着把“未来一周只工作2天”当成一个段子。作为一个常年被日报、周报、数据汇总、消息回复淹没的从业者,我第一次听到这个观点时也觉得是专家的嘴骗人的鬼。但当我花了几个周末,把…

2026/10/18 6:58:58 阅读更多 →
OpenClaw AI助理实战:从部署到技能开发,打造7×24小时数字员工

OpenClaw AI助理实战:从部署到技能开发,打造7×24小时数字员工

“未来一周只工作两天”这个话题最近被讨论得挺热闹,先不管专家们怎么预测,我自己的判断是——AI助理这类工具确实是目前普通人最容易够到、也最接近“省人力”效果的东西。OpenClaw(社区里也叫Clawdbot)就是其中一个非常典型的开…

2026/10/18 6:58:58 阅读更多 →
道尔夫-切比雪夫加权:MATLAB实现等副瓣阵列综合与避坑指南

道尔夫-切比雪夫加权:MATLAB实现等副瓣阵列综合与避坑指南

简介:这份资源面向学习阵列信号处理与相控阵天线综合的高校学生、科研人员及工程技术人员,提供基于MATLAB的线阵道尔夫-切比雪夫加权完整仿真代码,用于解决低副瓣方向图综合中激励幅度分布求解与性能验证问题。压缩包共3个文件,包…

2026/10/18 6:58:58 阅读更多 →
企业数据灾备方案全解析:从备份到容灾的落地实践与避坑指南

企业数据灾备方案全解析:从备份到容灾的落地实践与避坑指南

前阵子有个做连锁零售的朋友跟我诉苦,说他们门店收银系统前一夜被勒索病毒加密,总部虽然保留了上一周的完整备份,但恢复出来的数据只能追溯到七天前,这一周所有的销售明细、会员积分、库存流水全丢了。他问我:为什么明…

2026/10/18 6:58:58 阅读更多 →
科研效率翻倍:6个网站打通文献检索到选刊投稿全流程

科研效率翻倍:6个网站打通文献检索到选刊投稿全流程

1. 先说说我自己的效率翻倍经历:从收藏夹吃灰开始研一上学期,我干过一件特别蠢的事:花了一整个周末,把各种"学术神器""科研必备网站"的帖子翻了个遍,然后把这些网站统统塞进浏览器收藏夹。收藏夹整…

2026/10/18 6:58:58 阅读更多 →
机器学习大作业实战指南:从数据清洗到模型调优的完整流程

机器学习大作业实战指南:从数据清洗到模型调优的完整流程

简介:这份资源是电子科技大学机器学习课程大作业的完整配套资料,面向正在修读机器学习、人工智能相关课程的高校学生,以及希望系统梳理算法理论与工程实践的自学者。内容围绕监督学习、无监督学习、半监督学习与强化学习等范式展开&#xff0…

2026/10/18 6:57:58 阅读更多 →

日新闻

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

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

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

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

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

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

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

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

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

2026/10/18 0:00:55 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 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/18 0:55:02 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →