基于netty实现自定义Rpc框架

1.需求介绍

dubbo 底层使用了 Netty 作为网络通讯框架,要求用 Netty 实现一个简单的 RPC 框架,消费者和提
供者约定接口和协议,消费者远程调用提供者的服务,
1. 创建一个接口,定义抽象方法。用于消费者和提供者之间的约定,
2. 创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据
3. 创建一个消费者,该类需要透明的调用自己不存在的方法,通过controller方式进行轮询访问,内部需要使用 Netty 进行数据通信
4. 提供者与消费者数据传输使用json字符串数据格式
5. 提供者与消费者使用netty集成spring boot 环境实现

2.需求分析

可以通过上面的需求介绍将需求分为三个模块,一个是接口模块,一个是服务端提供者模块,一个是消费者模块。其中接口模块需要实现一个User实体类,一个IUSerService接口,一个Request,和一个Response对象,因为数据是通过json字符串的数据格式,所以通过将Request和Response请求进行封装,然后进行交互;服务端(提供者)模块需要一个启动类,在启动类中添加一个线程运行提供者服务端,还需要实现一个接口实现类,用来取数据。然后提供一个服务端类和一个业务处理逻辑类,以及一个注解,注解作用是标记实现类;最后客户端(消费者)需要实现一个客户端,以及业务处理类,还有一个代理类去调用对应的方法。

3.具体实现过程

首选需要创建一个maven项目,作为父类,然后建立三个子模块,分别是api模块(接口模块),consumer模块(消费者模块),以及provider模块(提供者模块),这里我提供了两个provider,目的是实现轮询的效果,其实可以通过修改端口,然后提供两个不同的提供者。

 其中maven的pom.xml文件实现如下

<properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.com.w.wrpcwebconsumer.WRpcWebConsumerApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

w-api模块

pom.xml文件


    <dependencies>
        <!--netty依赖 -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
        </dependency>
        <!--json依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>
        <!--lombok依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
    </dependencies>

 IUserService接口

public interface IUserService {

    /**
     * 根据ID查询用户
     *
     * @param id
     * @return
     */
    User getById(int id);
}

RPCRequest类

/**
 * 封装的请求对象
 */
@Data
public class RpcRequest {

    /**
     * 请求对象的ID
     */
    private String requestId;
    /**
     * 类名
     */
    private String className;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 参数类型
     */
    private Class<?>[] parameterTypes;
    /**
     * 入参
     */
    private Object[] parameters;

}

RPCResponse类

/**
 * 封装的响应对象
 */
@Data
public class RpcResponse {

    /**
     * 响应ID
     */
    private String requestId;

    /**
     * 错误信息
     */
    private String error;

    /**
     * 返回的结果
     */
    private Object result;

}

w-consumer类

其中pom.xml文件

<dependencies>
        <dependency>
            <groupId>com.w</groupId>
            <artifactId>w-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

 application.yml文件

spring:
  application:
    # 应用名称
    name: system
  # 开启模板缓存(默认值: true )
  thymeleaf:
    # 开启模板缓存(默认值: true )
    cache: true
    # 检查模板是否存在,然后再呈现
    check-template: true
    servlet:
      # Content-Type 的值(默认值: text/html )
      content-type: text/html
      # 开启 MVC Thymeleaf 视图解析(默认值: true )
    enabled: true
    # 模板编码
    encoding: utf-8
    # 要被排除在解析之外的视图名称列表,⽤逗号分隔
    excluded-view-names:
    # 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
    mode: HTML
    # 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
    prefix: classpath:/templates/
#  # 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
server:
  port: 8084
mybatis:
  #指定Mybatis的Mapper文件
  mapper-locations: classpath:mappers/*.xml
  #指定Mybatis的实体目录
  type-aliases-package: com.unittec.system.mybatis.entity
ignore-host-url: /external/*
logging:
  config: classpath:log/logback-spring.xml

RpcClient

import com.w.handler.NettyClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.springframework.beans.factory.DisposableBean;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


public class RpcClient implements DisposableBean {
    EventLoopGroup loopGroup;
    private Channel channel;
    String ip;
    int port;


    NettyClientHandler nettyClientHandler = new NettyClientHandler();

    private ExecutorService executorService = Executors.newCachedThreadPool();

    public RpcClient(String ip, int port) {
        this.ip = ip;
        this.port = port;
        init();
    }

    public void init() {
        try {
            System.out.println("客户端init方法执行了。。。。");
            Bootstrap bootstrap = new Bootstrap();
            loopGroup = new NioEventLoopGroup();
            bootstrap.group(loopGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(nettyClientHandler);
                        }
                    })
            ;
            ChannelFuture future = bootstrap.connect(ip, port);
            channel = future.sync().channel();
            System.out.println("客户端连接上了");
        } catch (Exception e) {
            e.printStackTrace();
            shutdown();
        }
    }


    public void destroy() throws Exception {
        shutdown();
    }

    public void shutdown() {
        if (channel != null) {
            channel.close();
        }
        if (loopGroup != null) {
            loopGroup.shutdownGracefully();
        }
    }

    public Object send(String msg) throws ExecutionException, InterruptedException {
        System.out.println("客户端执行发送数据了");
        System.out.println(msg);
        nettyClientHandler.setRequestMsg(msg);
        Future submit = executorService.submit(nettyClientHandler);
        System.out.println("客户端接收了数据" + submit.get());
        return submit.get();
    }
}

这里通过send方法,实现消费者向提供者请求发送数据,发送数据是request封装的json格式的字符串。

controller

@Controller
public class TestController {
    @RequestMapping("/test")
    @ResponseBody
    private String getUserById(HttpServletRequest request, String id) {
        Object time = request.getSession().getAttribute("time");
        int index = 0;
        if (time != null) {
            index = (Integer) time;
        }
        IUserService userService = (IUserService) RpcClientProxy.createProxy(IUserService.class,index);
        User user = userService.getById(Integer.parseInt(id));
        index++;
        request.getSession().setAttribute("time", index);
        return "id:"+user.getId() + "====" + user.getName();
    }
}

这里的controller是通过轮询的方式去调用不同的服务提供者。

NettyClientHandler


public class NettyClientHandler extends SimpleChannelInboundHandler<String> implements Callable {
    ChannelHandlerContext context;
    String requestMsg;
    String responseMsg;

    public void setRequestMsg(String requestMsg) {
        this.requestMsg = requestMsg;
    }

    /**
     * 通道就绪事件
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端与服务端连上了");
        context = ctx;
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端与服务端断开了");
    }

    /**
     * 读取通道就绪事件
     *
     * @param channelHandlerContext
     * @param msg
     * @throws Exception
     */
    @Override
    protected synchronized void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
        //读取结果
        responseMsg = msg;
        System.out.println("客户端接收到数据了。。。" + msg + "唤醒线程。。。");
        //唤醒等待的线程
        notify();
    }


    public synchronized Object call() throws Exception {
        //发送消息
        context.writeAndFlush(requestMsg);
        System.out.println("客户端发送数据了,执行了call方法。。。等待了");
        //等待
        wait();
        return responseMsg;
    }
}

因为netty实现的是异步通信,所以这里通过线程等待唤醒的方式执行接受和发送数据。

RpcClientProxy

public class RpcClientProxy {
    /**
     * 客户端代理类-创建代理对象
     * 1.封装request请求
     * 2.创建RpcClient对象
     * 3.发送消息
     * 4.返回结果
     *
     * @return
     */
    public static Object createProxy(Class serviceClass, final Integer index) {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{serviceClass}, new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                RpcRequest request = new RpcRequest();
                request.setRequestId(UUID.randomUUID().toString());
                request.setClassName(method.getDeclaringClass().getName());
                request.setMethodName(method.getName());
                request.setParameterTypes(method.getParameterTypes());
                request.setParameters(args);
                System.out.println(index);
                RpcClient rpcClient;
                if(index%2==0){
                     rpcClient = new RpcClient("127.0.0.1", 10086);
                }else {
                     rpcClient = new RpcClient("127.0.0.1", 10087);
                }
                try {
                    Object responseMsg = rpcClient.send(JSON.toJSONString(request));
                    System.out.println("收到结果了。。。");
                    RpcResponse response = JSON.parseObject(responseMsg.toString(), RpcResponse.class);
                    if (response.getError() != null) {
                        throw new RuntimeException(response.getError());
                    }
                    Object result = response.getResult();
                    return JSON.parseObject(result.toString(), method.getReturnType());
                } catch (Exception e) {
                    throw e;
                } finally {
                    rpcClient.shutdown();
                }
            }
        });
    }
}

这里通过代理的方式,实现调用。

application

@SpringBootApplication
public class ConsumerApplication  {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);

    }
}

w-provider模块

pom.xml

 <dependencies>
        <dependency>
            <groupId>com.w</groupId>
            <artifactId>w-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--spring相关依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
    </dependencies>

 RpcService接口


@Retention(RetentionPolicy.RUNTIME)//在运行时可以获取到
@Target(ElementType.TYPE)//作用于接口和类上
public @interface RpcService {
}

RpcServerHandler


@Service
@ChannelHandler.Sharable
public class RpcServerHandler extends SimpleChannelInboundHandler<String> implements ApplicationContextAware {
    private static final Map<String, Object> SERVICE_INSTANCE_MAP = new ConcurrentHashMap<>(8);


    /**
     * 将标有RpcService的类注入到map中
     *
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("服务端开始处理业务了。。。。");
        /**
         * 首先获取所有的带RpcService的实体类UserServiceImpl
         */
        Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(RpcService.class);
        if (beanMap.size() != 0) {
            //遍历实体类UserServiceImpl 各种ServiceImpl
            Set<Map.Entry<String, Object>> beans = beanMap.entrySet();
            for (Map.Entry<String, Object> bean : beans) {
                //获得当前实体类UserServiceImpl
                Object beanObject = bean.getValue();
                //获取当前实体类的所有接口IUserService
                Class<?>[] interfaces = beanObject.getClass().getInterfaces();
                //判断是否存在接口
                if (interfaces.length == 0) {
                    throw new RuntimeException(bean.getKey() + "类必须实现接口");
                }
                //获取第一个接口IUserService,将接口名字作为key,实体类作为结果,然后存入到结果中
                SERVICE_INSTANCE_MAP.put(interfaces[0].getName(), beanObject);
            }
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("有客户端连接进来了。。。。");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端掉线了。。。");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
        System.out.println("服务端接受客户端请求");
        //接受客户端请求
        RpcRequest request = JSON.parseObject(msg, RpcRequest.class);
        RpcResponse response = new RpcResponse();
        response.setRequestId(request.getRequestId());
        try {
            //处理业务端逻辑
            response.setResult(handler(request));
        } catch (Exception e) {
            //处理异常逻辑
            response.setError(e.getMessage());
        }
        //回写结果
        System.out.println("处理逻辑执行完了,准备返回了" + JSON.toJSON(response));
        channelHandlerContext.writeAndFlush(JSON.toJSONString(response));
    }

    /**
     * 业务处理类
     *
     * @param request
     * @return
     * @throws InvocationTargetException
     */
    private Object handler(RpcRequest request) throws InvocationTargetException {
        System.out.println("执行处理逻辑");
        //获取class名称
        String className = request.getClassName();
        //获取method名称
        String methodName = request.getMethodName();
        //获取参数类型
        Class<?>[] parameterTypes = request.getParameterTypes();
        //获取参数
        Object[] parameters = request.getParameters();
        //根据接口名称找到对应的类
        Object bean = SERVICE_INSTANCE_MAP.get(className);
        if (bean == null) {
            throw new RuntimeException("根据beanName找不到服务,beanName:" + className);
        }
        //通过cglib的方式反射执行对应的方法
        FastClass fastClass = FastClass.create(bean.getClass());
        FastMethod method = fastClass.getMethod(methodName, parameterTypes);
        Object invoke = method.invoke(bean, parameters);
        return invoke;
    }
}

nettyServerProvider


@Service
public class NettyServerProvider implements DisposableBean {
    NioEventLoopGroup bossGroup;
    NioEventLoopGroup workerGroup;
    @Autowired
    RpcServerHandler handler;

    public void runServer(String ip, int port) {
        try {
            bossGroup = new NioEventLoopGroup(1);
            workerGroup = new NioEventLoopGroup();
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            //todo 业务处理类
                            pipeline.addLast(handler);
                        }
                    })
            ;
            ChannelFuture future = bootstrap.bind(ip, port).sync();
            System.out.println("服务端启动。。。"+ip+port);
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.toString());
        } finally {
            shutdownGroup();
        }

    }

    @Override
    public void destroy() throws Exception {
        shutdownGroup();
    }

    public void shutdownGroup() {
        if (bossGroup != null) {
            bossGroup.shutdownGracefully();
        }
        if (workerGroup != null) {
            workerGroup.shutdownGracefully();
        }

    }
}

UserServiceImpl


@Service
@RpcService
public class UserServiceImpl implements IUserService {
    Map<Integer, User> userMap = new HashMap<>(8);

    @Override
    public User getById(int id) {
        if (userMap == null || userMap.size() == 0) {
            User u1 = new User();
            u1.setName("系统1-张三");
            u1.setId(1);
            User u2 = new User();
            u2.setId(2);
            u2.setName("系统1-李四");
            userMap.put(u1.getId(), u1);
            userMap.put(u2.getId(), u2);
        }
        return userMap.get(id);

    }
}
@SpringBootApplication
public class ProviderSpringbootApplication implements CommandLineRunner {
    @Autowired
    private NettyServerProvider provider;

    public static void main(String[] args) {
        SpringApplication.run(ProviderSpringbootApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        new Thread(new Runnable() {
            @Override
            public void run() {
                provider.runServer("127.0.0.1", 10086);
            }
        }).start();
    }
}

最后运行结果,通过访问localhost:8084/test?id=1会的得到结果。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值