大厨小鲜——基于Netty自己动手编写RPC框架

Dubbo理解 让你调用远程服务的方法,就像调用本地方法一样,中间的通信细节全部透明掉。// 本地调用// RPC 调用——写法一模一样!但 userService 实际是远程的你看,代码完全一样。区别是本地调用走的是 JVM 内存调用;RPC 调用呢,底层帮你把方法名、参数序列化(编码)成二进制数据,通过网络发给远程机器,远程机器执行完再把结果序列化发回来。整个过程你是感知不到的。对比维度HTTPRPC(如 Dubbo)协议文本协议,请求头很大,信息冗余二进制协议,请求包很小性能。 阅读详情

今天我们要来做一道小菜,这道菜就是RPC通讯框架。它使用netty作为原料,fastjson序列化工具作为调料,来实现一个极简的多线程RPC服务框架。

我们暂且命名该RPC框架为rpckids。

640?wx_fmt=png

食用指南

在告诉读者完整的制作菜谱之前,我们先来试试这个小菜怎么个吃法,好不好吃,是不是吃起来很方便。如果读者觉得很难吃,那后面的菜谱就没有多大意义了,何必花心思去学习制作一门谁也不爱吃的大烂菜呢?

例子中我会使用rpckids提供的远程RPC服务,用于计算斐波那契数和指数,客户端通过rpckids提供的RPC客户端向远程服务传送参数,并接受返回结果,然后呈现出来。你可以使用rpckids定制任意的业务rpc服务。

640?wx_fmt=png

斐波那契数输入输出比较简单,一个Integer,一个Long。

指数服务自定义输入输出类

// 指数RPC的输入
public class ExpRequest {
    private int base;
    private int exp;

    // constructor & getter & setter
}

// 指数RPC的输出
public class ExpResponse {

    private long value;
    private long costInNanos;

    // constructor & getter & setter
}

斐波那契和指数计算处理

public class FibRequestHandler implements IMessageHandler<Integer> {

    private List<Long> fibs = new ArrayList<>();

    {
        fibs.add(1L); // fib(0) = 1
        fibs.add(1L); // fib(1) = 1
    }

    @Override
    public void handle(ChannelHandlerContext ctx, String requestId, Integer n) {
        for (int i = fibs.size(); i < n + 1; i++) {
            long value = fibs.get(i - 2) + fibs.get(i - 1);
            fibs.add(value);
        }
        // 输出响应
        ctx.writeAndFlush(new MessageOutput(requestId, "fib_res", fibs.get(n)));
    }

}

public class ExpRequestHandler implements IMessageHandler<ExpRequest> {

    @Override
    public void handle(ChannelHandlerContext ctx, String requestId, ExpRequest message) {
        int base = message.getBase();
        int exp = message.getExp();
        long start = System.nanoTime();
        long res = 1;
        for (int i = 0; i < exp; i++) {
            res *= base;
        }
        long cost = System.nanoTime() - start;
        // 输出响应
        ctx.writeAndFlush(new MessageOutput(requestId, "exp_res", new ExpResponse(res, cost)));
    }

}

构建RPC服务器

RPC服务类要监听指定IP端口,设定io线程数和业务计算线程数,然后注册斐波那契服务输入类和指数服务输入类,还有相应的计算处理器。

public class DemoServer {

    public static void main(String[] args) {
        RPCServer server = new RPCServer("localhost", 8888, 2, 16);
        server.service("fib", Integer.class, new FibRequestHandler())
              .service("exp", ExpRequest.class, new ExpRequestHandler());
        server.start();
    }

}

构建RPC客户端

RPC客户端要链接远程IP端口,并注册服务输出类(RPC响应类),然后分别调用20次斐波那契服务和指数服务,输出结果

public class DemoClient {

    private RPCClient client;
    
    public DemoClient(RPCClient client) {
        this.client = client;
        // 注册服务返回类型
        this.client.rpc("fib_res", Long.class).rpc("exp_res", ExpResponse.class);
    }
    
    public long fib(int n) {
        return (Long) client.send("fib", n);
    }
    
    public ExpResponse exp(int base, int exp) {
        return (ExpResponse) client.send("exp", new ExpRequest(base, exp));
    }
    
    public static void main(String[] args) {
        RPCClient client = new RPCClient("localhost", 8888);
        DemoClient demo = new DemoClient(client);
        
        for (int i = 0; i < 20; i++) {
            System.out.printf("fib(%d) = %d\n", i, demo.fib(i));
        }
        
        for (int i = 0; i < 20; i++) {
            ExpResponse res = demo.exp(2, i);
            System.out.printf("exp2(%d) = %d cost=%dns\n", i, res.getValue(), res.getCostInNanos());
        }
    }
}

运行

先运行服务器,服务器输出如下,从日志中可以看到客户端链接过来了,然后发送了一系列消息,最后关闭链接走了。

server started @ localhost:8888
connection comes
read a message
read a message
...
connection leaves

再运行客户端,可以看到一些列的计算结果都成功完成了输出。

fib(0) = 1
fib(1) = 1
fib(2) = 2
fib(3) = 3
fib(4) = 5
...
exp2(0) = 1 cost=559ns
exp2(1) = 2 cost=495ns
exp2(2) = 4 cost=524ns
exp2(3) = 8 cost=640ns
exp2(4) = 16 cost=711ns
...

牢骚

本以为是小菜一碟,但是编写完整的代码和文章却将近花费了一天的时间,深感写码要比做菜耗时太多了。因为只是为了教学目的,所以在实现细节上还有好多没有仔细去雕琢的地方。如果是要做一个开源项目,力求非常完美的话。至少还要考虑一下几点。

  1. 客户端连接池

  2. 多服务进程负载均衡

  3. 日志输出

  4. 参数校验,异常处理

  5. 客户端流量攻击

  6. 服务器压力极限

如果要参考grpc的话,还得实现流式响应处理。如果还要为了节省网络流量的话,又需要在协议上下功夫。这一大堆的问题还是抛给读者自己思考去吧。

640?wx_fmt=jpeg

关注公众号「码洞」,发送「RPC」即可获取以上完整菜谱的GitHub开源代码链接。读者有什么不明白的地方,洞主也会一一解答。

下面我们接着讲RPC服务器和客户端精细的制作过程

服务器菜谱

定义消息输入输出格式,消息类型、消息唯一ID和消息的json序列化字符串内容。消息唯一ID是用来客户端验证服务器请求和响应是否匹配。

public class MessageInput {
    private String type;
    private String requestId;
    private String payload;

    public MessageInput(String type, String requestId, String payload) {
        this.type = type;
        this.requestId = requestId;
        this.payload = payload;
    }

    public String getType() {
        return type;
    }

    public String getRequestId() {
        return requestId;
    }

    // 因为我们想直接拿到对象,所以要提供对象的类型参数
    public <T> T getPayload(Class<T> clazz) {
        if (payload == null) {
            return null;
        }
        return JSON.parseObject(payload, clazz);
    }

}

public class MessageOutput {

    private String requestId;
    private String type;
    private Object payload;

    public MessageOutput(String requestId, String type, Object payload) {
        this.requestId = requestId;
        this.type = type;
        this.payload = payload;
    }

    public String getType() {
        return this.type;
    }

    public String getRequestId() {
        return requestId;
    }

    public Object getPayload() {
        return payload;
    }

}

消息解码器,使用Netty的ReplayingDecoder实现。简单起见,这里没有使用checkpoint去优化性能了,感兴趣的话读者可以参考一下我之前在公众号里发表的相关文章,将checkpoint相关的逻辑自己添加进去。

public class MessageDecoder extends ReplayingDecoder<MessageInput> {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        String requestId = readStr(in);
        String type = readStr(in);
        String content = readStr(in);
        out.add(new MessageInput(type, requestId, content));
    }

    private String readStr(ByteBuf in) {
        // 字符串先长度后字节数组,统一UTF8编码
        int len = in.readInt();
        if (len < 0 || len > (1 << 20)) {
            throw new DecoderException("string too long len=" + len);
        }
        byte[] bytes = new byte[len];
        in.readBytes(bytes);
        return new String(bytes, Charsets.UTF8);
    }

}

消息处理器接口,每个自定义服务必须实现handle方法

public interface IMessageHandler<T> {

    void handle(ChannelHandlerContext ctx, String requestId, T message);

}

// 找不到类型的消息统一使用默认处理器处理
public class DefaultHandler implements IMessageHandler<MessageInput> {

    @Override
    public void handle(ChannelHandlerContext ctx, String requesetId, MessageInput input) {
        System.out.println("unrecognized message type=" + input.getType() + " comes");
    }

}

消息类型注册中心和消息处理器注册中心,都是用静态字段和方法,其实也是为了图方便,写成非静态的可能会优雅一些。

public class MessageRegistry {
    private static Map<String, Class<?>> clazzes = new HashMap<>();

    public static void register(String type, Class<?> clazz) {
        clazzes.put(type, clazz);
    }

    public static Class<?> get(String type) {
        return clazzes.get(type);
    }
}

public class MessageHandlers {

    private static Map<String, IMessageHandler<?>> handlers = new HashMap<>();
    public static DefaultHandler defaultHandler = new DefaultHandler();

    public static void register(String type, IMessageHandler<?> handler) {
        handlers.put(type, handler);
    }

    public static IMessageHandler<?> get(String type) {
        IMessageHandler<?> handler = handlers.get(type);
        return handler;
    }

}

响应消息的编码器比较简单

@Sharable
public class MessageEncoder extends MessageToMessageEncoder<MessageOutput> {

    @Override
    protected void encode(ChannelHandlerContext ctx, MessageOutput msg, List<Object> out) throws Exception {
        ByteBuf buf = PooledByteBufAllocator.DEFAULT.directBuffer();
        writeStr(buf, msg.getRequestId());
        writeStr(buf, msg.getType());
        writeStr(buf, JSON.toJSONString(msg.getPayload()));
        out.add(buf);
    }

    private void writeStr(ByteBuf buf, String s) {
        buf.writeInt(s.length());
        buf.writeBytes(s.getBytes(Charsets.UTF8));
    }

}

好,接下来进入关键环节,将上面的小模小块凑在一起,构建一个完整的RPC服务器框架,这里就需要读者有必须的Netty基础知识了,需要编写Netty的事件回调类和服务构建类。

@Sharable
public class MessageCollector extends ChannelInboundHandlerAdapter {
    // 业务线程池
    private ThreadPoolExecutor executor;

    public MessageCollector(int workerThreads) {
        // 业务队列最大1000,避免堆积
        // 如果子线程处理不过来,io线程也会加入处理业务逻辑(callerRunsPolicy)
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
        // 给业务线程命名
        ThreadFactory factory = new ThreadFactory() {

            AtomicInteger seq = new AtomicInteger();

            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("rpc-" + seq.getAndIncrement());
                return t;
            }

        };
        // 闲置时间超过30秒的线程自动销毁
        this.executor = new ThreadPoolExecutor(1, workerThreads, 30, TimeUnit.SECONDS, queue, factory,
                new CallerRunsPolicy());
    }

    public void closeGracefully() {
        // 优雅一点关闭,先通知,再等待,最后强制关闭
        this.executor.shutdown();
        try {
            this.executor.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
        }
        this.executor.shutdownNow();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 客户端来了一个新链接
        System.out.println("connection comes");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        // 客户端走了一个
        System.out.println("connection leaves");
        ctx.close();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof MessageInput) {
            System.out.println("read a message");
            // 用业务线程池处理消息
            this.executor.execute(() -> {
                this.handleMessage(ctx, (MessageInput) msg);
            });
        }
    }

    private void handleMessage(ChannelHandlerContext ctx, MessageInput input) {
        // 业务逻辑在这里
        Class<?> clazz = MessageRegistry.get(input.getType());
        if (clazz == null) {
            // 没注册的消息用默认的处理器处理
            MessageHandlers.defaultHandler.handle(ctx, input.getRequestId(), input);
            return;
        }
        Object o = input.getPayload(clazz);
        // 这里是小鲜的瑕疵,代码外观上比较难看,但是大厨表示才艺不够,很无奈
        // 读者如果感兴趣可以自己想办法解决
        @SuppressWarnings("unchecked")
        IMessageHandler<Object> handler = (IMessageHandler<Object>) MessageHandlers.get(input.getType());
        if (handler != null) {
            handler.handle(ctx, input.getRequestId(), o);
        } else {
            // 用默认的处理器处理吧
            MessageHandlers.defaultHandler.handle(ctx, input.getRequestId(), input);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 此处可能因为客户端机器突发重启
        // 也可能是客户端链接闲置时间超时,后面的ReadTimeoutHandler抛出来的异常
        // 也可能是消息协议错误,序列化异常
        // etc.
        // 不管它,链接统统关闭,反正客户端具备重连机制
        System.out.println("connection error");
        cause.printStackTrace();
        ctx.close();
    }

}

public class RPCServer {

    private String ip;
    private int port;
    private int ioThreads; // 用来处理网络流的读写线程
    private int workerThreads; // 用于业务处理的计算线程

    public RPCServer(String ip, int port, int ioThreads, int workerThreads) {
        this.ip = ip;
        this.port = port;
        this.ioThreads = ioThreads;
        this.workerThreads = workerThreads;
    }

    private ServerBootstrap bootstrap;
    private EventLoopGroup group;
    private MessageCollector collector;
    private Channel serverChannel;

    // 注册服务的快捷方式
    public RPCServer service(String type, Class<?> reqClass, IMessageHandler<?> handler) {
        MessageRegistry.register(type, reqClass);
        MessageHandlers.register(type, handler);
        return this;
    }

    // 启动RPC服务
    public void start() {
        bootstrap = new ServerBootstrap();
        group = new NioEventLoopGroup(ioThreads);
        bootstrap.group(group);
        collector = new MessageCollector(workerThreads);
        MessageEncoder encoder = new MessageEncoder();
        bootstrap.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipe = ch.pipeline();
                // 如果客户端60秒没有任何请求,就关闭客户端链接
                pipe.addLast(new ReadTimeoutHandler(60));
                // 挂上解码器
                pipe.addLast(new MessageDecoder());
                // 挂上编码器
                pipe.addLast(encoder);
                // 将业务处理器放在最后
                pipe.addLast(collector);
            }
        });
        bootstrap.option(ChannelOption.SO_BACKLOG, 100)  // 客户端套件字接受队列大小
                 .option(ChannelOption.SO_REUSEADDR, true) // reuse addr,避免端口冲突
                 .option(ChannelOption.TCP_NODELAY, true) // 关闭小流合并,保证消息的及时性
                 .childOption(ChannelOption.SO_KEEPALIVE, true); // 长时间没动静的链接自动关闭
        serverChannel = bootstrap.bind(this.ip, this.port).channel();
        System.out.printf("server started @ %s:%d\n", ip, port);
    }

    public void stop() {
        // 先关闭服务端套件字
        serverChannel.close();
        // 再斩断消息来源,停止io线程池
        group.shutdownGracefully();
        // 最后停止业务线程
        collector.closeGracefully();
    }

}

上面就是完整的服务器菜谱,代码较多,读者如果没有Netty基础的话,可能会看得眼花缭乱。如果你不常使用JDK的Executors框架,阅读起来估计也够呛。如果读者需要相关学习资料,可以找我索取。

客户端菜谱

服务器使用NIO实现,客户端也可以使用NIO实现,不过必要性不大,用同步的socket实现也是没有问题的。更重要的是,同步的代码比较简短,便于理解。所以简单起见,这里使用了同步IO。

定义RPC请求对象和响应对象,和服务器一一对应。

public class RPCRequest {

    private String requestId;
    private String type;
    private Object payload;

    public RPCRequest(String requestId, String type, Object payload) {
        this.requestId = requestId;
        this.type = type;
        this.payload = payload;
    }

    public String getRequestId() {
        return requestId;
    }

    public String getType() {
        return type;
    }

    public Object getPayload() {
        return payload;
    }

}

public class RPCResponse {

    private String requestId;
    private String type;
    private Object payload;

    public RPCResponse(String requestId, String type, Object payload) {
        this.requestId = requestId;
        this.type = type;
        this.payload = payload;
    }

    public String getRequestId() {
        return requestId;
    }

    public void setRequestId(String requestId) {
        this.requestId = requestId;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Object getPayload() {
        return payload;
    }

    public void setPayload(Object payload) {
        this.payload = payload;
    }

}

定义客户端异常,用于统一抛出RPC错误

public class RPCException extends RuntimeException {

    private static final long serialVersionUID = 1L;
    
    public RPCException(String message, Throwable cause) {
        super(message, cause);
    }
    
    public RPCException(String message) {
        super(message);
    }
    
    public RPCException(Throwable cause) {
        super(cause);
    }
}

请求ID生成器,简单的UUID64

public class RequestId {

    public static String next() {
        return UUID.randomUUID().toString();
    }
}

响应类型注册中心,和服务器对应

public class ResponseRegistry {

    private static Map<String, Class<?>> clazzes = new HashMap<>();
    
    public static void register(String type, Class<?> clazz) {
        clazzes.put(type, clazz);
    }
    
    public static Class<?> get(String type) {
        return clazzes.get(type);
    }
}

好,接下来进入客户端的关键环节,链接管理、读写消息、链接重连都在这里,写完了这个,一个极简RPC服务框架也就是完成了。

public class RPCClient {

    private String ip;
    private int port;
    private Socket sock;
    private DataInputStream input;
    private OutputStream output;
    
    public RPCClient(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }
    
    public void connect() throws IOException {
        SocketAddress addr = new InetSocketAddress(ip, port);
        sock = new Socket();
        sock.connect(addr, 5000); // 5s超时
        input = new DataInputStream(sock.getInputStream());
        output = sock.getOutputStream();
    }
    
    public void close() {
        // 关闭链接
        try {
            sock.close();
            sock = null;
            input = null;
            output = null;
        } catch (IOException e) {
        }
    }
    
    public Object send(String type, Object payload) {
        // 普通rpc请求,正常获取响应
        try {
            return this.sendInternal(type, payload, false);
        } catch (IOException e) {
            throw new RPCException(e);
        }
    }
    
    public RPCClient rpc(String type, Class<?> clazz) {
        // rpc响应类型注册快捷入口
        ResponseRegistry.register(type, clazz);
        return this;
    }
    
    public void cast(String type, Object payload) {
        // 单向消息,服务器不得返回结果
        try {
            this.sendInternal(type, payload, true);
        } catch (IOException e) {
            throw new RPCException(e);
        }
    }
    
    private Object sendInternal(String type, Object payload, boolean cast) throws IOException {
        if (output == null) {
            connect();
        }
        String requestId = RequestId.next();
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        DataOutputStream buf = new DataOutputStream(bytes);
        writeStr(buf, requestId);
        writeStr(buf, type);
        writeStr(buf, JSON.toJSONString(payload));
        buf.flush();
        byte[] fullLoad = bytes.toByteArray();
        try {
            // 发送请求
            output.write(fullLoad);
        } catch (IOException e) {
            // 网络异常要重连
            close();
            connect();
            output.write(fullLoad);
        }
        if (!cast) {
            // RPC普通请求,要立即获取响应
            String reqId = readStr();
            // 校验请求ID是否匹配
            if (!requestId.equals(reqId)) {
                close();
                throw new RPCException("request id mismatch");
            }
            String typ = readStr();
            Class<?> clazz = ResponseRegistry.get(typ);
            // 响应类型必须提前注册
            if (clazz == null) {
                throw new RPCException("unrecognized rpc response type=" + typ);
            }
            // 反序列化json串
            String payld = readStr();
            Object res = JSON.parseObject(payld, clazz);
            return res;
        }
        return null;
    }
    
    private String readStr() throws IOException {
        int len = input.readInt();
        byte[] bytes = new byte[len];
        input.readFully(bytes);
        return new String(bytes, Charsets.UTF8);
    }
    
    private void writeStr(DataOutputStream out, String s) throws IOException {
        out.writeInt(s.length());
        out.write(s.getBytes(Charsets.UTF8));
    }
}

牢骚重提

本以为是小菜一碟,但是编写完整的代码和文章却将近花费了一天的时间,深感写码要比做菜耗时太多了。因为只是为了教学目的,所以在实现细节上还有好多没有仔细去雕琢的地方。如果是要做一个开源项目,力求非常完美的话。至少还要考虑一下几点。

  1. 客户端连接池

  2. 多服务进程负载均衡

  3. 日志输出

  4. 参数校验,异常处理

  5. 客户端流量攻击

  6. 服务器压力极限

如果要参考grpc的话,还得实现流式响应处理。如果还要为了节省网络流量的话,又需要在协议上下功夫。这一大堆的问题还是抛给读者自己思考去吧。

640?wx_fmt=jpeg

关注公众号「码洞」,发送「RPC」即可获取以上完整菜谱的GitHub开源代码链接。读者有什么不明白的地方,洞主也会一一解答。

真的够可以的,基于Netty实现了RPC框架 RPC全称Remote Procedure Call,即远程过程调用,对于调用者无感知这是一个远程调用功能。目前流行的开源RPC 框架有阿里的Dubbo、Google 的 gRPC、Twitter 的Finagle 等。本次RPC框架的设计主要参考的是阿里的Dubbo,这里Netty 基本上是作为架构的技术底层而存在的,主要完成高性能的网络通信,从而实现高效的远程调用。 阅读详情

相关推荐

R语言实战:5分钟搞定相加交互效应分析(逻辑回归/Cox回归/GLMM/GEE全支持)

本文详细介绍了如何利用R语言中的`scitable`包快速实现相加交互效应分析,支持逻辑回归、Cox回归、GLMM和GEE等多种模型。通过自动化计算RERI、AP和SI等指标,大幅提升临床研究和流行病学分析的效率与准确性,特别适合处理多因素联合作用的复杂场景。

fanta的博客 672

大厨小鲜——基于Netty自己动手实现RPC框架

今天我们要来做一道小菜,这道菜就是RPC通讯框架。它使用netty作为原料,fastjson序列化工具作为调料,来实现一个极简的多线程RPC服务框架。 我们暂且命名该RPC框架为rpckids。 食用指南 在告诉读者完整的制作菜谱之前,我们先来试试这个小菜怎么个吃法,好不好吃,是不是吃起来很方便。如果读者觉得很难吃,那后面的菜谱就没有多大意义了,何必花心思去学习制作一门谁也不爱吃的大烂菜呢? 例...

weixin_34026276的博客 771

基于51单片机扫地机器人(全套Proteus仿真+原理图+程序+说明书)

(内含源程序代码,原理图,仿真,讲解视频,元件清单等,【精品】值得推荐!!!扫地机器人是以单片机为主控制器。通过单片机控制小车行驶、除尘、蜂鸣。扫地机器人使用双电机驱动,以提高整车运动的平稳性;在智能车进驶过程中,采用双超声波模块实现避障。

2301_76434107的博客 1897

由浅入深Netty简易实现RPC框架

目录 1 准备工作 2 服务器 handler 3 客户端代码第一版 4 客户端 handler 第一版 5 客户端代码 第二版 6 客户端 handler 第二版 1 准备工作 这些代码可以认为是现成的,无需从头编写练习 为了简化起见,在原来聊天项目的基础上新增 Rpc 请求和响应消息

赵广陆 727

手把手调试Altera FPGA远程升级:从ASMI扇区擦除到Remote Update IP配置的避坑实录

本文详细解析了Altera FPGA远程升级的全流程,重点介绍了Cyclone IV系列FPGA的ASMI扇区擦除和Remote Update IP配置的实战技巧。通过双镜像架构设计、ASMI IP核优化配置及Remote Update IP的隐藏参数设置,帮助开发者避免常见陷阱,提升远程升级的可靠性和效率。

weixin_30872499的博客 448

轻量级NAS的隐秘战场:比特米盒CumeBox2与CasaOS的生态融合实践

本文探讨了比特米盒CumeBox2轻量级NAS设备与CasaOS操作系统的生态融合实践。通过硬件选型、系统部署、性能优化和应用场景分析,展示了如何将CumeBox2与CasaOS深度整合,打造经济实用的家庭数据管理中心,特别适合入门级用户和小型办公环境。

rl6adventurer的博客 895

基于Netty的Rpc框架的简单实现

RPC是Remote Procedure Call的简称,即远程过程调用,通过rpc,我们可以像调用本地接口一样,调用远程接口。在这个过程中,我们不需要关注接口的实现。实现rpc的框架很多,比如著名的dubbo框架,这里介绍通过netty来实现一个简单的rpc。 //1、定义测试Service接口IHelloService //2、定义一个参数传输类,RpcRequestInfo //3、实...

weixin_43881309的博客 521

基于STM32、HAL库的CH9434总线转UART驱动程序设计

CH9434是一款USB转4串口控制芯片,可以将单个USB接口扩展为4个独立的串口(UART)。支持多种数据格式(数据位5-8,停止位1/1.5/2,奇偶校验或无校验)// 读取接收到的数据。// 重新启动接收中断。// 向所有4个串口发送数据。// UART初始化函数示例(USART1)// 其他UART初始化函数类似...// 初始化CH9434。// 初始化UART1。// 初始化UART2。// 初始化UART3。// 初始化UART4。工作电压:3.3V/5V兼容。

corlin6688的博客 990

netty 处理JSON序列化(FastJSON编解码)

解码器 编码器

Lyndon的专栏 1482

论文阅读:Universal and Transferable Adversarial Attacks on Aligned Language Models. LLM Attacks

因为“即开即用”的大型语言模型能够生成大量令人反感的内容,近期的工作集中在对这些模型进行对齐,以防止不受欢迎的生成。尽管在规避这些措施方面取得了一些成功——即所谓的针对大型语言模型(LLMs)的“越狱”——这些攻击需要大量的人类智慧,并且在实践中很脆弱。自动对抗性提示生成的尝试也取得了有限的成功。在本文中,我们提出了一种简单有效的攻击方法,可以导致对齐的语言模型生成令人反感的行为。

CSPhD-winston的博客 1418

【肝帝一周总结:全网最全最细】十万字python教程,学不会找我!教到你会为止!!内容超多,建议收藏慢慢看!

文章目录前言一定要看python入门python缩进Python注释Python 变量1. 定义理解2. 变量名命名3. 分配多个值4. 输出变量5. 全局变量 前言一定要看 python能干什么?有什么用?好处在哪?不要我讲了,这不是我在博客该讲的,言简意赅就是:通俗易懂,上手快,实用强。我会根据自己个人理解以及国外官网翻译为中文对大家进行讲解。完整源码我也会在最下面带上地址。内容过多,我不会全部演示,你完全可以复制粘贴到自己的pycharm上运行即可。 不管是国内的菜鸟教程还是我在这里的翻译,至少你跟

全栈川川 5万+

【WebSocket服务器】 1.服务器netty 2.客户端creator 3.fastjson通讯协议

1.客户端 start() { // init logic this.label.string = this.text; let socket = new WebSocket("ws://localhost:6080/ws"); // H5标准,底层做好了; socket.binaryType = "arraybuffer"; socket.onopen = function () { cc

focus-unchanged-thing 298

PhotovoltaicPanelCleanlinessDetector:基于YOLOv8的光伏板清洁状态监测系统

data.yamlyaml复制编辑names:0: clean1: dust3: leaves4: shadow✅ 高精度污染识别✅ 具备实时检测与UI展示✅ 可本地化部署应用未来方向增加污染程度分级(轻度、中度、严重)加入时间序列分析(污染变化趋势预测)无人机集成巡检模块。

YOLO 276

探秘Netty5:基于Netty自己动手实现RPC框架

大厨小鲜——基于Netty自己动手实现RPC框架 钱文品 Good news everyone! ​关注他 71 人赞了该文章 今天我们要来做一道小菜,这道菜就是RPC通讯框架。它使用netty作为原料,fastjson序列化工具作为调料,来实现一个极简的多线程RPC服务框架。 我们暂且命名该RPC框架为rpckids。 食用指南 在告诉读者...

Java技术江湖 3637

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

大厨小鲜——自己动手实现一个极简Web框架

上节课我们自己动手制作了一个RPC框架,本节课我们挑战一个稍有难度的一点的任务,手动制作一个Web框架。首先我们看看这个Web框架使用起来如何简单Hello Worldi...

shellquery的博客 116

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

嵌入式固件开发进阶:启动流程、故障定位与OTA升级工程化实战

嵌入式系统开发中,固件从裸机走向工程化交付,核心挑战往往集中在启动流程、故障定位和OTA升级这三个关键环节。启动流程是硬件与软件交汇的必经之路,从STM32等MCU的向量表初始化,到Uboot加载DDR,再到RT-Thread的自动初始化机制,每个阶段的前置条件与后置影响都直接影响系统稳定性。掌握一套系统化的故障定位方法论,学会通过因果链假设与可观测性手段(如串口日志、GPIO探针、Trace工具)快速收敛问题,能极大提升调试效率。而OTA升级的工程化设计则需兼顾分区规划、签名校验、断点续传与失败回滚,确保

weixin_34197488的博客 546

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

探秘Netty6:基于Netty自己动手实现Web框架

大厨小鲜——基于Netty自己动手实现Web框架 上节课我们自己动手制作了一个RPC框架,本节课我们挑战一个稍有难度的一点的任务,手动制作一个Web框架。 我不太愿意叫什么MVC框架,因为我在制作这个小项目的时候可没想过它要怎么怎么的MVC,一切皆以易于使用为目标。 首先我们看看这个Web框架使用起来如何简单 Hello World import http...

Java技术江湖 6205

教你用 Netty 实现一个简单的 RPC!

作者:莫那鲁道https://www.cnblogs.com/stateis0/p/8960791.html 2019-11-14 09:19:00 众所周知,dubbo 底层使用了 Netty 作为网络通讯框架,而 Netty 的高性能我们之前也分析过源码,对他也算还是比较了解了。 今天我们就自己用 Netty 实现一个简单的 RPC 框架。 1、需求 模仿 dubbo,消费者和提供者约定接口和协议,消费者远程调用提供者,提供者返回一个字符串,消费者打印提供者返回的数据。底层网络通信使用 Netty..

341

用Netty实现一个RPC框架

基于Netty实现的一个RPC框架

weixin_50185312的博客 870

基于Netty的高性能RPC框架(分布式缓存、雪花算法、幂等性)

解决了幂等性不能在多台服务器实现的问题,处理涉及分布式缓存、雪花算法、时钟回拨等有挑战性的问题。VERSIONVERSION2.1.02.1.0JDK: 8.0JDKJDK8.08.0NACOSNACOS1.431.43NETTYNETTYLICENCELICENCEMITMIT一个分布式微服务RPC框架 |英文说明文档|SpringBoot整合RPC基于Socket和Netty异步非阻塞通信的解决方案;

fyphome 1554

【Netty】基于Netty实现RPC框架

基于Netty实现RPC框架

qq_43745578的博客 2241

netty解决拆包粘包的三种方案

一、什么是粘包拆包 举个例子 客户端和服务器建立一个连接,客户端发送一条消息,客户端关闭与服务端的连接。 客户端和服务器简历一个连接,客户端连续发送两条消息,客户端关闭与服务端的连接。 对于第一种情况,服务端的处理流程可以是这样的:当客户端与服务端的连接建立成功之后,服务端不断读取客户端发送过来的数据,当客户端与服务端连接断开之后,服务端知道已经读完了一条消息,然后进行解码和后续处理...。对于第二种情况,如果按照上面相同的处理逻辑来处理,那就有问题了,我们来看看第二种情况下客户端发送的两条消息递交

wwwzhouzy的专栏 8223

Netty-RPC框架实现解

大体上我将整个框架分为四个部分Provider , Consumer , API ,server , Register。以上就是笔者根据Netty实现的核心RPC框架啦,如有不足,嘴下留情。这里是反射逻辑的核心方法。

weixin_58124445的博客 379

分布式理论,架构设计(四)自定义RPC

分布式理论,架构设计(四)自定义RPC自定义RPCRMI基于netty实现RPC框架 自定义RPC 在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现 远程通讯的技术,例如:RMI、Hessian、SOAP、ESB和JMS等。 要实现网络机器间的通讯,首先得来看看计算机系统网络通信的基本原理,在底层层面去看,网络 通信需要做的就是将流从一台计算机传输到另外一台计算机,基于传输协议和网络IO来实现,其中传输协议比较出名的有tcp、udp等等,tcp、udp都是在基于S

拐柒的博客 427
上一篇: 一个好玩的彩色ascii码生成工具
下一篇: 欢迎加入码洞技术交流群
codehole_
博客等级 码龄7年 34粉丝 132原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值