实现一个简单的RPC

开始尝试搭建一个RPC框架,先从最简单的写起。

RPC(Remote Procedure Call)远程过程调用,是一个节点请求另一个节点提供的服务。

如何实现呢,很简单,创建服务端和客户端,客户端通过网络传输,告诉服务端需要调用的接口,服务端接收到客户端发送来的请求后,执行这个接口的实现类,然后返回结果。这里直接假设客户端知道服务端的地址了,后面再进行更复杂的实现。

后面的代码主要放关键部分。

照着这个原理,首先要有客户端和服务端都能访问的接口,这个接口主要就是返回一些信息。

public interface HelloService {
    String hello(HelloObject object);
}

这个对象要通过网络传输,所以要序列化

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

@Data
@AllArgsConstructor
public class HelloObject implements Serializable {
    private Integer id;
    private String message;
}

它的实现类

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloServiceImpl implements HelloService {
    private static final Logger logger = LoggerFactory.getLogger(HelloServiceImpl.class);


    public String hello(HelloObject object) {
        logger.info("服务器接收到客户端信息:{}", object.getMessage());
        return "客户端接收的返回值,id=" + object.getId();

    }
}

对于客户端发送的请求,服务端需要知道一些基本的信息才能正确调用接口

import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

@Data
@Builder
public class RPCRequest implements Serializable {
    //待调用接口名称
    private String interfaceName;

    //待调用方法名称
    private String methodName;

    //调用方法的参数
    private Object[] parameters;

    //调用方法的参数类型
    private Class<?>[] paramTypes;

}

然后服务端执行完毕后,要给客户端返回信息

import lombok.Data;

import java.io.Serializable;
@Data
//服务端调用后返回给客户端的信息
public class RPCResponse<T> implements Serializable {
    //响应状态码
    private Integer statusCode;

    //响应状态补充信息
    private String message;

    //响应数据
    private T data;

构建一个客户端对象,用来处理发送请求
使用try-with-resource来关闭资源

public class RPCClient {
    private static final Logger logger = LoggerFactory.getLogger(RPCClient.class);
    public Object sendRequest(RPCRequest rpcRequest,String host,int port){
        try(Socket socket = new Socket(host,port)){

            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            objectOutputStream.writeObject(rpcRequest);
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());

            objectOutputStream.flush();
            RPCResponse rpcResponse = (RPCResponse)objectInputStream.readObject();
            
            return rpcResponse.getData();

        }
        catch (IOException | ClassNotFoundException e) {
            logger.error("调用时有错误发生:", e);
            throw new RPCException("服务调用失败: ", e);
        }
    }
}

            

但通用接口的实现类只有服务端有,客户端并没有。所以客户端没有办法直接生成接口实现类的实例来发送给服务端。这时就可以采用动态代理的方式。

public class RPCClientProxy implements InvocationHandler {
    private static final Logger logger = LoggerFactory.getLogger(RPCClientProxy.class);
    private String host;
    private int port;
    public RPCClientProxy(String host, int port) {
        this.host = host;
        this.port = port;
    }
    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) throws Throwable {
        logger.info("调用方法: {}#{}", method.getDeclaringClass().getName(), method.getName());
        RPCRequest rpcRequest = RPCRequest.builder()
                .interfaceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .parameters(args)
                .paramTypes(method.getParameterTypes())
                .build();
        RPCClient rpcClient = new RPCClient();
        return rpcClient.sendRequest(rpcRequest, host, port);
    }

}

重写invoke()方法,它在客户端需要调用接口的时候生成一个包含所需参数的请求对象,通过之前所写的发送请求方法来将请求发送出去,然后得到客户端返回的消息。
客户端就基本写好了。
对于服务端,它需要接受服务,保存相关信息。

public interface ServiceRegistry {
    <T> void register(T service);

    Object getService(String serviceName);
}

其功能为接收到服务时,将服务加入Set集合中,同时构建一个ConcurrentHashMap来保存服务实现的接口名称。(集合方面的知识有些不熟悉,后面巩固一下)

public class ServiceRegistryImpl implements ServiceRegistry{
    private static final Logger logger = LoggerFactory.getLogger(ServiceRegistry.class);
    private final Map<String,Object> serviceMap = new ConcurrentHashMap<>();
    private final Set<String> registeredService = ConcurrentHashMap.newKeySet();

    @Override
    public synchronized <T> void register(T service) {
        String serviceName = service.getClass().getCanonicalName();
        if(registeredService.contains(serviceName))
            return;
        registeredService.add(serviceName);
        Class<?>[] interfaces = service.getClass().getInterfaces();
        if(interfaces.length == 0){
            throw new RPCException(RPCError.SERVICE_NOT_IMPLEMENT_ANY_INTERFACE);
        }
        for(Class<?> i:interfaces){
            serviceMap.put(i.getCanonicalName(),service);
        }
        logger.info("向接口 {} 注册服务 {}",interfaces,serviceName);

    }

    @Override
    public synchronized Object getService(String serviceName) {
        Object service = serviceMap.get(serviceName);
        if(service == null)
            throw new RPCException(RPCError.SERVICE_NOT_FOUND);
        return service;
    }
}

之后创建一个线程,这个线程的作用是接收需要提供服务的对象,并将其发送出去

public class RequestHandlerThread implements Runnable{
    private static final Logger logger = LoggerFactory.getLogger(RequestHandlerThread.class);

    private Socket socket;
    private RequestHandler requestHandler;
    private ServiceRegistry serviceRegistry;

    public RequestHandlerThread(Socket socket, RequestHandler requestHandler, ServiceRegistry serviceRegistry) {
        this.socket = socket;
        this.requestHandler = requestHandler;
        this.serviceRegistry = serviceRegistry;
    }
    @Override
    public void run() {
        try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) {
            RPCRequest rpcRequest = (RPCRequest) objectInputStream.readObject();
            String interfaceName = rpcRequest.getInterfaceName();
            Object service = serviceRegistry.getService(interfaceName);
            Object result = requestHandler.handle(rpcRequest, service);
            objectOutputStream.writeObject(RPCResponse.success(result));
            objectOutputStream.flush();
        } catch (IOException | ClassNotFoundException e) {
            logger.error("调用或发送时有错误发生:", e);
        }
    }
}

之后有专门处理的类,通过反射,获取服务需要执行的方法并调用

public class RequestHandler {
    private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class);
    public Object handle(RPCRequest rpcRequest,Object service){
        Object result = null;
        try{
            result = invokeTargetMethod(rpcRequest, service);
            logger.info("服务:{} 成功调用方法:{}", rpcRequest.getInterfaceName(), rpcRequest.getMethodName());
        } catch (IllegalAccessException | InvocationTargetException e) {
            logger.error("调用或发送时有错误发生:", e);
        } return result;
    }


    private Object invokeTargetMethod(RPCRequest rpcRequest,Object service) throws InvocationTargetException, IllegalAccessException {
        Method method;
        try {
            method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());
        } catch (NoSuchMethodException e) {
            return RPCResponse.fail(ResponseCode.METHOD_NOT_FOUND);
        }
        return method.invoke(service, rpcRequest.getParameters());

    }
}

最后便是服务端的构建,以线程池的方式构建服务端。
前面的准备工作已做好,在创建服务端时,传入一个已注册的服务,便可直接启动。

public class RPCServer {

    private static final Logger logger = LoggerFactory.getLogger(RPCServer.class);
    private static final int CORE_POOL_SIZE = 5;
    private static final int MAXIMUM_POOL_SIZE = 50;
    private static final int KEEP_ALIVE_TIME = 60;
    private static final int BLOCKING_QUEUE_CAPACITY = 100;
    private final ExecutorService threadPool;
    private RequestHandler requestHandler = new RequestHandler();
    private final ServiceRegistry serviceRegistry;

    public RPCServer(ServiceRegistry serviceRegistry){
        this.serviceRegistry = serviceRegistry;
        BlockingQueue<Runnable> workingQueue = new ArrayBlockingQueue<>(BLOCKING_QUEUE_CAPACITY);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,workingQueue,threadFactory);

    }
    public void start(int port) {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            logger.info("服务器启动……");
            Socket socket;
            while((socket = serverSocket.accept()) != null) {
                logger.info("消费者连接: {}:{}", socket.getInetAddress(), socket.getPort());
                threadPool.execute(new RequestHandlerThread(socket, requestHandler, serviceRegistry));
            }
            threadPool.shutdown();
        } catch (IOException e) {
            logger.error("服务器启动时有错误发生:", e);
        }
    }


}

先启动服务端

public class ServerTest {
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        ServiceRegistry serviceRegistry = new ServiceRegistryImpl();
        serviceRegistry.register(helloService);
        RPCServer rpcServer = new RPCServer(serviceRegistry);
        rpcServer.start(9000);

    }
}

然后启动客户端

public class ClientTest {
    public static void main(String[] args) {
        RPCClientProxy proxy = new RPCClientProxy("127.0.0.1", 9000);
        HelloService helloService = proxy.getProxy(HelloService.class);
        HelloObject object = new HelloObject(12, "这是客户端传来的消息");
        String res = helloService.hello(object);
        System.out.println(res);

    }
}

在这里插入图片描述
在这里插入图片描述
简单的实现就完成了。
这一个小项目对反射,线程的知识运用得较多,我也感觉到自己这方面还有很大的空白,需要马上学习。

参考资料
https://blog.csdn.net/qq_40856284/article/details/106972652
https://github.com/Snailclimb/guide-rpc-framework

RPC(Remote Procedure Call)是一种远程调用协议,它允许客户端程序调用远程服务器上的函数或方法。C++可以使用一些库来实现RPC服务,其中比较流行的有 gRPC 和 Apache Thrift。 以下是一个使用 gRPC 的简易 RPC 服务的示例: 1. 首先,需要安装 gRPC 和 Protocol Buffers: ``` sudo apt install -y build-essential autoconf libtool pkg-config grpc libgrpc++-dev protobuf-compiler-grpc ``` 2. 创建一个 Protocol Buffers 文件 `example.proto`,定义 RPC 服务的接口: ``` syntax = "proto3"; package example; service ExampleService { rpc SayHello (HelloRequest) returns (HelloResponse) {} } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } ``` 3. 使用 Protocol Buffers 编译器生成 C++ 代码: ``` protoc --grpc_out=. --cpp_out=. example.proto ``` 4. 实现 RPC 服务的接口: ``` #include <iostream> #include <memory> #include <string> #include <grpcpp/grpcpp.h> #include "example.grpc.pb.h" using grpc::Server; using grpc::ServerBuilder; using grpc::ServerContext; using grpc::Status; using example::HelloRequest; using example::HelloResponse; using example::ExampleService; class ExampleServiceImpl final : public ExampleService::Service { Status SayHello(ServerContext* context, const HelloRequest* request, HelloResponse* response) override { std::string prefix("Hello "); response->set_message(prefix + request->name()); return Status::OK; } }; void RunServer() { std::string server_address("0.0.0.0:50051"); ExampleServiceImpl service; grpc::ServerBuilder builder; builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); builder.RegisterService(&service); std::unique_ptr<Server> server(builder.BuildAndStart()); std::cout << "Server listening on " << server_address << std::endl; server->Wait(); } int main(int argc, char** argv) { RunServer(); return 0; } ``` 5. 编译并运行服务器代码: ``` g++ -std=c++11 -I. -I/usr/local/include -L/usr/local/lib example.pb.cc example.grpc.pb.cc example_server.cc -lgrpc++ -lgrpc -lgpr -lprotobuf -lpthread -o example_server ./example_server ``` 6. 编写客户端代码: ``` #include <iostream> #include <memory> #include <string> #include <grpcpp/grpcpp.h> #include "example.grpc.pb.h" using grpc::Channel; using grpc::ClientContext; using grpc::Status; using example::HelloRequest; using example::HelloResponse; using example::ExampleService; class ExampleClient { public: ExampleClient(std::shared_ptr<Channel> channel) : stub_(ExampleService::NewStub(channel)) {} std::string SayHello(const std::string& name) { HelloRequest request; request.set_name(name); HelloResponse response; ClientContext context; Status status = stub_->SayHello(&context, request, &response); if (status.ok()) { return response.message(); } else { return "RPC failed"; } } private: std::unique_ptr<ExampleService::Stub> stub_; }; int main(int argc, char** argv) { ExampleClient client(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials())); std::string name("World"); std::string reply = client.SayHello(name); std::cout << "Received: " << reply << std::endl; return 0; } ``` 7. 编译并运行客户端代码: ``` g++ -std=c++11 -I. -I/usr/local/include -L/usr/local/lib example.pb.cc example.grpc.pb.cc example_client.cc -lgrpc++ -lgrpc -lgpr -lprotobuf -lpthread -o example_client ./example_client ``` 以上是一个简易的使用 gRPC 实现RPC 服务和客户端的示例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值