如何写一个RPC框架(五):服务器端实现

在后续一段时间里, 我会写一系列文章来讲述如何实现一个RPC框架(我已经实现了一个示例框架, 代码在我的github上)。 这是系列第五篇文章, 主要讲述了服务器端的实现。

在前面的几篇文章里, 我们已经实现了客户端创建proxy bean, 并利用它来发送请求、处理返回的全部流程:

  1. 扫描package找出需要代理的service
  2. 通过服务注册中心和Load Balancer获取service地址
  3. 利用Netty与service建立连接, 并且复用所创建的channel
  4. 创建request, 用唯一的requestId来标识它, 发送这个请求, 调用future.get()
  5. 收到response,利用response中附带的requestId找到对应future,让future变成done的状态

这篇文章, 我们会介绍server端的实现。

1.获取server端所实现的接口

private List<Class<?>> getServiceInterfaces(ApplicationContext ctx) {
        Class<? extends Annotation> clazz = RPCService.class;
        return ctx.getBeansWithAnnotation(clazz)
                .values().stream()
                .map(AopUtils::getTargetClass)
                .map(cls -> Arrays.asList(cls.getInterfaces()))
                .flatMap(List::stream)
                .filter(cls -> Objects.nonNull(cls.getAnnotation(clazz)))
                .collect(Collectors.toList());
    }

利用Spring的ApplicationContext, 获取bean容器中带有RPCService注解的bean。

2.bean与对应的接口名一一对应并保存在map中

private Map<String, Object> handlerMap = new HashMap<>();
getServiceInterfaces(ctx)
                .stream()
                .forEach(interfaceClazz -> {
                    String serviceName = interfaceClazz.getAnnotation(RPCService.class).value().getName();
                    Object serviceBean = ctx.getBean(interfaceClazz);
                    handlerMap.put(serviceName, serviceBean);
                    log.debug("Put handler: {}, {}", serviceName, serviceBean);
                });

handlerMap的作用是, 收到请求时, 可以通过这个map找到该请求所对应的处理对象。

3.启动服务器并注册所实现的service

private void startServer() {
        // Get ip and port
        String[] addressArray = StringUtils.split(serviceAddress, ":");
        String ip = addressArray[0];
        int port = Integer.parseInt(addressArray[1]);

        log.debug("Starting server on port: {}", port);
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel channel) throws Exception {
                            ChannelPipeline pipeline = channel.pipeline();
                            pipeline.addLast(new RPCDecoder(RPCRequest.class, new ProtobufSerializer()));
                            pipeline.addLast(new RPCEncoder(RPCResponse.class, new ProtobufSerializer()));
                            pipeline.addLast(new RPCServerHandler(handlerMap));
                        }
                    });
            bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture future = bootstrap.bind(ip, port).sync();
            log.info("Server started");

            registerServices();

            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException("Server shutdown!", e);
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

private void registerServices() {
        if (serviceRegistry != null && serviceAddress != null) {
            for (String interfaceName : handlerMap.keySet()) {
                serviceRegistry.register(interfaceName, serviceAddress.toString());
                log.info("Registering service: {} with address: {}", interfaceName, serviceAddress);
            }
        }
    }

这里几个地方需要说明一下:

  1. 这里的ip和port我直接用了配置文件中传入的, 优化的方案应该是获取本地ip以及找到一个可用端口
  2. 利用Netty创建server, 在pipeline中加入RPCServerHandler, 这个handler将在下文给出
  3. 向服务注册中心注册实现的所有服务

4.RPCServerHandler的实现

@AllArgsConstructor
public class RPCServerHandler extends SimpleChannelInboundHandler<RPCRequest> {

    private Map<String, Object> handlerMap;

    @Override
    public void channelRead0(final ChannelHandlerContext ctx, RPCRequest request) throws Exception {
        log.debug("Get request: {}", request);
        RPCResponse response = new RPCResponse();
        response.setRequestId(request.getRequestId());
        try {
            Object result = handleRequest(request);
            response.setResult(result);
        } catch (Exception e) {
            log.warn("Get exception when hanlding request, exception: {}", e);
            response.setException(e);
        }
        ctx.writeAndFlush(response).addListener(
                (ChannelFutureListener) channelFuture -> {
                    log.debug("Sent response for request: {}", request.getRequestId());
                });
    }

    private Object handleRequest(RPCRequest request) throws Exception {
        // Get service bean
        String serviceName = request.getInterfaceName();
        Object serviceBean = handlerMap.get(serviceName);
        if (serviceBean == null) {
            throw new RuntimeException(String.format("No service bean available: %s", serviceName));
        }

        // Invoke by reflect
        Class<?> serviceClass = serviceBean.getClass();
        String methodName = request.getMethodName();
        Class<?>[] parameterTypes = request.getParameterTypes();
        Object[] parameters = request.getParameters();
        Method method = serviceClass.getMethod(methodName, parameterTypes);
        method.setAccessible(true);
        return method.invoke(serviceBean, parameters);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("server caught exception", cause);
        ctx.close();
    }
}

这个实现相当简单,收到请求之后, 根据servicename,找到对应的handler,再利用反射进行方法调用。

就这样, 一个简单的RPCServer就实现了。 完整代码请看我的github

在当今数字化教育蓬勃发展的背景下,校园网络作为教学与科研的关键基础设施,其重要性日益凸显。本文旨在探讨小型校园网络的规划与设计,以满足网络实验教学的需求,为相关专业师生提供一个高效、稳定且功能完备的网络实验环境,助力教学活动顺利开展,提升学生的实践能力和创新思维。 网络实验教学要求校园网络具备高度的灵活性与可扩展性。学生需在实验过程中模拟各种网络拓扑结构、配置不同网络设备参数,这就要求网络能够快速调整资源分配,适应多样化的实验场景。同时,为保证实验数据的准确性和实验过程的稳定性,网络的高可靠性与低延迟特性不可或缺。此外,考虑到校园内多用户同时接入的场景,网络还需具备良好的并发处理能力,确保每位用户都能流畅地进行实验操作。 采用层次化结构构建小型校园网络,分为核心层、汇聚层与接入层。核心层选用高性能交换机,负责高速数据转发与关键路由决策,保障网络主干的稳定运行;汇聚层连接不同教学区域,实现数据的汇聚与初步处理,通过划分虚拟局域网(VLAN)对不同专业或班级的实验流量进行隔离,避免相互干扰;接入层则直接连接学生终端设备,提供充足的接入端口,满足大量用户同时接入的需求,并通过端口安全策略限制非法设备接入,保障网络安全。 在设备选型上,核心层交换机需具备高吞吐量、低延迟以及丰富的路由协议支持能力,以满足复杂网络流量的转发需求;汇聚层交换机则注重VLAN划分与管理功能,以及对链路聚合的支持,提升网络的可靠性和带宽利用率;接入层交换机则需具备高密度端口、灵活的端口配置以及完善的用户认证功能。配置方面,通过静态路由与动态路由协议相结合的方式,确保网络路径的最优选择;在汇聚层与接入层设备上启用VLAN Trunk技术,实现不同VLAN间的数据交换;同时,利用网络管理软件对设备进行集中监控与管理,实时掌握网络运行状态,及时发现并解决潜在问题。 网络安全是校园网络规划的关键环节。在接入层设置严
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值