SakuraMotion 2021 - RPC 框架
type: channel
link: https://yingwu2021.xyz/index.php/category/RPC-%E6%A1%86%E6%9E%B6/
items: 12 （优化）使用CompletableFuture优化接受服务提供端返回结果
items: 11 RPC 框架代码分析之其他模块
items: 10 RPC 框架代码分析之注册中心模块
items: 09 RPC 框架代码分析之网络传输模块
items: 08 ZooKeeper常用命令+ Curator使用详解
items: 07 静态代理+JDK/CGLIB 动态代理实战
items: 06 Netty从入门到网络通信实战
items: 05 Socket 网络通信实战
items: 04 序列化介绍以及序列化协议选择
items: 既然有 HTTP 协议，为什么还要有 RPC？

12 （优化）使用CompletableFuture优化接受服务提供端返回结果
type: item
date: 2025-12-02T18:00:00+08:00
description: AI 摘要 文章介绍将Netty RPC客户端接收服务端返回结果的方式，从阻塞的AttributeMap改为CompletableFuture，实现异步非阻塞调用，代码更清晰，使用更简单，并建议限制未处理请求Map容量防OOM。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结12 （优化）使用CompletableFuture优化接受服务提供端返回结果使用 AttributeMap 接受服务端返回结果最开始的时候是通过 AttributeMap 绑定到Channel上实现的，相关代码如下：NettyClientTransport.java（用来发送 RpcRequest 请求）NettyClientHandler.java （自定义客户端 ChannelHandler 来处理服务端发过来的数据）这种是实现的缺点是不清晰，而且你每次都要调用 channel.closeFuture().sync(); 阻塞来手动等待请求返回。使用 CompletableFuture 进行优化我使用 CompletableFuture 包装返回结果，对代码进行了重构，重要部分的代码如下：NettyClientTransport.java（用来发送 RpcRequest 请求）NettyClientHandler.java （自定义客户端 ChannelHandler 来处理服务端发过来的数据）UnprocessedRequests.java 存放了未处理的请求（建议限制 map 容器大小，避免未处理请求过多 OOM现在，在你只需要通过下面的方式就能成功接收到客户端返回的结果：CompletableFuture<RpcResponse> completableFuture = (CompletableFuture<RpcResponse>) clientTransport.sendRpcRequest(rpcRequest); rpcResponse = completableFuture.get();

11 RPC 框架代码分析之其他模块
type: item
date: 2025-11-12T02:55:00+08:00
description: AI 摘要 文章剖析RPC框架的“其他模块”：用动态代理RpcClientProxy屏蔽网络细节，统一封装请求；借Spring BeanPostProcessor，通过@RpcService发布服务、@RpcReference注入代理，实现注解式注册与消费。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结11 RPC 框架代码分析之其他模块动态代理屏蔽网络传输细节我们在前面的章节讲到过我们需要用到动态代理来屏蔽复杂的网络传输细节。对应的代码: RpcClientProxy.java@Slf4j public class RpcClientProxy implements InvocationHandler { public <T> T getProxy(Class<T> clazz) { return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) { } }当我们去调用一个远程的方法的时候，实际上是通过代理对象调用的。获取代理对象的方法如下： public <T> T getProxy(Class<T> clazz) { return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this); }网络传输细节都被封装在了 invoke() 方法中。 public Object invoke(Object proxy, Method method, Object[] args) { log.info("invoked method: [{}]", method.getName()); RpcRequest rpcRequest = RpcRequest.builder().methodName(method.getName()) .parameters(args) .interfaceName(method.getDeclaringClass().getName()) .paramTypes(method.getParameterTypes()) .requestId(UUID.randomUUID().toString()) .group(rpcServiceProperties.getGroup()) .version(rpcServiceProperties.getVersion()) .build(); RpcResponse<Object> rpcResponse = null; if (rpcRequestTransport instanceof NettyRpcClient) { CompletableFuture<RpcResponse<Object>> completableFuture = (CompletableFuture<RpcResponse<Object>>) rpcRequestTransport.sendRpcRequest(rpcRequest); rpcResponse = completableFuture.get(); } if (rpcRequestTransport instanceof SocketRpcClient) { rpcResponse = (RpcResponse<Object>) rpcRequestTransport.sendRpcRequest(rpcRequest); } this.check(rpcResponse, rpcRequest); return rpcResponse.getData(); }通过注解注册/消费服务我们这里借用了 Spring 容器相关的功能。核心代码都放在了 ： src/main/java/github/javaguide/spring 包下面。我们定义两个注解：RcpService ：注册服务RpcReference ：消费服务**RcpService.java**@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Inherited public @interface RpcService { /** * Service version, default value is empty string */ String version() default …

10 RPC 框架代码分析之注册中心模块
type: item
date: 2025-10-23T05:49:00+08:00
description: AI 摘要 文章剖析自研RPC框架的注册中心模块：定义ServiceRegistry与ServiceDiscovery接口，基于ZooKeeper实现服务注册与发现，支持多版本、多分组、多实例，配合负载均衡策略完成地址选取。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结10 RPC 框架代码分析之注册中心模块我们之前在“如何自己实现一个 RPC 框架？”这篇文章中介绍到说：注册中心负责服务地址的注册与查找，相当于目录服务。 服务端启动的时候将服务名称及其对应的地址(ip+port)注册到注册中心，服务消费端根据服务名称找到对应的服务地址。有了服务地址之后，服务消费端就可以通过网络请求服务端了。简单来说注册中心就像是一个中转站，提供的作用就是根据调用的服务名称找到远程服务的地址（数据保存服务）。注册中心模块整体结构如下：我们定义了两个接口 ServiceDiscovery.java 和 ServiceRegistry.java，这两个接口分别定义了服务发现和服务注册行为。**ServiceRegistry.java**/** * 服务注册 * * @author shuang.kou * @createTime 2020年05月13日 08:39:00 */ public interface ServiceRegistry { /** * 注册服务到注册中心 * * @param rpcServiceName 完整的服务名称（class name+group+version） * @param inetSocketAddress 远程服务地址 */ void registerService(String rpcServiceName, InetSocketAddress inetSocketAddress); }**ServiceDiscovery.java**/** * 服务发现 * * @author shuang.kou * @createTime 2020年06月01日 15:16:00 */ public interface ServiceDiscovery { /** * 根据 rpcServiceName 获取远程服务地址 * * @param rpcServiceName 完整的服务名称（class name+group+version） * @return 远程服务地址 */ InetSocketAddress lookupService(String rpcServiceName); }接下来，我们使用 zookeeper 作为注册中心的实现方式，并实现了这两个接口。**ZkServiceRegistry.java**/** * 服务注册（基于zookeeper实现） */ @Slf4j public class ZkServiceRegistry implements ServiceRegistry { @Override public void registerService(String rpcServiceName, InetSocketAddress inetSocketAddress) { String servicePath = CuratorUtils.ZK_REGISTER_ROOT_PATH + "/" + rpcServiceName + inetSocketAddress.toString(); CuratorFramework zkClient = CuratorUtils.getZkClient(); CuratorUtils.createPersistentNode(zkClient, servicePath); } }当我们的服务被注册进 zookeeper 的时候，我们将完整的服务名称 rpcServiceName （class name+group+version）作为根节点 ，子节点是对应的服务地址（ip+端口号）。class name : 服务接口名也就是类名比如：github.javaguide.HelloService。version : 服务版本。主要是为后续不兼容升级提供可能group :服务所在的组。主要用于处理一个接口有多个类实现的情况。一个根节点（rpcServiceName）可能会对应多个服务地址（相同服务被部署多份的情况）。如果我们要获得某个服务对应的地址的话，就直接根据完整的服务名称来获取到其下的所有子节点，然后通过具体的负载均衡策略取出一个就可以了。相关代码如下在 ZkServiceDiscovery.java中已经给出。**ZkServiceDiscovery.java**/** * 服务发现（基于zookeeper实现） */ @Sl…

09 RPC 框架代码分析之网络传输模块
type: item
date: 2025-09-10T04:08:00+08:00
description: AI 摘要 文章剖析了自研 RPC 框架的网络传输模块：先定义 RpcRequest/Response 实体，再抽象 RpcRequestTransport 接口，分别用 Socket 与 Netty 实现客户端与服务端收发；Netty 版引入编解码器、自定义协议及心跳保活，并借助 CompletableFuture 实现异步回调，完成高性能远程调用。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结09 RPC 框架代码分析之网络传输模块以下提到的 服务端 指的是提供服务/方法的一端，客户端 指的是调用远程(服务端)服务/方法的一端。我们之前在“如何自己实现一个 RPC 框架？”这篇文章中介绍到说：既然我们要调用远程的方法，就要发送网络请求来传递目标类和方法的信息以及方法的参数等数据到服务端。 这就涉及到了网络传输！网络传输具体实现你可以使用 Socket （ Java 中最原始、最基础的网络通信方式。但是，Socket 是阻塞 IO、性能低并且功能单一）。你也可以使用同步非阻塞的 I/O 模型 NIO ，但是用它来进行网络编程真的太麻烦了。不过没关系，你可以使用基于 NIO 的网络编程框架 Netty ，它将是你最好的选择！guide-rpc-framework 使用了一种基于 Socket，一种基于 Netty 的方式（循序渐进）。网络传输模块整体结构如下：一共被分为了 4 个包constants : 存放一些网络传输模块共用的常量dto : 用于网络传输的类。handler : 里面只有一个用于处理 rpc 请求的类RpcRequestHandler（根据 rpc 请求调用目标类的目标方法）。transport : 用户网络传输相关类（真正传输网络请求的地方。提供了 Socket 和 Netty 两种网络传输方式）。网络传输实体类网络传输实体类在 dto 包下，主要有两个类。**RpcRequest.java**rpc 请求实体类。当你要调用远程方法的时候，你需要先传输一个 RpcRequest 给对方，RpcRequest 里面包含了要调用的目标方法和类的名称、参数等数据。另外，version 字段（服务版本）主要是为后续不兼容升级提供可能。group 字段主要用于处理一个接口有多个类实现的情况。@AllArgsConstructor @NoArgsConstructor @Getter @Builder @ToString public class RpcRequest implements Serializable { private static final long serialVersionUID = 1905122041950251207L; private String requestId; private String interfaceName; private String methodName; private Object[] parameters; private Class<?>[] paramTypes; private RpcMessageType rpcMessageType; private String version; private String group; public RpcServiceProperties toRpcProperties() { return RpcServiceProperties.builder().serviceName(this.getInterfaceName()) .version(this.getVersion()) .group(this.getGroup()).build(); } }**RpcResponse.java**既然有了 rpc 请求实体类，那肯定就要有 rpc 响应实体类了。当服务端通过 RpcRequest 中的相关数据调用到目标服务的目标方法之后，调用结果就通过 RpcResponse 返回给客户端。@AllArgsConstructor @NoArgsConstructor @Getter @Setter @Builder @ToString public class RpcResponse<T> implements Serializable { private static final long serialVersionUID = 715745410605631233L; private String requestId; /** * response code */ private Integer code; /** * response message */ private String mess…

08 ZooKeeper常用命令+ Curator使用详解
type: item
date: 2025-08-28T01:41:00+08:00
description: AI 摘要 文章演示了Docker安装ZooKeeper及常用命令（create/set/get/ls/stat/delete），并详解Curator连接、节点增删改查、监听器的代码实现，覆盖持久/临时/顺序节点与重试策略。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结08 ZooKeeper常用命令+ Curator使用详解guide-rpc-framework 使用了 Zookeeper 来存储服务的相关信息 ，并且使用的是 ZooKeeper Java客户端 Curator 来对 ZooKeeper 进行增删改查等操作。所以，本文就简单介绍一下 ZooKeeper常用命令 以及 Curator 的基本使用。前言这篇文章简单给演示一下 ZooKeeper 常见命令的使用以及 ZooKeeper Java客户端 Curator 的基本使用。介绍到的内容都是最基本的操作，能满足日常工作的基本需要。如果文章有任何需要改善和完善的地方，欢迎在评论区指出，共同进步！ZooKeeper 安装和使用使用Docker 安装 zookeepera.使用 Docker 下载 ZooKeeperdocker pull zookeeper:3.5.8b.运行 ZooKeeperdocker run -d --name zookeeper -p 2181:2181 zookeeper:3.5.8连接 ZooKeeper 服务a.进入ZooKeeper容器中先使用 docker ps 查看 ZooKeeper 的 ContainerID，然后使用 docker exec -it ContainerID /bin/bash 命令进入容器中。b.先进入 bin 目录,然后通过 **./zkCli.sh -server 127.0.0.1:2181**命令连接ZooKeeper 服务root@eaf70fc620cb:/apache-zookeeper-3.5.8-bin# cd bin如果你看到控制台成功打印出如下信息的话，说明你已经成功连接 ZooKeeper 服务。常用命令演示查看常用命令(help 命令)通过 help 命令查看 ZooKeeper 常用命令创建节点(create 命令)通过 create 命令在根目录创建了 node1 节点，与它关联的字符串是"node1"[zk: 127.0.0.1:2181(CONNECTED) 34] create /node1 “node1”通过 create 命令在根目录创建了 node1 节点，与它关联的内容是数字 123[zk: 127.0.0.1:2181(CONNECTED) 1] create /node1/node1.1 123 Created /node1/node1.1更新节点数据内容(set 命令)[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 "set node1"获取节点的数据(get 命令)get 命令可以获取指定节点的数据内容和节点的状态,可以看出我们通过 set 命令已经将节点数据内容改为 "set node1"。set node1 cZxid = 0x47 ctime = Sun Jan 20 10:22:59 CST 2019 mZxid = 0x4b mtime = Sun Jan 20 10:41:10 CST 2019 pZxid = 0x4a cversion = 1 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 9 numChildren = 1查看某个目录下的子节点(ls 命令)通过 ls 命令查看根目录下的节点[zk: 127.0.0.1:2181(CONNECTED) 37] ls / [dubbo, ZooKeeper, node1]通过 ls 命令查看 node1 目录下的节点[zk: 127.0.0.1:2181(CONNECTED) 5] ls /node1 [node1.1]ZooKeeper 中的 ls 命令和 linux 命令中的 ls 类似， 这个命令将列出绝对路径 path 下的所有子节点信息（列出 1 级，并不递归）查看节点状态(stat 命令)通过 stat 命令查看节点状态[zk: 127.0.0.1:2181(CONNECTED) 10] stat /node1 cZxid = 0x47 ctime = Sun Jan 20 10:22:59 CST 2019 mZxid = 0x47 mtime = Sun Jan 20 10:22:59 CST 2019 pZxid = 0x4a cvers…

07 静态代理+JDK/CGLIB 动态代理实战
type: item
date: 2025-07-16T07:20:00+08:00
description: AI 摘要 文章通过短信发送示例，对比演示静态代理、JDK动态代理与CGLIB动态代理的编码步骤、优缺点及适用场景：静态代理编译期硬编码，维护困难；JDK动态代理基于接口生成运行时代理类；CGLIB用继承生成子类，可代理无接口类；总结三者灵活性、性能与框架应用差异。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结07 静态代理+JDK/CGLIB 动态代理实战代理模式代理模式是一种比较好的理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问，这样就可以在不修改原目标对象的前提下，提供额外的功能操作，扩展目标对象的功能。代理模式的主要作用是扩展目标对象的功能，比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。举个例子：你找了小红来帮你问话，小红就可以看作是代理你的代理对象，代理的行为（方法）是问话。https://medium.com/@mithunsasidharan/understanding-the-proxy-design-pattern-5e63fe38052a代理模式有静态代理和动态代理两种实现方式，我们 先来看一下静态代理模式的实现。静态代理静态代理中，我们对目标对象的每个方法的增强都是手动完成的（_后面会具体演示代码_），非常不灵活（_比如接口一旦新增加方法，目标对象和代理对象都要进行修改_）且麻烦(_需要对每个目标类都单独写一个代理类_)。 实际应用场景非常非常少，日常开发几乎看不到使用静态代理的场景。上面我们是从实现和应用角度来说的静态代理，从 JVM 层面来说， 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。静态代理实现步骤:定义一个接口及其实现类；创建一个代理类同样实现这个接口将目标对象注入进代理类，然后在代理类的对应方法调用目标类中的对应方法。这样的话，我们就可以通过代理类屏蔽对目标对象的访问，并且可以在目标方法执行前后做一些自己想做的事情。下面通过代码展示！1.定义发送短信的接口public interface SmsService { String send(String message); }2.实现发送短信的接口public class SmsServiceImpl implements SmsService { public String send(String message) { System.out.println("send message:" + message); return message; } }3.创建代理类并同样实现发送短信的接口public class SmsProxy implements SmsService { private final SmsService smsService; public SmsProxy(SmsService smsService) { this.smsService = smsService; } @Override public String send(String message) { //调用方法之前，我们可以添加自己的操作 System.out.println("before method send()"); smsService.send(message); //调用方法之后，我们同样可以添加自己的操作 System.out.println("after method send()"); return null; } }4.实际使用public class Main { public static void main(String[] args) { SmsService smsService = new SmsServiceImpl(); SmsProxy smsProxy = new SmsProxy(smsService); smsProxy.send("java"); } }运行上述代码之后，控制台打印出：before method send() send message:java after method send()可以输出结果看出，我们已经增强了 SmsServiceImpl 的send()方法。动态代理相比于静态代理来说，动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类，并且也不需要我们必须实现接口，我们可以直接代理实现类( _CGLIB 动态代理机制_)。从 JVM 角度来说，动态代理是在运行时动态生成类字节码，并加载到 JVM 中的。说到动态代理，Spring AOP、RPC 框架应该是两个不得不的提的，它们的实现都依赖了动态代理。动态代理在我们日常开发中使用的相对较小，但是在框架中的几乎是必…

06 Netty从入门到网络通信实战
type: item
date: 2025-06-05T01:28:00+08:00
description: AI 摘要 文章介绍Netty高性能NIO框架特点，演示用Kryo序列化实现RPC通信：定义RpcRequest/RpcResponse实体，编写Netty客户端、服务端及编解码器，完成一次请求-响应测试。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结06 Netty从入门到网络通信实战Netty 介绍简单用 3 点概括一下 Netty 吧！Netty 是一个基于 NIO 的 client-server(客户端服务器)框架，使用它可以快速简单地开发网络应用程序。它极大地简化并简化了 TCP 和 UDP 套接字服务器等网络编程,并且性能以及安全性等很多方面甚至都要更好。支持多种协议如 FTP，SMTP，HTTP 以及各种二进制和基于文本的传统协议。用官方的总结就是：Netty 成功地找到了一种在不妥协可维护性和性能的情况下实现易于开发，性能，稳定性和灵活性的方法。Netty 特点根据官网的描述，我们可以总结出下面一些特点：统一的 API，支持多种传输类型，阻塞和非阻塞的。简单而强大的线程模型。自带编解码器解决 TCP 粘包/拆包问题。自带各种协议栈。真正的无连接数据包套接字支持。比直接使用 Java 核心 API 有更高的吞吐量、更低的延迟、更低的资源消耗和更少的内存复制。安全性不错，有完整的 SSL/TLS 以及 StartTLS 支持。社区活跃成熟稳定，经历了大型项目的使用和考验，而且很多开源项目都使用到了 Netty 比如我们经常接触的 Dubbo、RocketMQ 等等。......使用 Netty 能做什么？这个应该是老铁们最关心的一个问题了，凭借自己的了解，简单说一下，理论上 NIO 可以做的事情 ，使用 Netty 都可以做并且更好。Netty 主要用来做网络通信 :作为 RPC 框架的网络通信工具 ： 我们在分布式系统中，不同服务节点之间经常需要相互调用，这个时候就需要 RPC 框架了。不同服务节点的通信是如何做的呢？可以使用 Netty 来做。比如我调用另外一个节点的方法的话，至少是要让对方知道我调用的是哪个类中的哪个方法以及相关参数吧！实现一个自己的 HTTP 服务器 ：通过 Netty 我们可以自己实现一个简单的 HTTP 服务器，这个大家应该不陌生。说到 HTTP 服务器的话，作为 Java 后端开发，我们一般使用 Tomcat 比较多。一个最基本的 HTTP 服务器可要以处理常见的 HTTP Method 的请求，比如 POST 请求、GET 请求等等。实现一个即时通讯系统 ： 使用 Netty 我们可以实现一个可以聊天类似微信的即时通讯系统，这方面的开源项目还蛮多的，可以自行去 Github 找一找。消息推送系统 ：市面上有很多消息推送系统都是基于 Netty 来做的。......哪些开源项目用到了 Netty？我们平常经常接触的 Dubbo、RocketMQ、Elasticsearch、gRPC 等等都用到了 Netty。可以说大量的开源项目都用到了 Netty，所以掌握 Netty 有助于你更好的使用这些开源项目并且让你有能力对其进行二次开发。实际上还有很多很多优秀的项目用到了 Netty,Netty 官方也做了统计，统计结果在这里：https://netty.io/wiki/related-projects.html 。Netty 使用 kryo 序列化传输对象实战注意 ：Kryo不支持没有无参构造函数的对象进行反序列化，因此如果某个对象希望使用Kryo来进行序列化操作的话，需要有相应的无参构造函数才可以。传输实体类我们首先定义两个对象，这两个对象是客户端与服务端进行交互的实体类。 客户端将 RpcRequest 类型的对象发送到服务端，服务端进行相应的处理之后将得到结果 RpcResponse 对象返回给客户端。客户端请求RpcRequest.java :客户端请求实体类@AllArgsConstructor @Getter @NoArgsConstructor @Builder @ToString public class RpcRequest { private String interfaceName; private String methodName; }服务端响应RpcResponse.java ：服务端响应实体类@AllArgsConstructor @Getter @NoArgsConstructor @Builder @ToString public class RpcResponse { private String message; }客户端初始化客户端客户端中主要有一个用于向服务端发送消息的 **sendMessage()**方法，通过这个方法你可以将消息也就…

05 Socket 网络通信实战
type: item
date: 2025-05-22T16:42:00+08:00
description: AI 摘要 文章通过实战演示 Java Socket 通信：服务端用 ServerSocket 监听，客户端用 Socket 连接，双方以对象流收发 Message。指出单线程只能处理一个连接的缺陷，提出线程池改进，并说明 BIO 模型局限，引出后续用 Netty 实现 NIO 的方案。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结05 Socket 网络通信实战05 Socket 网络通信实战前言guide-rpc-framework 的第一版使用的是 JDK 提供了 socket 进行网络编程。为了搞懂具体原理，我们首先要学会使用 Socket 进行网络通信。什么是 Socket(套接字)Socket 是一个抽象概念，应用程序可以通过它发送或接收数据。在使用 Socket 进行网络通信的时候，通过 Socket 就可以让我们的数据在网络中传输。操作套接字的时候，和我们读写文件很像。套接字是 IP 地址与端口的组合，套接字 Socket=（IP 地址：端口号）。要通过互联网进行通信，至少需要一对套接字：运行于服务器端的 Server Socket。运行于客户机端的 Client Socket在 Java 开发中使用 Socket 时会常用到两个类，都在 java.net 包中：Socket: 一般用于客户端ServerSocket :用于服务端Socket 网络通信过程Socket 网络通信过程如下图所示：https://www.javatpoint.com/socket-programmingSocket 网络通信过程简单来说分为下面 4 步：建立服务端并且监听客户端请求客户端请求，服务端和客户端建立连接两端之间可以传递数据关闭资源对应到服务端和客户端的话，是下面这样的。服务器端：创建 ServerSocket 对象并且绑定地址（ip）和端口号(port)：server.bind(new InetSocketAddress(host, port))通过 accept()方法监听客户端请求连接建立后，通过输入流读取客户端发送的请求信息通过输出流向客户端发送响应信息关闭相关资源客户端：创建Socket 对象并且连接指定的服务器的地址（ip）和端口号(port)：socket.connect(inetSocketAddress)连接建立后，通过输出流向服务器端发送请求信息通过输入流获取服务器响应的信息关闭相关资源Socket 网络通信实战服务端public class HelloServer { private static final Logger logger = LoggerFactory.getLogger(HelloServer.class); public void start(int port) { //1.创建 ServerSocket 对象并且绑定一个端口 try (ServerSocket server = new ServerSocket(port);) { Socket socket; //2.通过 accept()方法监听客户端请求 while ((socket = server.accept()) != null) { logger.info("client connected"); try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) { //3.通过输入流读取客户端发送的请求信息 Message message = (Message) objectInputStream.readObject(); logger.info("server receive message:" + message.getContent()); message.setContent("new content"); //4.通过输出流向客户端发送响应信息 objectOutputStream.writeObject(message); objectOutputStream.flush(); } catch (IOException | ClassNotFoundException e) { logger.error("occur exception:", e); } } } catch (IOException e) { logger.error("occur IOException:", e); } } pu…

04 序列化介绍以及序列化协议选择
type: item
date: 2025-04-17T08:21:00+08:00
description: AI 摘要 文章介绍序列化与反序列化概念、TCP/IP模型归属，对比JDK、Kryo、Protobuf、ProtoStuff、Hessian等协议优缺点，指出Kryo适合纯Java高性能场景，Protobuf/ProtoStuff/Hessian支持跨语言，建议按需求选型。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结04 序列化介绍以及序列化协议选择什么是序列化和反序列化?如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中，或者在网络传输 Java 对象，这些场景都需要用到序列化。简单来说：序列化： 将数据结构或对象转换成二进制字节流的过程反序列化：将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程对于 Java 这种面向对象编程语言来说，我们序列化的都是对象（Object）也就是实例化后的类(Class)，但是在 C++这种半面向对象的语言中，struct(结构体)定义的是数据结构类型，而 class 对应的是对象类型。下面是序列化和反序列化常见应用场景：对象在进行网络传输（比如远程方法调用 RPC 的时候）之前需要先被序列化，接收到序列化的对象之后需要再进行反序列化；将对象存储到文件之前需要进行序列化，将对象从文件中读取出来需要进行反序列化；将对象存储到数据库（如 Redis）之前需要用到序列化，将对象从缓存数据库中读取出来需要反序列化；将对象存储到内存之前需要进行序列化，从内存中读取出来之后需要进行反序列化。维基百科是如是介绍序列化的：序列化（serialization）在计算机科学的数据处理中，是指将数据结构或对象状态转换成可取用格式（例如存成文件，存于缓冲，或经由网络中发送），以留待后续在相同或另一台计算机环境中，能恢复原先状态的过程。依照序列化格式重新获取字节的结果时，可以利用它来产生与原始对象相同语义的副本。对于许多对象，像是使用大量引用的复杂对象，这种序列化重建的过程并不容易。面向对象中的对象序列化，并不概括之前原始对象所关系的函数。这种过程也称为对象编组（marshalling）。从一系列字节提取数据结构的反向操作，是反序列化（也称为解编组、deserialization、unmarshalling）。综上：序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。https://www.corejavaguru.com/java/serialization/interview-questions-1序列化协议对应于 TCP/IP 4 层模型的哪一层？我们知道网络通信的双方必须要采用和遵守相同的协议。TCP/IP 四层模型是下面这样的，序列化协议属于哪一层呢？应用层传输层网络层网络接口层如上图所示，OSI 七层协议模型中，表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话，就是将二进制流转换成应用层的用户数据。这不就对应的是序列化和反序列化么？因为，OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层，所以序列化协议属于 TCP/IP 协议应用层的一部分。常见序列化协议有哪些？JDK 自带的序列化方式一般不会用 ，因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff，这些都是基于二进制的序列化协议。像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好，但是性能较差，一般不会选择。JDK 自带的序列化方式JDK 自带的序列化，只需实现 java.io.Serializable接口即可。@AllArgsConstructor @NoArgsConstructor @Getter @Builder @ToString public class RpcRequest implements Serializable { private static final long serialVersionUID = 1905122041950251207L; private String requestId; private String interfaceName; private String methodName; private Object[] parameters; private Class<?>[] paramTypes; private RpcMessageTypeEnum rpcMessageTypeEnum; }serialVersionUID 有什么作用？序列化号 serialVersionUID 属于版本控制的作用。反序列化时，会检查 serialVersionUID 是否和当前类的 ser…

既然有 HTTP 协议，为什么还要有 RPC？
type: item
date: 2025-04-06T21:23:00+08:00
description: AI 摘要 HTTP 与 RPC 同属 TCP 之上的应用层协议，但定位不同：HTTP 面向浏览器通用访问，文本冗余大；RPC 面向内部服务，可定制二进制序列化、连接池，性能更高，像调本地函数。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结从 TCP 聊起作为一个程序员，假设我们需要在 A 电脑的进程发一段数据到 B 电脑的进程，我们一般会在代码里使用 Socket 进行编程。这时候，我们可选项一般也就 TCP 和 UDP 二选一。TCP 可靠，UDP 不可靠。除非是马总这种神级程序员（早期 QQ 大量使用 UDP），否则，只要稍微对可靠性有些要求，普通人一般无脑选 TCP 就对了。类似下面这样。fd = socket(AF_INET,SOCK_STREAM,0);其中 SOCK_STREAM，是指使用字节流传输数据，说白了就是 TCP 协议。在定义了 Socket 之后，我们就可以愉快的对这个 Socket 进行操作，比如用 bind() 绑定 IP 端口，用 connect() 发起建连。在连接建立之后，我们就可以使用 send() 发送数据，recv() 接收数据。光这样一个纯裸的 TCP 连接，就可以做到收发数据了，那是不是就够了？不行，这么用会有问题。使用纯裸 TCP 会有什么问题八股文常背，TCP 是有三个特点，面向连接、可靠、基于字节流。这三个特点真的概括的非常精辟，这个八股文我们没白背。每个特点展开都能聊一篇文章，而今天我们需要关注的是基于字节流这一点。字节流可以理解为一个双向的通道里流淌的数据，这个数据其实就是我们常说的二进制数据，简单来说就是一大堆 01 串。纯裸 TCP 收发的这些 01 串之间是没有任何边界的，你根本不知道到哪个地方才算一条完整消息。正因为这个没有任何边界的特点，所以当我们选择使用 TCP 发送"夏洛"和"特烦恼"的时候，接收端收到的就是"夏洛特烦恼"，这时候接收端没发区分你是想要表达"夏洛"+"特烦恼"还是"夏洛特"+"烦恼"。这就是所谓的粘包问题，之前也写过一篇专门的文章聊过这个问题。说这个的目的是为了告诉大家，纯裸 TCP 是不能直接拿来用的，你需要在这个基础上加入一些自定义的规则，用于区分消息边界。于是我们会把每条要发送的数据都包装一下，比如加入消息头，消息头里写清楚一个完整的包长度是多少，根据这个长度可以继续接收数据，截取出来后它们就是我们真正要传输的消息体。而这里头提到的消息头，还可以放各种东西，比如消息体是否被压缩过和消息体格式之类的，只要上下游都约定好了，互相都认就可以了，这就是所谓的协议。每个使用 TCP 的项目都可能会定义一套类似这样的协议解析标准，他们可能有区别，但原理都类似。于是基于 TCP，就衍生了非常多的协议，比如 HTTP 和 RPC。HTTP 和 RPC我们回过头来看网络的分层图。TCP 是传输层的协议，而基于 TCP 造出来的 HTTP 和各类 RPC 协议，它们都只是定义了不同消息格式的应用层协议而已。HTTP 协议（Hyper Text Transfer Protocol），又叫做超文本传输协议。我们用的比较多，平时上网在浏览器上敲个网址就能访问网页，这里用到的就是 HTTP 协议。而 RPC（Remote Procedure Call），又叫做远程过程调用。它本身并不是一个具体的协议，而是一种调用方式。举个例子，我们平时调用一个本地方法就像下面这样。 res = localFunc(req)如果现在这不是个本地方法，而是个远端服务器暴露出来的一个方法 remoteFunc，如果我们还能像调用本地方法那样去调用它，这样就可以屏蔽掉一些网络细节，用起来更方便，岂不美哉？ res = remoteFunc(req)基于这个思路，大佬们造出了非常多款式的 RPC 协议，比如比较有名的gRPC，thrift。值得注意的是，虽然大部分 RPC 协议底层使用 TCP，但实际上它们不一定非得使用 TCP，改用 UDP 或者 HTTP，其实也可以做到类似的功能。到这里，我们回到文章标题的问题。既然有 HTTP 协议，为什么还要有 RPC？其实，TCP 是70 年代出来的协议，而 HTTP 是 90 年代才开始流行的。而直接使用裸 TCP 会有问题，可想而知，这中间这么多年有多少自定义的协议，而这里面就有80 年代出来的 RPC。所以我们该问的不是既然有 HTTP 协议为什么要有 RPC，而是为什么有 RPC 还要有 HTTP 协议。那既然有 RPC 了，为什么还要有 HTTP 呢？现在电脑上装的各种联网软件，比如 xx 管家，xx 卫士，它们都作为客户端（Client）需要跟服务端（Server）建立连接收发消息，此时都会用到应用层协议，在这种 Clien…