从零构建Java RPC框架Jaws:深入理解Netty、动态代理与分布式服务治理
1. 项目概述为什么我们要再造一个RPC轮子“Jaws”这个名字听起来有点酷对吧它是我最近从零开始折腾出来的一个Java RPC框架。你可能会问市面上已经有Dubbo、gRPC、Spring Cloud那一大票成熟的框架了为什么还要自己造轮子这不是重复造轮子吗说实话最开始我也这么想。但真正动手之后我发现这个过程的价值远超预期。它不是一个为了炫技的玩具而是一个旨在“五脏俱全”的教学与理解型项目。所谓“五脏俱全”意味着它虽然精简但必须完整涵盖一个生产级RPC框架的核心模块服务注册与发现、网络通信、序列化、负载均衡、容错机制等。通过亲手实现一遍你能把那些在面试八股文里背得滚瓜烂熟的名词比如“Netty NIO模型”、“动态代理”、“一致性哈希负载均衡”变成指尖流淌的代码和脑子里清晰无比的架构图。这对于深入理解分布式系统的基石——RPC有着不可替代的作用。无论你是想夯实Java基础、备战高级面试还是渴望窥探中间件黑盒内部的奥秘这个项目都能给你带来一次酣畅淋漓的“深度游”。2. 核心架构设计与思路拆解2.1 顶层设计模块化与职责分离一个健壮的RPC框架其架构必须清晰。Jaws采用了经典的分层设计自底向上大致可以分为四层网络传输层这是框架的腿脚负责字节流的收发。我们选用Netty作为基石它提供了高性能、事件驱动的异步网络编程能力。这一层需要封装连接管理、编解码器编码/解码、心跳保活等基础能力。协议与序列化层这是框架的语言负责将Java对象转换成能在网络中传输的二进制数据以及反向转换。这一层定义了客户端和服务端交互的“报文格式”协议并集成多种序列化方式如JSON、Hessian、Protobuf、Kryo让使用者可以根据场景在性能、可读性、兼容性之间做权衡。服务治理层这是框架的大脑负责调度和协调。它包含了最体现RPC价值的核心功能服务注册与发现服务提供者启动后向注册中心如ZooKeeper、Nacos、Etcd注册自己的地址消费者从注册中心拉取或订阅可用的服务地址列表。Jaws需要抽象出通用的Registry接口以支持多种注册中心。负载均衡当有多个服务提供者时消费者需要一种策略来选择其中一个发起调用。常见的策略有随机、轮询、一致性哈希、最小连接数等。容错机制网络是不稳定的调用可能失败。我们需要提供失败重试、快速失败、故障转移等策略来保证系统的韧性。代理与桩层这是框架的面具对使用者隐藏复杂性。通过JDK动态代理或CGLib为服务接口生成一个“代理对象”。用户调用这个代理对象的方法时实际上会被拦截由代理对象完成寻址、序列化、网络发送、结果反序列化等一系列操作让远程调用看起来和本地调用一样简单。设计心得在初期务必严格定义各层之间的接口。例如网络层只关心ByteBuf的收发不关心里面是什么内容序列化层只负责Object与byte[]的转换服务治理层则操作ServiceInstance这样的领域对象。清晰的接口边界能极大降低模块间的耦合方便后续替换具体实现比如从ZooKeeper换到Nacos或进行单元测试。2.2 技术选型背后的考量为什么是Netty在Java领域进行高性能网络编程Netty几乎是唯一的选择。它封装了复杂的NIO API提供了优雅的Reactor线程模型主从多线程模型内置了丰富的编解码器和工具类如ByteBuf社区活跃文档齐全。自己从ServerSocketChannel和Selector开始写不是不行但会陷入大量底层细节偏离了构建“RPC框架”的核心目标。使用Netty我们可以站在巨人的肩膀上专注于业务逻辑。序列化方案如何选这是一个典型的权衡问题。Jaws计划支持可插拔的序列化器。JSON人类可读跨语言支持极好但序列化后的体积大性能一般。适合对调试友好度要求高、跨语言调用的场景。Hessian二进制协议比JSON高效是Dubbo的默认序列化方式之一但跨语言能力较弱。Protobuf/Thrift需要预定义IDL接口描述语言生成代码。它们以极高的编码效率和紧凑的字节体积著称是性能敏感型服务的首选但需要额外的编译步骤。Kryo纯Java序列化库速度极快序列化后的体积也很小但不支持跨语言且对类结构的变动如增删字段比较敏感。 在Jaws中我会优先实现JSON和Kryo因为它们一个代表通用性一个代表高性能且无需引入外部编译工具便于项目理解和运行。注册中心选型为了简化部署和依赖Jaws的第一个版本可能会实现一个基于内存的简单注册中心用于演示流程。但架构上必须为集成ZooKeeper、Nacos等留好扩展口。ZooKeeper强一致性CP模型适合对数据一致性要求极高的场景Nacos同时支持服务发现和配置管理AP/CP可切换更云原生。在接口设计时要抽象出register、deregister、subscribe、lookup等核心方法。3. 核心细节解析与实操要点3.1 自定义通信协议设计直接发送序列化后的字节流是不够的网络通信需要“协议”来界定一个完整消息的边界和含义。一个典型的RPC协议帧可以这样设计--------------------------------------------------------------------- | 魔数 (4字节) | 版本号(1字节) | 消息类型(1字节) | 序列化方式(1字节) | 状态(1字节) | --------------------------------------------------------------------- | 消息ID (8字节) | --------------------------------------------------------------------- | 消息体长度 (4字节) | --------------------------------------------------------------------- | 消息体内容 (变长) | ---------------------------------------------------------------------魔数通常是一个固定的数字如0xJAWS用于在TCP流中快速识别出是否是本框架的有效数据包类似于文件格式的“魔数”。版本号用于协议升级的兼容性处理。消息类型区分是请求Request、响应Response、心跳Heartbeat还是其他控制消息。序列化方式标识消息体用哪种序列化算法编码接收方据此选择对应的反序列化器。状态主要用于响应消息表示调用成功、业务异常、网络异常等。消息ID一个全局唯一的标识符可以使用Snowflake算法生成用于将异步发送的请求和后续收到的响应关联起来。消息体长度明确指示后面变长内容的字节数这是解决TCP粘包/拆包问题的关键字段之一。消息体内容序列化后的实际RPC调用信息。对于请求包含接口名、方法名、参数类型、参数值对于响应包含返回值或异常信息。在Netty中我们需要自定义MessageToByteEncoder和ByteToMessageDecoder或更简单的LengthFieldBasedFrameDecoder来实现这个协议的编解码。实操要点解决TCP粘包/拆包是网络编程的必修课。Netty提供了多种解码器对于上述定长头部变长体的协议使用LengthFieldBasedFrameDecoder是最佳实践。你需要正确配置lengthFieldOffset长度字段偏移量、lengthFieldLength长度字段自身占用的字节数这里是4、lengthAdjustment长度调整值让解码器跳过头部的哪些部分和initialBytesToStrip需要跳过的字节数比如跳过头部直接拿到消息体。多花时间理解这几个参数一劳永逸。3.2 动态代理与调用拦截这是实现“像调用本地方法一样调用远程服务”魔法的关键。以JDK动态代理为例public class JdkDynamicProxy implements InvocationHandler { private final Class? serviceInterface; private final ServiceDiscoverer discoverer; private final LoadBalancer loadBalancer; Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1. 构建RPC请求对象 RpcRequest request buildRequest(serviceInterface, method, args); // 2. 服务发现与负载均衡获取一个可用的服务提供者地址 InetSocketAddress address discoverer.discover(serviceInterface.getName()); address loadBalancer.select(addressList); // 假设discover返回列表 // 3. 通过网络客户端发送请求同步或异步并获取结果 RpcResponse response transportClient.sendRequest(address, request); // 4. 处理响应返回结果或抛出异常 if (response.hasException()) { throw response.getException(); } return response.getResult(); } }用户通过Proxy.newProxyInstance获取到这个代理对象后所有对其方法的调用都会被invoke方法拦截进而转换为远程调用流程。注意事项这里隐藏了一个复杂性——异步调用。在高并发场景下同步阻塞等待响应会浪费线程资源。更优的做法是transportClient.sendRequest返回一个Future或CompletableFuture甚至支持回调Callback。这就需要引入一个“请求-响应”的映射表以消息ID为Key暂存对应的Future。当Netty收到响应时根据其消息ID找到对应的Future并完成它。这是实现高性能RPC的关键一步。3.3 服务注册发现的实现要点即使我们先用内存注册中心演示其核心逻辑与连接外部中心无异。服务提供者端启动Netty服务端后将本机IP、端口、服务接口名、权重等信息封装成一个ServiceInstance对象调用RegistryService.register()方法进行注册。通常还需要定时发送心跳来告知注册中心“我还活着”。服务消费者端在启动或首次引用服务时调用RegistryService.subscribe(serviceName)订阅该服务的变更。注册中心客户端需要维护一个本地缓存ConcurrentHashMapString, List并监听注册中心的数据变化如ZooKeeper的Watcher实时更新缓存。这样每次调用时负载均衡器都是从新鲜的本地缓存中选取地址避免了每次调用都访问注册中心的网络开销。避坑指南服务发现的一个常见坑是“订阅不及时导致调用失败”。消费者启动时可能提供者还未注册完毕。因此消费者端需要有重试机制和容错逻辑。例如首次发现服务列表为空时不应立即抛出异常可以等待一小段时间配合重试机制或者有降级策略。此外本地缓存与注册中心的数据一致性需要根据注册中心的特性CP/AP来设计同步策略。4. 实操过程与核心环节实现4.1 搭建项目骨架与定义核心模型首先使用Maven或Gradle创建一个多模块项目是保持清晰结构的好方法。例如jaws-framework ├── jaws-core // 核心接口与抽象类 ├── jaws-registry // 注册中心抽象与实现memory, zookeeper, nacos ├── jaws-serialization // 序列化器抽象与实现json, kryo, hessian ├── jaws-transport // 网络传输层基于Netty ├── jaws-example-api // 示例API接口模块 ├── jaws-example-provider // 示例服务提供者 └── jaws-example-consumer // 示例服务消费者在jaws-core模块中定义最核心的几个模型类RpcRequest: 包含requestId,interfaceName,methodName,parameterTypes,parameters等字段。RpcResponse: 包含requestId,result,exception字段。ServiceInstance: 服务实例描述包含host,port,serviceName,metadata如权重、版本等。4.2 实现基于Netty的网络传输层服务端实现(jaws-transport模块)public class NettyRpcServer { public void start(String host, int port) { EventLoopGroup bossGroup new NioEventLoopGroup(1); // 接收连接 EventLoopGroup workerGroup new NioEventLoopGroup(); // 处理IO try { ServerBootstrap b new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializerSocketChannel() { Override protected void initChannel(SocketChannel ch) { // 解决粘包拆包 ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024*1024, 12, 4, 0, 0)); // 自定义协议解码器 (ByteBuf - RpcRequest) ch.pipeline().addLast(new RpcRequestDecoder()); // 自定义协议编码器 (RpcResponse - ByteBuf) ch.pipeline().addLast(new RpcResponseEncoder()); // 业务处理器 ch.pipeline().addLast(new NettyServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f b.bind(host, port).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } }NettyServerHandler的channelRead0方法会收到解码后的RpcRequest对象。这里需要根据interfaceName和methodName通过反射找到本地真正的服务实现类并调用其方法然后将结果封装成RpcResponse写回。客户端实现 客户端需要维护一个Channel连接池Map实现连接复用。NettyRpcClient负责获取或创建到某个地址的连接并发送请求。关键在于实现一个RpcFuture用于异步获取结果。public class RpcFuture implements FutureObject { private RpcResponse response; private CountDownLatch latch new CountDownLatch(1); // ... 其他属性如回调函数 public void done(RpcResponse response) { this.response response; latch.countDown(); // 通知等待的线程 // 执行回调... } Override public Object get() throws InterruptedException, ExecutionException { latch.await(); // 阻塞等待结果 return response.getResult(); } // ... 其他方法 }在客户端处理器NettyClientHandler中收到响应后根据response.getRequestId()从全局的pendingRequests一个ConcurrentHashMap中找到对应的RpcFuture并调用其done方法。4.3 集成序列化与负载均衡序列化器的集成应设计为可插拔。定义一个Serializer接口包含serialize(T obj)和deserialize(byte[] bytes, Class clazz)方法。通过SPIService Provider Interface机制或者简单的配置工厂模式来加载具体的实现。public interface Serializer { byte[] serialize(Object object); T T deserialize(byte[] bytes, ClassT clazz); } public class SerializerFactory { private static final MapString, Serializer SERIALIZER_CACHE new HashMap(); static { SERIALIZER_CACHE.put(json, new JsonSerializer()); SERIALIZER_CACHE.put(kryo, new KryoSerializer()); } public static Serializer getSerializer(String type) { return SERIALIZER_CACHE.getOrDefault(type, SERIALIZER_CACHE.get(json)); } }在协议编解码器中根据协议头中的“序列化方式”字段选择对应的序列化器进行消息体的编解码。负载均衡器同样抽象为LoadBalancer接口核心方法是ServiceInstance select(List instances)。实现几种经典策略RandomLoadBalancer: 随机选择。RoundRobinLoadBalancer: 轮询注意线程安全可以使用原子类。ConsistentHashLoadBalancer: 一致性哈希能保证相同参数的请求总是落到同一台机器常用于缓存路由等场景。实现时需要注意虚拟节点数量来保证均衡性。5. 常见问题与排查技巧实录在开发和测试Jaws的过程中我遇到了不少典型问题这里记录下排查思路。问题一客户端调用后一直阻塞超时后报错。排查步骤检查服务端是否启动成功看服务端日志Netty是否绑定端口成功有无异常。检查网络连通性用telnet命令测试客户端机器是否能连通服务端的IP和端口。检查协议与编解码器这是最常见的问题。确保客户端和服务端使用的协议魔数、长度字段偏移量等完全一致。在RpcRequestEncoder和RpcRequestDecoder的首尾添加日志打印出编码前和解码后的对象对比是否一致。特别注意序列化/反序列化过程中对象的类路径是否完全相同尤其是在示例中接口类最好打成独立的JAR包供提供者和消费者共同依赖。检查线程模型确保在Netty的IO线程如workerGroup中不要进行耗时的业务操作如复杂的数据库查询否则会阻塞整个EventLoop影响其他连接的处理。耗时操作应提交到独立的业务线程池。技巧在开发初期可以先用最简单的字符串如“PING”作为消息体进行收发测试绕过复杂的序列化问题先确保网络通道和基础编解码是通的。问题二服务消费者找不到可用的服务提供者。排查步骤检查注册中心服务提供者是否成功注册去ZooKeeper用zkCli或Nacos控制台查看服务列表。检查消费者订阅逻辑消费者启动时是否成功订阅并获取到了服务列表查看本地缓存Map的内容。检查服务名消费者查找的服务名是否与提供者注册的服务名完全一致大小写敏感。检查网络与防火墙注册中心地址是否可访问消费者与注册中心之间的网络是否有防火墙限制技巧在内存注册中心实现中可以添加一个简单的HTTP端点用来实时查看当前注册的所有服务实例便于调试。问题三高并发下出现性能瓶颈或内存溢出。排查方向连接池是否为每个请求都创建新连接务必实现并正确配置连接池复用TCP连接。序列化性能使用JProfiler或Arthas监控CPU热点看是否消耗在序列化上。对于内部高性能服务考虑切换到Kryo或Protobuf。线程池与队列服务端的业务线程池配置是否合理队列是否无界堆积根据压测结果调整核心线程数、最大线程数和队列大小。Netty参数调优如SO_RCVBUF/SO_SNDBUFTCP缓冲区大小、SO_BACKLOG连接等待队列、ChannelOption.ALLOCATORByteBuf分配器建议使用池化的PooledByteBufAllocator。对象池与重用频繁创建RpcRequest、RpcResponse等对象会产生大量GC压力。可以考虑使用Netty的Recycler或Apache Commons Pool来实现这些对象的池化。技巧使用异步调用CompletableFuture而非同步阻塞调用可以极大提升客户端的吞吐量避免线程因等待网络IO而被大量占用。问题四服务提供者下线后消费者仍向其发起调用导致失败。解决方案这是服务发现中“客户端缓存”带来的问题。需要引入健康检查和故障实例剔除机制。主动心跳消费者端定时向服务实例发送心跳请求失败多次则将其从本地缓存中标记为不健康或直接移除。被动通知如果注册中心支持如ZooKeeper的临时节点依靠注册中心的通知来更新缓存。失败重试与熔断在调用失败时不仅进行重试还应结合熔断器如Hystrix或Resilience4j的思路当对某个实例的失败率达到阈值时暂时熔断对该实例的请求并定期尝试恢复。构建Jaws的过程就像在组装一台精密的仪器。每一个模块、每一行代码都需要仔细斟酌其职责和边界。当你看到自己写的服务提供者成功启动消费者通过一个简单的接口调用就获取到远程结果时那种成就感是无与伦比的。这个项目带给你的绝不仅仅是一个可以写在简历上的项目经验更是对分布式系统底层通信、服务治理思想的深刻领悟。我建议你在实现基础版本后可以尝试挑战更高级的特性比如SPI扩展机制、基于注解的零配置发布与引用、与Spring Boot的集成等这会让你的“轮子”更加贴近工业级应用。

相关新闻

cesium 实战系列之图标

cesium 实战系列之图标

cesium 图标我们都知道viewer.entities.add添加个图片、文字啥的都是很简单的,网上随便一搜有都是,那么我今天就弄点不一样的。 canvas 标识牌 我们普通的标识牌只能单纯的放图片/文字,然而很多时候需要放的内容不仅仅是单纯的图片/文字&…

2026/10/18 18:43:20 阅读更多 →
SMUDebugTool完整指南:AMD Ryzen平台的SMU调试、超频与硬件排障一站搞定

SMUDebugTool完整指南:AMD Ryzen平台的SMU调试、超频与硬件排障一站搞定

SMUDebugTool完整指南:AMD Ryzen平台的SMU调试、超频与硬件排障一站搞定 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目…

2026/10/16 6:58:35 阅读更多 →
cesium 实战系列之风场进阶版

cesium 实战系列之风场进阶版

在此前我写过一个关于风场的一个文章&#xff0c;今天的这个是在区域风场的基础上做的一个案例&#xff0c;可以动态改变例子个数、线宽度、移动速度、颜色和存活时间&#xff0c;先看效果&#xff1a; 风场进阶版代码实现&#xff1a; <div classwindycanvas_list><…

2026/10/18 16:47:45 阅读更多 →

最新新闻

AI率过高怎么办?九款降AI率工具与避坑指南

AI率过高怎么办?九款降AI率工具与避坑指南

上个月帮一位在外地项目上常驻的在职朋友看论文。他发来一张截图&#xff0c;检测系统提示“疑似AI生成内容占比接近四成”&#xff0c;导师在批注里连续写了好几遍——这几段请你按自己的话重写。他挺委屈&#xff1a;内容确实是自己对着调研材料一点一点整理出来的&#xff0…

2026/10/19 23:26:19 阅读更多 →
开源代码评审工作流:本地化、可审计、可定制的LLM实践方案

开源代码评审工作流:本地化、可审计、可定制的LLM实践方案

1. 项目概述&#xff1a;这不是一个“工具”&#xff0c;而是一套可落地的开源代码评审工作流“open-code-review”这个名字乍一听像某个新发布的 CLI 工具&#xff0c;或者某家大厂刚开源的 AI 代码审查平台。但实际翻遍 GitHub、Hugging Face 和主流技术社区&#xff0c;你会…

2026/10/19 23:26:19 阅读更多 →
基于YOLO11的无人机检测识别系统设计与实现

基于YOLO11的无人机检测识别系统设计与实现

一提到毕设做深度学习目标检测&#xff0c;十个里有八个都在刷COCO、VOC数据集&#xff0c;真正能落到现实问题上的反而不多。但“空域安全无人机检测识别”这个方向不一样&#xff0c;它直接对准了一个真实且刚需的场景——机场周边、重要场馆、活动安保都需要防范低空慢速小目…

2026/10/19 23:23:08 阅读更多 →
BP-PID残差补偿控制:Simulink中可解释的神经网络增强型PID实现

BP-PID残差补偿控制:Simulink中可解释的神经网络增强型PID实现

简介&#xff1a;本资源是一套基于Simulink实现BP神经网络自整定PID控制器的完整工程实践材料&#xff0c;面向自动化、控制工程及人工智能交叉领域的高校学生、科研人员与工业控制工程师&#xff0c;解决传统PID参数依赖经验整定、适应性差、动态响应不足等实际问题。压缩包共…

2026/10/19 23:18:58 阅读更多 →
STM32 SD卡数据落盘实战:fatfs移植与CSV文件生成

STM32 SD卡数据落盘实战:fatfs移植与CSV文件生成

简介&#xff1a;这份资源面向嵌入式开发学习者与STM32工程师&#xff0c;聚焦在STM32F429平台上集成FatFS文件系统、驱动SD卡写入数据并生成.csv文件&#xff0c;同时结合以太网驱动搭建TCP服务器接收网络数据。内容覆盖FatFS的移植与参数配置、SDIO或SPI方式下的SD卡初始化与…

2026/10/19 23:16:55 阅读更多 →
AI视频生成工作流:从产品拆解到批量成片的完整指南

AI视频生成工作流:从产品拆解到批量成片的完整指南

去年年底接了一个电商代运营团队的活儿&#xff0c;帮他们把带货短视频从“每周人工憋3条”改成“一天批量出20条成片”。项目做完以后&#xff0c;团队里负责剪辑的同事跟我说了一句话&#xff0c;让我印象特别深&#xff1a;以前最怕的不是剪片子&#xff0c;而是想不出脚本、…

2026/10/19 23:05:36 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介&#xff1a;本资源为电源技术方向的学术研究文档&#xff0c;聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略&#xff0c;面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 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/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址&#xff1a; https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集&#xff0c;共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →