Dubbo泛化调用入门到精通_dubbo 泛化调用-程序员宅基地

技术标签: Dubbo 实现分析  java  rpc  

Dubbo 泛化调用、阿里的好舒服的泛化调用都是类似的功能。最近给同事排查一个dubbo-admin 控制台调用报错的问题的小研究,为此还给社区提了一个issue,具体可以查看链接 dubbo-admin 泛型参数测试 ClassNotFoundException 看了最新版本好像自动去掉了出现泛型<这样的信息通通干掉,不过没有发布release版本。 参考文档:使用泛化调用


1、泛化调用

1.1 概念

泛化接口调用方式主要用于客户端没有 API 接口(泛化调用是指不需要依赖服务的二方包)及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,API网关比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。泛化调用的方式可以有效减少平台型产品的二方包依赖,实现系统的轻量级运行。对于服务提供方新增的接口不需要修改二方包的版本,即可调用,不方便的就是对于数据入参、返回值由于没有二方包很难理解,全部都是org.apache.dubbo.common.utils.PojoUtils#generalize(java.lang.Object) 转换为基本类型或者Map等等。

特点
  • 没有二方、三方的 接口的类信息、class 字节码
  • 直接通过基本的类型参数表示复杂的对象

1.2 PojoUtils 说明

PojoUtils. Travel object deeply, and convert complex type to simple type. (深度遍历对象,并将复杂类型转换为简单类型),同时也提供了反向转换。
Simple type below will be remained:

  • Primitive Type, also include String, Number(Integer, Long), Date
  • Array of Primitive Type
  • Collection, eg: List, Map, Set etc.
  • Other type will be covert to a map which contains the attributes and value pair of object.


PojoUtils 提供了讲复杂对象转换为简单的对象,通过简单对象转换为复杂对象。有点像序列化和反序列话,你要说它是不是,感觉也没错哈!官方的测试例子不错,可以去了解一下

 List<User> users = new ArrayList<>();
 User user = new User();
 user.setName("other");
 user.setAge(31);
 users.add(user);

 Object generalize = PojoUtils.generalize(users);
 System.out.println(generalize);
 
 //[{name=other, class=org.apache.dubbo.samples.Dto.User, age=31}]

2、泛化例子

基于官方demo 中 dubbo-samples-zookeper,本地安装一个zk 即可启动
https://github.com/apache/dubbo-samples/tree/master/java/dubbo-samples-zookeeper

2.1 实践指南

泛化调用最好的一个参考例子就是dubbo-admin 中 ,通过泛化进行控制台的调用实践,泛化没有二方包,服务提供方的方法参数类型无法直接使用强类型表示,传递的参数也无法直接使用强类型只能通过 参考 :PojoUtils 序列化 Object generalize = PojoUtils.generalize(data) 例子传递 Map、List 、Array 等基本类型的参数
eg List -> [{name=other, age=31, class=org.apache.dubbo.samples.Dto.User}] 就是List<HashMap<String,String> 这样的结构**,中增加 class 字段信息,反序列化时候使用表征对应的泛型的信息,或者类class的信息**。

泛化必须参数
  • 服务的名称、版本、分组 唯一限定符
  • 方法名称、方法泛型参数类型
  • 传递的 PojoUtils.generalize(data) 序列化后的基本参数

为啥需要这些,就是为了找到具体的接口的提供者、接口的具体的方法是哪个?调用传递的参数是啥,即可享受正常的调用逻辑。如下是dubbo-admin 控制台实践指南,Dubbo API 网关也是类似的概念哦!只要通过配置或者传递参数中有如下的概念即可搞定问题。


泛化泛化的结果:也是基本类型的数据,根据 PojoUtils.generalize(result) 根据result 进行Pojo作为泛化的结果。如下是 dubbo-admin 控制台测试的代码
https://github.com/apache/dubbo-admin/blob/develop/dubbo-admin-server/src/main/java/org/apache/dubbo/admin/service/impl/GenericServiceImpl.java

public Object invoke(String service, String method, String[] parameterTypes, Object[] params) {
    

        ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
        String group = Tool.getGroup(service);
        String version = Tool.getVersion(service);
        String intf = Tool.getInterface(service);
        reference.setGeneric(true);
        reference.setApplication(applicationConfig);
        reference.setInterface(intf);
        reference.setVersion(version);
        reference.setGroup(group);

        try {
    
            removeGenericSymbol(parameterTypes);
            GenericService genericService = reference.get();
            return genericService.$invoke(method, parameterTypes, params);
        } finally {
    
            reference.destroy();
        }
    }

    /**
     * remove generic from parameterTypes 排除掉泛型信息
     *
     * @param parameterTypes
     */
    private void removeGenericSymbol(String[] parameterTypes){
    
        for (int i = 0; i < parameterTypes.length; i++) {
    
            int index = parameterTypes[i].indexOf("<");
            if (index > -1) {
    
                parameterTypes[i] = parameterTypes[i].substring(0, index);
            }
        }
    }

2.2 简单的泛化调用

Provider 接口

针对官方的版本 添加了一个泛化的对象的接口

import java.util.List;

import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.samples.Dto.User;
import org.apache.dubbo.samples.api.GreetingService;

@Service(version = "1.0.0.daily", group = "dubbo")
public class AnnotatedGreetingService implements GreetingService {
    

    /**
     * 简单测试
     * 
     * @param name
     * @return
     */
    @Override
    public String sayHello(String name) {
    
        System.out.println("greeting service received: " + name);
        return "hello, " + name;
    }

    /**
     * 测试泛型接口
     * 
     * @param users
     * @return
     */
    @Override
    public List<User> getListUserIncludeMe(List<User> users) {
    
        if (users == null) {
    
            return null;
        }
        User user = new User();
        user.setName("wangji");
        user.setAge(28);
        users.add(user);
        return users;
    }
}


/**
 * @author wangji
 * @date 2020/6/2 7:12 PM
 */
public class User {
    
    private String name;

    private Integer age;

    public String getName() {
    
        return name;
    }

    public void setName(String name) {
    
        this.name = name;
    }

    public Integer getAge() {
    
        return age;
    }

    public void setAge(Integer age) {
    
        this.age = age;
    }
}


泛化调用

最最关键的地方在于 genericService.$invoke,调用这个泛化接口的几个最主要的参数信息。最好自己动手实践一下子。

public static void main(String[] args) {
    

    ApplicationConfig application = new ApplicationConfig();
    application.setName("Test-" + UUID.randomUUID().toString());
    RegistryConfig registryConfig = new RegistryConfig();
    registryConfig.setAddress("zookeeper://127.0.0.1:2181");
    application.setRegistry(registryConfig);
    application.setQosEnable(false);

    ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
    String group = "dubbo";
    String version = "1.0.0.daily";
    String interfaceName = "org.apache.dubbo.samples.api.GreetingService";
    reference.setGeneric(true);
    reference.setApplication(application);
    reference.setInterface(interfaceName);
    reference.setVersion(version);
    reference.setGroup(group);
    GenericService genericService = reference.get();

    Object data = genericService.$invoke("sayHello", new String[] {
     "java.lang.String" },
                                         new Object[] {
     "wangji" });

    System.out.println("result:" + data);

 }
// result:hello, wangji

2.3 含有泛型对象的泛化调用

如果你不知道怎么编写这个参数,可以 PojoUtils.generalize(data) 通过这个接口来生成对象,来看一下生成的数据的结构是咋样的。

注意泛型参数传递的参数类型不包含 java.util.List<org.apache.dubbo.samples.Dto.User> 后面的泛型的信息,只需要传递 java.util.List 即可。

 public static void main(String[] args) {
    

     ApplicationConfig application = new ApplicationConfig();
     application.setName("Test-" + UUID.randomUUID().toString());
     RegistryConfig registryConfig = new RegistryConfig();
     registryConfig.setAddress("zookeeper://127.0.0.1:2181");
     application.setRegistry(registryConfig);
     application.setQosEnable(false);

     ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
     String group = "dubbo";
     String version = "1.0.0.daily";
     String interfaceName = "org.apache.dubbo.samples.api.GreetingService";
     reference.setGeneric(true);
     reference.setApplication(application);
     reference.setInterface(interfaceName);
     reference.setVersion(version);
     reference.setGroup(group);
     GenericService genericService = reference.get();

     List<User> users = new ArrayList<>();
     User user = new User();
     user.setName("other");
     user.setAge(31);
     users.add(user);

     Object generalize = PojoUtils.generalize(users);
     System.out.println(generalize);

     Object getListUserIncludeMe = genericService.$invoke("getListUserIncludeMe", new String[] {
     "java.util.List" },
                                                          new Object[] {
     generalize });

     System.out.println(getListUserIncludeMe);

    }

// [{name=other, class=org.apache.dubbo.samples.Dto.User, age=31}]
// [{name=other, class=org.apache.dubbo.samples.Dto.User, age=31}, {name=wangji, class=org.apache.dubbo.samples.Dto.User, age=28}]

2.4 指定IP调用Provider

测试环境中相同的服务会存在多台,如果你想调用具体的某台机子咋办呢?指定具体的IP进行调用,没有看到官方的文档、问了一下小杰子的实践,参考试了一下泛化调用直接ip 也是ok的,只需要找到具体的服务提供者即可。
不过不同的版本好像不一样,没有仔细尝试


设置服务的url 即可满足,能够定位一个服务即可

dubbo 版本 2.7.2
reference.setUrl("dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService");

dubbo 版本 2.7.0

需要添加分组的信息才行

reference.setUrl("dubbo://192.168.1.3:20880/分组/org.apache.dubbo.samples.api.GreetingService");

指定ip调用完整的例子

基于 2.7.2 版本

public static void main(String[] args) {
    
    ApplicationConfig application = new ApplicationConfig();
    application.setName("Test-" + UUID.randomUUID().toString());
    RegistryConfig registryConfig = new RegistryConfig();
    registryConfig.setAddress("zookeeper://127.0.0.1:2181");
    application.setRegistry(registryConfig);
    application.setQosEnable(false);

    ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
    String group = "dubbo";
    String version = "1.0.0.daily";
    String interfaceName = "org.apache.dubbo.samples.api.GreetingService";
    reference.setGeneric(true);
    reference.setApplication(application);
    reference.setInterface(interfaceName);
    reference.setVersion(version);
    reference.setGroup(group);

    reference.setUrl("dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService");
    GenericService genericService = reference.get();

    Object data = genericService.$invoke("sayHello", new String[] {
     "java.lang.String" },
                                         new Object[] {
     "wangji" });

    System.out.println("result:" + data);

 }

issue 中错误的调用

java.util.List<org.apache.dubbo.samples.Dto.User> 这个是由于PojoUtils的实现问题,导致这样的写法是有问题的。

Object getListUserIncludeMe = genericService.$invoke("getListUserIncludeMe", new String[] { "java.util.List<org.apache.dubbo.samples.Dto.User>" },
                                                             new Object[] { generalize });
    
// at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
// at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:41)
// at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
// at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$CallbackRegistrationInvoker.invoke(ProtocolFilterWrapper.java:150)
// at org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:152)
// at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:102)
// at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:193)
// at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
// at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
// at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
// at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
// at java.lang.Thread.run(Thread.java:748)
// Caused by: java.lang.ClassNotFoundException: java.util.List<org.apache.dubbo.samples.Dto.User>
// at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
// at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
// at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
// at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
// at java.lang.Class.forName0(Native Method)
// at java.lang.Class.forName(Class.java:348)
// at org.apache.dubbo.common.utils.ReflectUtils.name2class(ReflectUtils.java:743)
// at org.apache.dubbo.common.utils.ReflectUtils.name2class(ReflectUtils.java:670)
// at org.apache.dubbo.common.utils.ReflectUtils.findMethodByMethodSignature(ReflectUtils.java:885)

3、实现原理

dubbo泛化调用原理 画的这个图不错十分的形象生动,可以总结一下 泛化的原理

原理简介

+-------------------------------------------+               +-------------------------------------------+
|  consumer 端                               |               | provider 端                                |
|                                           |               |                                           |
|                                           |               |                                           |
|                                           |               |                                           |
|                                           |               |                                           |
|                    +------------------+   |               |       +--------------+                    |
|                    |GenericImplFilter |   |  Invocation   |       |GenericFilter |                    |
|             +----> |                  +-------------------------> |              |                    |
|             |      +------------------+   |               |       +--------------+                    |
| +-----------+                             |               |                      |    +-----------+   |
| |           |                             |               |                      |    |           |   |
| |Client     |                             |               |                      +--> | Service   |   |
| |           |                             |               |                           |           |   |
| +-----------+                             |               |                           +-------+---+   |
|                                           |               |                                   |       |
|      ^             +------------------+   |               |       +--------------+            |       |
|      |             |GenericImplFilter |   |               |       |GenericFilter | <----------+       |
|      +-------------+                  | <-------------------------+              |                    |
|                    +------------------+   |               |       +--------------+                    |
|                                           |               |                                           |
|                                           |               |                                           |
|                                           |               |                                           |
|                                           |               |                                           |
+-------------------------------------------+               +-------------------------------------------+
  • 基于PojoUtils 简化复杂对象,不用引入二方包。
  • 针对 com.alibaba.dubbo.rpc.service.GenericService.$invoke(String method, String[] parameterTypes, Object[] args) 这个接口进行特殊判断,基于拦截器处理特殊拦截。
  • 基于拦截器,消费者端 GenericImplFilter 处理(不考虑dubbo 其他的复杂序列化的需求很简单,基本上啥都不做)
  • 基于拦截器,服务提供者端 GenericFilter处理,>1 基于接口、方法名称、方法参数类型查询具体的服务、服务的方法名称; 2> 基于PojoUtils 、方法参数类型、方法参数 反序列化为复杂的参数对象PojoUtils.realize(args, params, method.getGenericParameterTypes()) ;3、基于PojoUtils.generalize(appResponse.getValue()) 序列化返回值。

简单实现泛化的过滤器

消费者端
/**
 * GenericImplInvokerFilter 基于官方的class 2.7.2 删掉不要的东西,简化理解
 */
@Activate(group = CommonConstants.CONSUMER, value = GENERIC_KEY, order = 20000)
public class GenericImplFilter extends ListenableFilter {
    

    private static final Logger logger = LoggerFactory.getLogger(GenericImplFilter.class);

    private static final Class<?>[] GENERIC_PARAMETER_TYPES = new Class<?>[]{
    String.class, String[].class, Object[].class};

    public GenericImplFilter() {
    
        super.listener = new GenericImplListener();
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    
        String generic = invoker.getUrl().getParameter(GENERIC_KEY);
        if ((invocation.getMethodName().equals($INVOKE) || invocation.getMethodName().equals($INVOKE_ASYNC))
                && invocation.getArguments() != null
                && invocation.getArguments().length == 3
                && ProtocolUtils.isGeneric(generic)) {
    

            invocation.setAttachment(
                    GENERIC_KEY, invoker.getUrl().getParameter(GENERIC_KEY));
        }
        return invoker.invoke(invocation);
    }

    static class GenericImplListener implements Listener {
    
        @Override
        public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
    
            
        }

        @Override
        public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
    

        }
    }

}

生产者端
/**
 * GenericInvokerFilter. 基于官方的class 2.7.2 删掉不要的东西,简化理解
 */
@Activate(group = CommonConstants.PROVIDER, order = -20000)
public class GenericFilter extends ListenableFilter {
    

    public GenericFilter() {
    
        super.listener = new GenericListener();
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
    
        if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
                && inv.getArguments() != null
                && inv.getArguments().length == 3
                && !GenericService.class.isAssignableFrom(invoker.getInterface())) {
    
            String name = ((String) inv.getArguments()[0]).trim();
            String[] types = (String[]) inv.getArguments()[1];
            Object[] args = (Object[]) inv.getArguments()[2];
            try {
    
                // 找到调用的接口
                Method method = ReflectUtils.findMethodByMethodSignature(invoker.getInterface(), name, types);
                Class<?>[] params = method.getParameterTypes();
                if (args == null) {
    
                    args = new Object[params.length];
                }
                String generic = inv.getAttachment(GENERIC_KEY);

                if (StringUtils.isBlank(generic)) {
    
                    generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
                }

                if (StringUtils.isEmpty(generic)
                        || ProtocolUtils.isDefaultGenericSerialization(generic)) {
    
                    // 将PojoUtils 转换的简单对象 转换为复杂的对象 
                    args = PojoUtils.realize(args, params, method.getGenericParameterTypes());
                }
                return invoker.invoke(new RpcInvocation(method, args, inv.getAttachments()));
            } catch (NoSuchMethodException e) {
    
                throw new RpcException(e.getMessage(), e);
            } catch (ClassNotFoundException e) {
    
                throw new RpcException(e.getMessage(), e);
            }
        }
        return invoker.invoke(inv);
    }

    static class GenericListener implements Listener {
    

        @Override
        public void onResponse(Result appResponse, Invoker<?> invoker, Invocation inv) {
    
            if ((inv.getMethodName().equals($INVOKE) || inv.getMethodName().equals($INVOKE_ASYNC))
                    && inv.getArguments() != null
                    && inv.getArguments().length == 3
                    && !GenericService.class.isAssignableFrom(invoker.getInterface())) {
    

                String generic = inv.getAttachment(GENERIC_KEY);
                if (StringUtils.isBlank(generic)) {
    
                    generic = RpcContext.getContext().getAttachment(GENERIC_KEY);
                }

                if (appResponse.hasException() && !(appResponse.getException() instanceof GenericException)) {
    
                    appResponse.setException(new GenericException(appResponse.getException()));
                }
                // 设置反序列化 讲复杂对象转换为简单的基础对象
                appResponse.setValue(PojoUtils.generalize(appResponse.getValue()));
                
            }
        }

        @Override
        public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
    

        }
    }
}

基于真实数据观察 过滤器的值的传递

org.apache.dubbo.rpc.RpcInvocation 打印的效果,方便理解。

public String toString() {
    
        return "RpcInvocation [methodName=" + methodName + ", parameterTypes="
                + Arrays.toString(parameterTypes) + ", arguments=" + Arrays.toString(arguments)
                + ", attachments=" + attachments + "]";
    }

消费者端GenericImplFilter观察

org.apache.dubbo.rpc.filter.GenericImplFilter

GenericImplFilter 消费者 invoker.getUrl()的值
dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService?application=Test-0519fed7-71d4-494b-b225-15f153bb79db&dubbo=2.0.2&generic=true&group=dubbo&interface=org.apache.dubbo.samples.api.GreetingService&lazy=false&pid=12632&qos-enable=false&register.ip=192.168.1.3&side=consumer&sticky=false&timestamp=1591110891800&version=1.0.0.daily

GenericImplFilter 消费者 invocation.toString()的值
RpcInvocation 
[methodName=$invoke,//调用的方法
parameterTypes=[class java.lang.String, class [Ljava.lang.String;,
class [Ljava.lang.Object;],//通用泛型的参数类型
arguments=[sayHello, [Ljava.lang.String;@68b32e3e, [Ljava.lang.Object;@bcef303],
attachments={}
]

生产者端GenericFilter 观察

org.apache.dubbo.rpc.filter.GenericFilter

GenericFilter RpcInvocation的toString的值
RpcInvocation 
[methodName=$invoke, 
parameterTypes=[class java.lang.String, class [Ljava.lang.String;, class [Ljava.lang.Object;],
arguments=[sayHello, [Ljava.lang.String;@4174acb6, [Ljava.lang.Object;@5486085f],
attachments={path=org.apache.dubbo.samples.api.GreetingService, 
input=351, dubbo=2.0.2, 
interface=org.apache.dubbo.samples.api.GreetingService, 
version=1.0.0.daily, generic=true, 
group=dubbo}]

GenericFilter invoker.getUrl()
dubbo://192.168.1.3:20880/org.apache.dubbo.samples.api.GreetingService?anyhost=true&application=zookeeper-demo-provider&bean.name=ServiceBean:org.apache.dubbo.samples.api.GreetingService:1.0.0.daily:dubbo&bind.ip=192.168.1.3&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=dubbo&interface=org.apache.dubbo.samples.api.GreetingService&methods=sayHello,getListUserIncludeMe&pid=12595&qos-accept-foreign-ip=false&register=true&release=2.7.2&revision=1.0.0.daily&side=provider&timestamp=1591110149299&version=1.0.0.daily

4、总结

对于dubbo的泛化调用的文章一直想记录一篇,一直苦于没有思路,不知道怎么找到合适的切入角来描述这个问题,正好最近协助同事排查问题,正好有所收获,因此记录一篇博客,在了解过程中不断的进化,加深了对于dubbo的认知。6-2 号,特殊的日子,好像是我阳历的生日 happy ~~




版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012881904/article/details/106512572

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签