手写RPC(七) 核心模块网络协议模块编写 ---- 实现客户端

相对服务端,客户端的实现就简单了许多,因为编码器已经实现好了,实现客户端的处理器:

package com.info.core;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CustomClientHandler extends SimpleChannelInboundHandler<Protocol<Response>> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Protocol<Response> msg) throws Exception {
        log.info("receive rpc server result");
        long requestId = msg.getHeader().getRequestId();
        CustomFuture<Response> future = RequestHolder.REQUEST_MAP.remove(requestId);
        future.getPromise().setSuccess(msg.getContent()); //返回结果
    }
}

这里稍微麻烦的是获取结果的方法,因为netty对我们java的nio进行了封装,提供了多路复用的IO处理方式,极大的提高了效率。因此,netty对我们的future对象进行了拓展,netty提供的Promise对象是可写的 Future,用于设置IO操作的结果。

package com.info.core;

import io.netty.util.concurrent.Promise;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@AllArgsConstructor
@Getter
@Setter
public class CustomFuture<T> {

    //Promise是可写的 Future, Future自身并没有写操作相关的接口,
    // Netty通过 Promise对 Future进行扩展,用于设置IO操作的结果
    private Promise<T> promise;
}

同时,我们需要一个管理器来管理请求id与封装结果的CustomFuture对象之间的映射关系。

package com.info.core;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

// 保存requestid和future的对应结果
public class RequestHolder {

    public static final AtomicLong REQUEST_ID = new AtomicLong();

    public static final Map<Long, CustomFuture> REQUEST_MAP = new ConcurrentHashMap<>();
}

这样我们就可以通过promise获取我们的调用结果了。
同样的,客户端也需要一个初始化器把我们所写的主键联系起来使之生效。

package com.info.protocol.netty.client;

import com.info.protocol.netty.core.codec.CustomDecoder;
import com.info.protocol.netty.core.codec.CustomEncoder;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LoggingHandler;

public class CustomClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline()
                .addLast(new LoggingHandler())
                .addLast(new CustomEncoder())
                .addLast(new CustomDecoder())
                .addLast(new CustomClientHandler());
    }
}

最后需要把我们的初始化器添加到客户端处理逻辑中。客户端相处理逻辑对简单,连接服务端发送请求。

package com.info.protocol;

import com.info.core.CustomClientInitializer;
import com.info.core.Protocol;
import com.info.core.Request;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class NettyClient {

    private final Bootstrap bootstrap;
    private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    private String serviceAddress;
    private int servicePort;

    public NettyClient(String serviceAddress, int servicePort) {
        log.info("begin init NettyClient");
        bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup)
                .channel(NioSocketChannel.class)
                .handler(new CustomClientInitializer());
        this.serviceAddress = serviceAddress;
        this.servicePort = servicePort;
    }

    public void sendRequest(Protocol<Request> protocol) throws InterruptedException {
        ChannelFuture future = bootstrap.connect(this.serviceAddress, this.servicePort).sync();
        future.addListener(listener -> {
            if (future.isSuccess()) {
                log.info("connect rpc server {} success.", this.serviceAddress);
            } else {
                log.error("connect rpc server {} failed .", this.serviceAddress);
                future.cause().printStackTrace();
                eventLoopGroup.shutdownGracefully();
            }
        });
        log.info("begin transfer data");
        // 发送数据
        future.channel().writeAndFlush(protocol);
    }
}

至此,主体代码实现完毕,下节内容开始整合各个模块准备测试。

系列文章传送门如下:
手写RPC(一) 絮絮叨叨
手写RPC(二) 碎碎念
手写RPC(三) 基础结构搭建
手写RPC(四) 核心模块网络协议模块编写 ---- netty服务端
手写RPC(五) 核心模块网络协议模块编写 ---- 自定义协议
手写RPC(六) 核心模块网络协议模块编写 ---- 实现编解码器
手写RPC(八) provider、consumer 实现
手写RPC(九) 测试
手写RPC(十) 优化
关于 LengthFieldBasedFrameDecoder 不得不说的事

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不务正业的攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值