手写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 不得不说的事

猜你喜欢

转载自blog.csdn.net/hxj413977035/article/details/121604516