【手写一个RPC框架】simpleRPC-04

2023-11-06

在这里插入图片描述

本项目所有代码可见:https://github.com/weiyu-zeng/SimpleRPC

前言

之前谈到,网络传输使用BIO的方式,我们在simpleRPC-04改为NIO来传输,引入netty的编解码方式。

我们在simpleRPC-04中将使用netty来优化我们的客户端client和服务端server,

实现

项目创建

创建module:simpleRPC-04
在这里插入图片描述
在java下创建package:com.rpc

配置依赖

我们配置pom.xml依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SimpleRPC</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>simpleRPC-04</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.51.Final</version>
        </dependency>
    </dependencies>

</project>

common

我们的common和simpleRPC-03是一样的:

Blog.java

package com.rpc.common;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;


@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Blog implements Serializable {
    private Integer id;
    private Integer useId;
    private String title;
}

RPCRequest.java

package com.rpc.common;


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[] params;
    // 参数类型
    private Class<?>[] paramsTypes;
}

RPCResponse.java

package com.rpc.common;

import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

/**
 * 服务器端回应的抽象
 */
@Data
@Builder
public class RPCResponse implements Serializable {

    // 状态信息
    private int code;

    private String message;
    // 具体数据
    private Object data;

    public static RPCResponse success(Object data) {
        return RPCResponse.builder().code(200).data(data).build();
    }

    public static RPCResponse fail() {
        return RPCResponse.builder().code(500).message("服务器发生错误").build();
    }
}

User.java

package com.rpc.common;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;


/**
 * @author zwy
 *
 * 定义简单User信息
 */

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    // 客户端和服务端共有的
    private Integer id;
    private String userName;
    private Boolean sex;
}

service

同样的,service和simpleRPC-04是一样的:

ServiceProvider.java

package com.rpc.service;


import java.util.HashMap;
import java.util.Map;

/**
 * @author zwy
 */
public class ServiceProvider {
    /**
     * 一个实现类可能实现多个接口
     */
    private Map<String, Object> interfaceProvider;

    // 构造函数,初始化一个空的 hashmap 赋给 Map<String, Object> interfaceProvider
    public ServiceProvider() {
        this.interfaceProvider = new HashMap<>();
    }

    public void provideServiceInterface(Object service) {
        // 反射,.getClass().getInterfaces()得到class的interface,按照interfaces name(key)和object(value)存入map
        Class<?>[] interfaces = service.getClass().getInterfaces();
        for (Class clazz : interfaces) {
            interfaceProvider.put(clazz.getName(), service);
        }
    }

    public Object getService(String interfaceName) {
        return interfaceProvider.get(interfaceName);  // 通过interface name得到object
    }
}

BlogService.java

package com.rpc.service;

import com.rpc.common.Blog;

public interface BlogService {

    Blog getBlogById(Integer id);
}

BlogServiceImpl.java

package com.rpc.service;

import com.rpc.common.Blog;

public class BlogServiceImpl implements BlogService {

    @Override
    public Blog getBlogById(Integer id) {
        Blog blog = Blog.builder()
                        .id(id)
                        .title("我的博客")
                        .useId(22).build();
        System.out.println("客户端查询了" + id + "博客");
        return blog;
    }
}

UserService.java

package com.rpc.service;

import com.rpc.common.User;

/**
 * @author zwy
 *
 * 服务器端提供服务的方法的接口
 */
public interface UserService {

    // 客户端通过这个接口调用服务端的实现类
    User getUserByUserId(Integer id);

    // 给这个服务增加一个功能
    Integer insertUserId(User user);
}

UserServiceImpl.java

package com.rpc.service;

import com.rpc.common.User;


/**
 * @author zwy
 *
 * 服务器端提供服务的方法
 */
public class UserServiceImpl implements UserService {

    @Override
    public User getUserByUserId(Integer id) {
        // 模拟从数据库中取用户的行为
        User user = User.builder()
                        .id(id)
                        .userName("he2121")
                        .sex(true).build();
        System.out.println("客户端查询了" + id + "的用户");
        return user;
    }

    @Override
    public Integer insertUserId(User user) {
        System.out.println("插入数据成功: " + user);
        return 1;
    }
}

server

我们的simpleRPC-04客户端和服务端用netty进行改进,

我们的PRCServer.java接口和simpleRPC-03一样:

RPCServer.java

package com.rpc.server;

/**
 * @author weiyu_zeng
 *
 * RPC服务器端:接受,解析request,封装,发送response
 *
 */
public interface RPCServer {
    void start(int port);
    void stop();
}

我们创建一个NettyRPCServer.java来实现RPCServer接口:

代码和注释如下:

package com.rpc.server;

import com.rpc.service.ServiceProvider;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.AllArgsConstructor;


/**
 * @author weiyu_zeng
 *
 * 实现RPCServer接口,负责监听与发送数据
 *
 * NioEventLoopGroup:实际上就是一个线程池,里面有可执行的Executor#Runnable,同时继承了Iterable 迭代器
 * 每一个 NioEventLoopGroup 中都包含了多个NioEventLoop,而每个 NioEventLoop 又绑定着一个线程。
 * 一个 NioEventLoop 可以处理多个 Channel 中的 IO 操作,而其只有一个线程。所以对于这个线程资源的使用,
 * 就存在了竞争。此时为每一个 NioEventLoop都绑定了一个多跑复用器 Selector,由 Selector 来决定当前 NioEventLoop
 * 的线程为哪些 Channel 服务。
 *
 * ServerBootstrap:负责初始化netty服务器,并且开始监听端口的socket请求。
 * ServerBootstrap用一个ServerSocketChannelFactory 来实例化。ServerSocketChannelFactory 有两种选择,
 * 一种是NioServerSocketChannelFactory,一种是OioServerSocketChannelFactory。前者使用NIO,后则使用普通的阻塞式IO。
 * 它们都需要两个线程池实例作为参数来初始化,一个是boss线程池,一个是worker线程池。
 *
 * ServerBootstrap.bind(int):负责绑定端口,当这个方法执行后,ServerBootstrap就可以接受指定端口上的socket连接了。
 * 一个ServerBootstrap可以绑定多个端口。bind方法会创建一个serverchannel,并且会将当前的channel注册到eventloop上面.
 *
 * ChannelFuture:最顶层是继承的jdk 的Future 接口,Future 类就是代表了异步计算的结果。
 * Netty 里面的IO操作全部是异步的。这意味着,IO操作会立即返回,但是在调用结束时,无法保证IO操作已完成。取而代之,
 * 将会返回给你一个ChannelFuture 实例,提供IO操作的结果信息或状态。
 * channelFuture.channel():返回ChannelFuture关联的Channel;
 * channelFuture.channel().closeFuture().sync()相当于在这里阻塞,直到serverchannel关闭。
 *
 * shutdownGracefully():所有现有channel将自动关闭,并且应拒绝重新连接尝试
 */
@AllArgsConstructor
public class NettyRPCServer implements RPCServer {
    private ServiceProvider serviceProvider;

    @Override
    public void start(int port) {
        // netty服务线程组负责建立连接(TCP/IP连接),work负责具体的请求
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        System.out.println("Netty服务端启动了");

        try {
            // 启动Netty服务器
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            // 初始化
            serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
                           .childHandler(new NettyServerInitializer(serviceProvider));
            // 同步阻塞
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            // 死循环监听
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

    @Override
    public void stop() {

    }
}

NettyServerInitializer.java

package com.rpc.server;

import com.rpc.service.ServiceProvider;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolver;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import lombok.AllArgsConstructor;

/**
 * @author weiyu_zeng
 *
 * 初始化,主要负责序列化的编码解码, 需要解决netty的粘包问题
 *
 * ChannelInitializer;它提供了一个简单的方法来初始化一个 Channel,一种特殊的ChannelInboundHandler,
 * 用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始化操作。
 * ChannelPipeline:是ChannelHandler的容器,它负责ChannelHandler的管理和事件拦截与调度。
 * 内部维护了一个ChannelHandler的链表和迭代器,可以方便地实现ChannelHandler查找、添加、替换和删除
 *
 * Netty的消息传递都是基于流,通过Channel和Buffer传递的,自然,Object也需要转换成Channel和Buffer来传递
 * Netty本身已经给我们写好了这样的转换工具。ObjectEncoder和ObjectDecoder:
 * Netty给我们处理自己业务的空间是在灵活的可子定义的Handler上的,也就是说,如果我们自己去做这个转换工作,
 * 那么也应该在Handler里去做。而Netty,提供给我们的ObjectEncoder和Decoder也恰恰是一组Handler
 *
 * LengthFieldBasedFrameDecoder:自定义长度帧解码器,解码器自定义长度解决TCP粘包黏包问题。(
 *  TCP粘包是指发送方发送的若干个数据包到接收方时粘成一个包。从接收缓冲区来看,后一个包数据的头紧接着前一个数据的尾
 *  当TCP连接建立后,Client发送多个报文给Server,TCP协议保证数据可靠性,但无法保证Client发了n个包,服务端也按照n个包接收。
 *  Client端发送n个数据包,Server端可能收到n-1或n+1个包。
 *  )
 *  如何解决粘包现象
 * 1. 添加特殊符号,接收方通过这个特殊符号将接收到的数据包拆分开 - DelimiterBasedFrameDecoder特殊分隔符解码器
 * 2. 每次发送固定长度的数据包 - FixedLengthFrameDecoder定长编码器
 * 3. 在消息头中定义长度字段,来标识消息的总长度 - LengthFieldBasedFrameDecoder自定义长度解码器 √
 */
@AllArgsConstructor
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
    private ServiceProvider serviceProvider;
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 解码器:消息格式 [长度][消息体],解决粘包问题
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4,
                        0, 4));
        // 编码器:计算当前待大宋消息的长度,写入到前4个字节中
        pipeline.addLast(new LengthFieldPrepender(4));

        // 这里使用的还是java 序列化方式, netty的自带的解码编码支持传输这种结构
        pipeline.addLast(new ObjectEncoder());
        pipeline.addLast(new ObjectDecoder(new ClassResolver() {
            @Override
            public Class<?> resolve(String className) throws ClassNotFoundException {
                return Class.forName(className);
            }
        }));

        pipeline.addLast(new NettyRPCServerHandler(serviceProvider));
    }
}

NettyRPCServerHandler.java

package com.rpc.server;

import com.rpc.common.RPCRequest;
import com.rpc.common.RPCResponse;
import com.rpc.service.ServiceProvider;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.AllArgsConstructor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author weiyu_zeng
 *
 * ChannelHandlerContext.writeAndFlush:方法会将数据写到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler开始处理。
 */
@AllArgsConstructor
public class NettyRPCServerHandler extends SimpleChannelInboundHandler<RPCRequest> {
    private ServiceProvider serviceProvider;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RPCRequest msg) throws Exception {
        // System.out.println(msg);
        RPCResponse response = getResponse(msg);
        ctx.writeAndFlush(response);
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    // 这里和WorkThread里的getResponse差不多
    RPCResponse getResponse(RPCRequest request) {
        // 得到服务名
        String interfaceName = request.getInterfaceName();
        // 得到服务器相应类
        Object service = serviceProvider.getService(interfaceName);
        // 反射调用方法
        Method method = null;
        try {
            method = service.getClass().getMethod(request.getMethodName(), request.getParamsTypes());
            Object invoke = method.invoke(service, request.getParams());
            return RPCResponse.success(invoke);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
            System.out.println("方法执行错误");
            return RPCResponse.fail();
        }
    }
}

定义服务端:TestServer.java

package com.rpc.server;

import com.rpc.service.*;

public class TestServer {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        BlogService blogService = new BlogServiceImpl();

//        Map<String, Object> serviceProvide = new HashMap<>();
//        serviceProvide.put("com.ganghuan.myRPCVersion2.service.UserService",userService);
//        serviceProvide.put("com.ganghuan.myRPCVersion2.service.BlogService",blogService);
        ServiceProvider serviceProvider = new ServiceProvider();
        serviceProvider.provideServiceInterface(userService);  // 把userService存入 serviceProvider
        serviceProvider.provideServiceInterface(blogService);  // 把blogService存入 serviceProvider

//        RPCServer RPCServer = new ThreadPoolRPCRPCServer(serviceProvider);
        RPCServer RPCServer = new NettyRPCServer(serviceProvider);
        RPCServer.start(8899);
    }
}

client

接下来是客户端的改造

RPCClient.java和simpleRPC-03一样:

package com.rpc.client;


import com.rpc.common.RPCRequest;
import com.rpc.common.RPCResponse;

/**
 * @author weiyu_zeng
 *
 * RPC客户端:发送请求,获得response
 */
public interface RPCClient {
    RPCResponse sendRequest(RPCRequest request);
}

RPCClientProxy.java 代理类需要稍微修改:

package com.rpc.client;

import com.rpc.common.RPCRequest;
import com.rpc.common.RPCResponse;
import lombok.AllArgsConstructor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author weiyu_zeng
 *
 * 客户端代理:把动态代理封装request对象(这里和simpleRPC-02的ClientProxy函数一样,保留了动态代理的设计)
 */
@AllArgsConstructor
public class RPCClientProxy implements InvocationHandler {
    private RPCClient client;

    // jdk动态代理,每一次代理对象调用方法,会经过此方法增强(反射获取request对象,socket发送至客户端)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // request的构建,使用了lombok中的builder,更加简洁
        RPCRequest request = RPCRequest.builder().interfaceName(method.getDeclaringClass().getName())
                                       .methodName(method.getName())
                                       .params(args)
                                       .paramsTypes(method.getParameterTypes())
                                       .build();
        // 数据传输
        RPCResponse response = client.sendRequest(request);
//        System.out.println(response);
        return response.getData();
    }

    <T> T getProxy(Class<T> clazz) {
        Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this);
        return (T)o;
    }
}

NettyRPCClient.java

package com.rpc.client;

import com.rpc.common.RPCRequest;
import com.rpc.common.RPCResponse;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;

/**
 * @author weiyu_zeng
 *
 * 实现RPCClient接口
 */
public class NettyRPCClient implements RPCClient{
    private static final Bootstrap bootstrap;
    private static final EventLoopGroup evenLoopGroup;
    private String host;
    private int port;

    // 构造函数
    public NettyRPCClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    // netty客户端初始化,重复使用
    static {
        evenLoopGroup = new NioEventLoopGroup();
        bootstrap = new Bootstrap();
        bootstrap.group(evenLoopGroup).channel(NioSocketChannel.class).handler(new NettyClientInitializer());
    }

    // 这里需要操作一下,因为netty的传输都是异步的,你发送request,会立刻返回一个值, 而不是想要的相应的response
    @Override
    public RPCResponse sendRequest(RPCRequest request) {
        try {
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            Channel channel = channelFuture.channel();
            // 发送数据
            channel.writeAndFlush(request);
            channel.closeFuture().sync();
            // 阻塞的获得结果,通过给channel设计别名,获取特定名字下的channel中的内容(这个在hanlder中设置)
            // AttributeKey是,线程隔离的,不会由线程安全问题。
            // 实际上不应通过阻塞,可通过回调函数
            AttributeKey<RPCResponse> key = AttributeKey.valueOf("RPCResponse");
            RPCResponse response = channel.attr(key).get();

            System.out.println(response);
            return response;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

NettyClientInitializer.java

package com.rpc.client;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolver;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

/**
 * @author weiyu_zeng
 *
 * 同样的与服务端解码和编码格式
 */
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 消息格式 [长度][消息体]
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4,
                        0, 4));
        // 计算当前待大宋消息的长度,写入到前4个字节中
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new ObjectEncoder());

        pipeline.addLast(new ObjectDecoder(new ClassResolver() {
            @Override
            public Class<?> resolve(String className) throws ClassNotFoundException {
                return Class.forName(className);
            }
        }));
        pipeline.addLast(new NettyClientHandler());
    }
}

NettyClientHandler.java

package com.rpc.client;

import com.rpc.common.RPCResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.AttributeKey;

/**
 * @author weiyu_zeng
 *
 * AttributeMap这是是绑定在Channel或者ChannelHandlerContext上的一个附件,ChannelHandlerContext中的AttributeMap是独有的,
 * Channel上的AttributeMap就是大家共享的,每一个ChannelHandler都能获取到。AttributeMap的结构,其实和Map的格式很像,
 * key是AttributeKey,value是Attribute,我们可以根据AttributeKey找到对应的Attribute
 */
public class NettyClientHandler extends SimpleChannelInboundHandler<RPCResponse> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RPCResponse msg) throws Exception {
        // 接收到response, 给channel设计别名,让sendRequest里读取response
        AttributeKey<RPCResponse> key = AttributeKey.valueOf("RPCResponse");
        ctx.channel().attr(key).set(msg);
        ctx.channel().close();
    }

    // 跟NettyRPCServerHandler一样
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

最后定义客户端:

TestClient.java

package com.rpc.client;

import com.rpc.common.Blog;
import com.rpc.common.User;
import com.rpc.service.BlogService;
import com.rpc.service.UserService;


/**
 * @author weiyu_zeng
 *
 * 用例测试的类,继承了很大一部分RPCClient 的main函数。
 * 但也新增了新的使用方法。
 */
public class TestClient {
    public static void main(String[] args) {
        // 构建一个使用java socket或者netty的客户端
        RPCClient rpcClient = new NettyRPCClient("127.0.0.1", 8899);
        // 把这个客户端传入代理客户端
        RPCClientProxy rpcClientProxy = new RPCClientProxy(rpcClient);
        // 代理客户端根据不同的服务,获得一个代理类, 并且这个代理类的方法以或者增强(封装数据,发送请求)
        UserService userService = rpcClientProxy.getProxy(UserService.class);

        // 服务的方法1
        User userByUserId = userService.getUserByUserId(10);
        System.out.println("从服务器端得到的user为:" + userByUserId);

        // 服务的方法2
        User user = User.builder().userName("张三").id(100).sex(true).build();
        Integer integer = userService.insertUserId(user);
        System.out.println("向服务器端插入数据" + integer);

        // 服务的方法3
        BlogService blogService = rpcClientProxy.getProxy(BlogService.class);
        Blog blogById = blogService.getBlogById(10000);
        System.out.println("从服务端得到的blog为:" + blogById);
    }
}


文件结构

simpleRPC-04的文件结构如下

在这里插入图片描述

运行

我们先运行 TestServer.java

在这里插入图片描述

然后运行 TestClient.java

在这里插入图片描述

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【手写一个RPC框架】simpleRPC-04 的相关文章

随机推荐

  • C语言:操作符以及部分表达式介绍

    目录 1 操作符 1 1 算数操作符 1 2 移位操作符 1 3 位操作符 1 4 1 赋值操作符 1 4 2 复合赋值符 1 5 单目操作符 1 6 关系操作符 1 7 逻辑操作符 1 8 条件操作符 1 9 逗号操作符 1 10 下标引
  • udp发包结合tkinter

    import socket import tkinter from tkinter import filedialog from tkinter filedialog import def action 获取输入框内容 date entry
  • 深度学习双显卡配置_更新深度学习装备:双(1080Ti)显卡装机实录

    前言 之前一直在装有一张1080Ti的服务器上跑代码 但是当数据量超过10W 图像数据集 的时候 训练时就稍微有点吃力了 速度慢是一方面 关键显存存在瓶颈 导致每次训练的batch size不敢调的过高 batch size与训练结果存在一
  • 播放器实战22 解决花屏与卡顿问题

    1 内存对齐 1 1什么是内存对齐 在C语言中 结构是一种复合数据类型 其构成元素既可以是基本数据类型 如int long float等 的变量 也可以是一些复合数据类型 如数组 结构 联合等 的数据单元 在结构中 编译器为结构的每个成员按
  • 小熊派笔记2

    GPIO案例 GPIO接口函数 初始化 wifiiot gpio h接口 wifiiot gpio ex h接口 扩展函数 设置GPIO拉力和驱动器强度 LED对应的gpio引脚是gpio2通过控制gpio2输出的电平信号来实现闪烁 设置
  • 三极管和场效应管-易错点

    NPN三极管是电流控制器件 共发射极电路中 放大区 Ice Ib x Vbe正偏 Vbc反偏 电势 Vc Vb Ve 饱和区 Ice Ib x 两个都正偏 电势 Vb Vc Ve Vce之间是饱和管压降Vces 截止区 Ice 0 两个都反
  • VerilogHDL实现除法操作

    硬件电路中实现除法操作一般基于两种方式 乘法操作和减法操作 基于减法的除法器 对于32位的无符号除法 被除数a除以除数b 他们的商和余数位数一定不超过32位 首先将a转换为32位 b也转换为32位 在每周期的开始 先将a左移一位 末尾补0
  • 如何知道你的Linux内核占用的内存大小?

    如何知道你的Linux内核占用的内存大小 1 代码段等 2 kernel heap 2 1 kmalloc 2 2 vmalloc 3 进程的页表 4 内核占用内存大小总和 1 代码段等 内核所需的代码段 bss段 内核栈等 dmesg g
  • 双调排序算法的实现(C++)

    双调排序算法的实现 C 双调排序 Bitonic Sort 是一种并行排序算法 它在某些情况下比传统的排序算法具有更好的性能 该算法的特点是可以通过并行比较和交换操作来对元素进行排序 适用于并行计算环境 本文将介绍双调排序算法的实现 并提供
  • subList截图数据集合,便于分页或分批次保存至数据库

    import java util ArrayList import java util List public class subList param list 待切割集合 param len 集合按照多大size来切割 param
  • ZNYQ初体验,持续记录中...

    首先在VIVADO中创建zynq PS核 步骤如下 如下新建工程 新建 block design add ip 选择zynq7 processing system 如下图所示 双击打开配置界面如下 下面我们简要地介绍一下页面导航面板中各个页
  • 解决页面禁止拷贝的问题

    众所周知 像这种禁止的动作一般都是网页中的javascript在作怪 那么 我们直接把他禁用就好了吖 其实 最简单的 在你前面网页的导航栏里面输入javascript void 就可以尽情的白嫖了
  • go语言 FindWindow

    Go 语言中的 FindWindow 函数是用于查找窗口句柄的函数 它是由 Windows API 提供的 在 Go 中通过 syscall 包来调用 用法示例 packagemain import fmt syscall unsafe v
  • 有趣的数据结构算法5——利用循环链表解决Josephus问题

    有趣的数据结构算法5 利用循环链表解决Josephus问题 题目复述 解题思路 实现代码 GITHUB下载连接 本次教程主要讲述如何利用循环链表解决Josephus问题 题目复述 据说著名犹太历史学家 Josephus有过以下的故事 在罗马
  • Selenium碰到的异常记录

    Java版本的Selenium异常记录 1 没有找到类的异常 NoClassDefFoundError 异常如下 Exception in thread main java lang NoClassDefFoundError com goo
  • 记录Android开发中SELINUX权限问题

    记录Android开发中SELINUX权限和用户权限问题 在安卓开发中 当linux内核中配置了SELINUX权限管理 访问硬件相关的设备文件 led tty等 时 如果没有对文件和访问文件的程序设置selinux的权限 就有可能报如下错误
  • 编写java程序151条建议读书笔记(20)

    建议139 大胆采用开源工具 MVC框架有Structs 也有Spring MVC WebWorker IoC容器有Spring 也有Coolgle Guice ORM有Hibernate MyBatis 日志有经典的log4j 崭新的lo
  • Java—RPC:远程过程调用(1)

    Java RPC 远程过程调用 1 在我们学习RPC的过程中 首先我们先认识一下项目结构在发展中干的变化 一 项目结构变化 1 单体结构 单体结构又叫单一项目 在我们所认识的传统项目基本上都是单一项目 j可是在互联网逐步发展的过程中 逐渐的
  • 卡通渲染技巧(三)——崩坏3卡通渲染实践

    系列链接 卡通渲染技巧 一 漫反射部分 卡通渲染技巧 二 高光部分 描边 卡通渲染技巧 三 崩坏3卡通渲染实践 耳听为虚眼见为实 不实际看一下你永远不知道技术分享里吹了多少牛 其实是没有实际应用到游戏里 前排赞美 SnapDragon Pr
  • 【手写一个RPC框架】simpleRPC-04

    目录 前言 实现 项目创建 配置依赖 common service server client 文件结构 运行 本项目所有代码可见 https github com weiyu zeng SimpleRPC 前言 之前谈到 网络传输使用BI