2024.2.21 模拟实现 RabbitMQ —— 实现转发规则

目录

需求分析

直接交换机(Direct )

主题交换机(Topic )

扇出交换机(Fanout )

Topic 交换机转发规则

routingKey 组成

bindingKey 组成

匹配规则

情况一

情况二

情况三

实现 Router 类

校验 bindingKey 和 routingKey

消息匹配机制

Topic 交换机匹配规则

针对 Router 单元测试


需求分析

直接交换机(Direct )

  • 通过设定 消息的 routingKey = 队列名,以此指定该消息需传给哪个队列

实例理解

  • 如下图所示,此时可直接无视绑定关系,直接从内存中拿到对应队列名的队列
  • 然后再将消息传给该队列即可


主题交换机(Topic )

  • 依据 Topic 交换机的转发规则,判定消息需传给哪些队列

扇出交换机(Fanout )

  • 给该交换机中所有绑定好的队列均传入消息

Topic 交换机转发规则

  • bindingKey:创建绑定时,给绑定指定的特殊字符串(相当于出题)
  • routingKey:发布消息时,给消息上指定的特殊字符串(相当于做答案)
  • 当 routingKey 的答案能够与 bindingKey 相对应时,便可将消息转发给该队列 

routingKey 组成

  1. 数字、字母、下划线
  2. 使用 . 将整个 routingKey 分成多个部分

实例理解

  •  aaa.bbb.ccc    合法
  • aaa.110.bbb     合法
  • aaa                   合法

bindingKey 组成

  1. 数字、字母、下划线
  2. 使用 . 将整个 bindingKey 分成多个部分
  3. 支持两种特殊的符号作为通配符( * 和 # 必须是作为被 . 分割出来的独立的部分)
  •  * ——> 可以匹配任何一个 独立的部分
  • # ——> 可以匹配任何 0 个或者多个独立的部分

实例理解

  • aaa.*.bbb       合法
  • aaa.*bb.ccc   非法

匹配规则

情况一
  • 当 bindingKey 中没有 * 或 # 这两个特殊符号时,必须要求 routingKey 和 bindingKey 一模一样,才能算匹配成功!

实例理解

  • bindingKey = aaa.bbb.ccc
  • routingKey = aaa.bbb.ccc (匹配成功)
  • routingKey = aaa.bbb.ccd (匹配失败)

注意:

  • 情况一非常类似于 Direct 交换机的转发
  • 尤其是将 bindingKey 设置成和队列名字相同,此时就完全等价于 Direct 交换机了!

情况二
  • 当 bindingKey 中有特殊符号 * 时

实例理解 

  • bindingKey = aaa.*.ccc
  • routingKey = aaa.bbb.ccc (匹配成功)
  • routingKey = aaa.b.ccc (匹配成功)
  • routingKey = aaa.b.b.ccc(匹配失败)

情况三
  • 当 bindingKey 中有特殊符号 # 时

实例理解

  • bindingKey = aaa.#.ccc
  • routingKey = aaa.bbb.ccc (匹配成功)
  • routingKey = aaa.b.b.ccc (匹配成功)
  • routingKey = aaa.ccc(匹配成功)
  • routingKey = aaa.b.b.b(匹配失败)

问题:

  • 将交换机中每个队列的 bindingKey 设置成一个 # 时,会有啥效果呢?

回答:

  • 此时,该交换机中的 全部队列 都能匹配所有的 routingKey
  • 即该交换机就相当于 Fanout 交换机了!

注意点一:

  • Direct 交换机 和 Fanout 交换机,均属于 Topic 交换机的 特例

注意点二:

  • 上述规则都是 AMQP 协议所约定的!RabbitMQ 仅仅只是实现了该规则而已!

实现 Router 类

校验 bindingKey 和 routingKey

//    bindingKey 的构造规则:
//    1、数字,字母,下划线
//    2、使用 . 分割成若干部分
//    3、允许使用 * 和 # 作为通配符,但是通配符只能作为独立的分段
    public boolean checkBindingKey(String bindingKey) {
        if(bindingKey.length() == 0) {
//            空字符串也是合法情况,比如在使用 direct / fanout 交换机的时候,bindingKey 是用不上的
//            因为我们在使用 direct 交换机时,是直接将 routingKey 作为 消息队列的名字,直接根据名字来进行匹配
//            在使用 fanout 交换机时,无需匹配,直接将该消息转给交换机中绑定的所有消息队列
            return true;
        }
//        检查字符串中不能存在非法字符
        for (int i = 0;i < bindingKey.length();i++) {
            char ch = bindingKey.charAt(i);
            if(ch >= 'A' && ch <= 'Z') {
                continue;
            }
            if(ch >= 'a' && ch <= 'z') {
                continue;
            }
            if(ch >= '0' && ch <= '9') {
                continue;
            }
            if(ch == '_' || ch == '.' || ch == '*' || ch == '#') {
                continue;
            }
            return false;
        }
//        检查 * 或者 # 是否是独立的部分
//        aaa.*.bbb 合法情况; aaa.a*.bbb 非法情况
        String[] words = bindingKey.split("\\.");
        for (String word : words) {
//            检查 word 长度 > 1 并且包含了 * 或者 #,就是非法的格式了
            if(word.length() > 1 && (word.contains("*") || word.contains("#")) ) {
                return false;
            }
        }
//        约定一下,通配符之间的相邻关系 (人为约定的)
//        为啥这么约定?因为前三种相邻的时候,实现匹配的逻辑会非常繁琐,同时功能性提升不大
//        1、 aaa.#.#.bbb 非法情况
//        2、 aaa.#.*.bbb 非法情况
//        3、 aaa.*.#.bbb 非法情况
//        4、 aaa.*.*.bbb 合法情况
        for (int i = 0;i < words.length;i++) {
//            连续两个 ##
            if(words[i].equals("#") && words[i+1].equals("#")) {
                return false;
            }
//            # *
            if(words[i].equals("#") && words[i+1].equals("*")) {
                return false;
            }
//            * #
            if(words[i].equals("*") && words[i+1].equals("#")) {
                return false;
            }
        }
        return true;
    }

//    routingKey 的构造规则
//    1、数字,字母,下划线
//    2、使用 . 分割成若干部分
    public boolean checkRoutingKey(String routingKey) {
        if(routingKey.length() == 0) {
//            空字符串,合法的情况,比如在使用 fanout 交换机的时候,routingKey 用不上,就可以设定为 ""
            return true;
        }
        for (int i = 0;i<routingKey.length();i++) {
            char ch = routingKey.charAt(i);
//            判定该字符是否是大写字母
            if(ch >= 'A' && ch <= 'Z') {
                continue;
            }
//            判定该字符是否是小写字母
            if(ch >= 'a' && ch <= 'z') {
                continue;
            }
//            判定该字符是否是阿拉伯数字
            if(ch >= '0' && ch <= '9') {
                continue;
            }
//            判定是否是 _ 或者 .
            if(ch == '_' || ch == '.') {
                continue;
            }
//            该字符,不是上述任何一种合法情况,就直接返回 false
            return false;
        }
//        把每个字符都检查过,没有遇到非法情况,此时直接返回 true
        return true;
    }

问题:

  • 观察下图,为啥 split 方法中的参数 "." 需要加两个反斜杠呢?

回答:

  • 首先 "." 在正则表达式中,是一个特殊的符号,此处是将 . 作为原始文本来进行匹配
  • 要想使用 . 的原始文本,就需要进行转义,即 在正则中使用 "\." 的方式来表示
  • 又因为在 Java 的字符串中,"\" 是一个特殊字符
  • 所以要想写入 "\." 这样的文本,又得在其前面加上一个反斜杠来进行转义,即 "\\."

消息匹配机制

//    这个方法用来判定该消息是否可以转发给这个绑定对应的队列
    public boolean route(ExchangeType exchangeType,Binding binding,Message message) throws MqException {
//        根据不同的 exhcangeType 使用不同的判定转发规则
        if(exchangeType == ExchangeType.FANOUT) {
//            如果是 fanout 类型,该交换机上绑定的所有队列都需要转发
            return true;
        }else if(exchangeType == ExchangeType.TOPIC) {
//            如果是 topic 主题交换机,规则就要更复杂一些
            return routeTopic(binding,message);
        }else {
//            其他情况是不应该存在的
            throw new MqException("[Router] 交换机类型非法! exchangeType = " + exchangeType);
        }
    }
  • 当为 Fanout 交换机时,无需匹配 bindingKey 和 routingKey,直接返回 true
  • 让该消息转发给所以绑定了 Fanout 交换机的队列
  • 当为 Topic 交换机时,则需要进行 bindingKey 和 routingKey 的匹配

Topic 交换机匹配规则

处理思路

  • 此处我们采用 双指针算法 进行匹配

  • 针对 bindingKey 的下标,判定当前下标指向部分的具体情况:
  1. 指向的是普通的字符串,此时要求和 routingKey 对应的下标指向的内容得完全一致!
  2. 指向的是 * ,此时无论  routingKey 这边指向的是啥,双方均同时下标前进
  3. 遇到了 # ,且 # 后面没有其他的内容了,直接返回 true,匹配成功!
  4. 遇到了 # ,但 # 后面还有其他的内容,拿着 # 后面的部分,去 routingKey 中查找,找到后面的部分,在 routingKey 中出现的位置,如果后面的部分,在 routingKey 中不存在,直接认为匹配失败,返回 false!如果后面的部分,在 routingKey 中存在,就将 routingKey 的箭头指向这个位置之后,然后继续往后匹配
  5. 两个箭头移动过程中,如果同时到达双方的末尾,则返回 true,如果一个箭头先到了末尾,另一个箭头还没到,则返回 false!

代码实现:

    private boolean routeTopic(Binding binding,Message message) {
//        先把这两个 key 进行切分
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");

//        引入两个下标,指向上述两个数字,初始情况下都为 0
        int bindingIndex = 0;
        int routingIndex = 0;
//        此处使用 while 更合适,每次循环,下标不一定就是 +1,不适合使用 for
        while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {
            if(bindingTokens[bindingIndex].equals("*")) {
//                【情况二】如果遇到 * 号直接进入下一轮,* 可以匹配到任意一个部分!
                bindingIndex++;
                routingIndex++;
                continue;
            }else if(bindingTokens[bindingIndex].equals("#")) {
//                如果遇到 # 先要看看有没有下一个位置
                bindingIndex++;
                if(bindingIndex == bindingTokens.length) {
//                    【情况三】该 # 后面没有东西了,说明此时一定能匹配成功了!
                    return true;
                }
//                # 【情况四】后面还有东西,拿着这个内容,去 routingKey 中往后找,找到对应的位置
//                findNextMatch 这个方法用来查找该部分在 routingKey 的位置,返回该下标,没找到,就返回 -1
                routingIndex = findNextMatch(routingTokens,routingIndex,bindingTokens[bindingIndex]);
                if(routingIndex == -1) {
//                    没找到匹配的结果,匹配失败
                    return false;
                }
//                找到匹配的情况,继续往后匹配
                bindingIndex++;
                routingIndex++;
            }else {
//                【情况一】如果遇到普通字符串,要求两边的内容是一样的
                if(!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }
//        【情况五】判断是否是双方同时到达末尾
//        比如 aaa.bbb.ccc 和 aaa.bbb 是要匹配失败的
        if(bindingIndex == bindingTokens.length && routingIndex == routingTokens.length) {
            return true;
        }
        return false;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for (int i = routingIndex; i < routingTokens.length; i++) {
            if(routingTokens[i].equals(bindingToken)) {
                return i;
            }
        }
        return -1;
    }

针对 Router 单元测试

  • 编写测试用例代码是十分重要的!
package com.example.demo;

import com.example.demo.common.MqException;
import com.example.demo.mqserver.core.Binding;
import com.example.demo.mqserver.core.ExchangeType;
import com.example.demo.mqserver.core.Message;
import com.example.demo.mqserver.core.Router;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class RouterTests {
    private Router router = new Router();
    private Binding binding = null;
    private Message message = null;

    @BeforeEach
    public void setUp() {
        binding = new Binding();
        message = new Message();
    }

    @AfterEach
    private void tearDown() {
        binding = null;
        message = null;
    }

//    【测试用例】
//    bindingKey               routingKey               result
//    aaa                      aaa                      true
//    aaa.bbb                  aaa.bbb                  true
//    aaa.bbb                  aaa.bbb.ccc              false
//    aaa.bbb                  aaa.ccc                  false
//    aaa.bbb.ccc              aaa.bbb.ccc              true
//    aaa.*                    aaa.bbb                  true
//    aaa.*.bbb                aaa.bbb.ccc              false
//    *.aaa.bbb                aaa.bbb                  false
//    #                        aaa.bbb.ccc              true
//    aaa.#                    aaa.bbb                  true
//    aaa.#                    aaa.bbb.ccc              true
//    aaa.#.ccc                aaa.ccc                  true
//    aaa.#.ccc                aaa.bbb.ccc              true
//    aaa.#.ccc                aaa.aaa.bbb.ccc          true
//    #.ccc                    ccc                      true
//    #.ccc                    aaa.bbb.ccc              true

    @Test
    public void test1() throws MqException {
        binding.setBindingKey("aaa");
        message.setRoutingKey("aaa");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test2() throws MqException {
        binding.setBindingKey("aaa.bbb");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test3() throws MqException {
        binding.setBindingKey("aaa.bbb");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test4() throws MqException {
        binding.setBindingKey("aaa.bbb");
        message.setRoutingKey("aaa.ccc");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test5() throws MqException {
        binding.setBindingKey("aaa.bbb.ccc");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test6() throws MqException {
        binding.setBindingKey("aaa.*");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test7() throws MqException {
        binding.setBindingKey("aaa.*.bbb");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test8() throws MqException {
        binding.setBindingKey("*.aaa.bbb");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test9() throws MqException {
        binding.setBindingKey("#");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test10() throws MqException {
        binding.setBindingKey("aaa.#");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test11() throws MqException {
        binding.setBindingKey("aaa.#");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test12() throws MqException {
        binding.setBindingKey("aaa.#.ccc");
        message.setRoutingKey("aaa.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test13() throws MqException {
        binding.setBindingKey("aaa.#.ccc");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test14() throws MqException {
        binding.setBindingKey("aaa.#.ccc");
        message.setRoutingKey("aaa.aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test15() throws MqException {
        binding.setBindingKey("#.ccc");
        message.setRoutingKey("ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test16() throws MqException {
        binding.setBindingKey("#.ccc");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/402631.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

为新固态硬盘安装操作系统

目录 背景方案具体步骤1 为新硬盘进行分区2 下载Dism3 下载win10的iso文件4 通过Dism 重装系统5 从biso调整启动顺序5 遗留问题 参考资料 背景 情况是这样的&#xff0c;我的电脑本来就有一块sata的固态硬盘&#xff0c;作为c盘&#xff0c;装载的是win10系统。 一方面只有500…

滚雪球学Java(68):全面了解Java中常用的集合类:LinkedHashMap的应用与实践

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE相关知识点了&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好…

【JAVA高级面试题】运用锁机制实现一个自定义的阻塞队列

文章目录 前言实战演示写在最后 前言 前几天看见一个高级Java面试题&#xff0c;我觉得很有代表意义。既考察了面试者的基本锁机制运用&#xff0c;也了解了阻塞队列的产生实现原理。先分享出来&#xff0c;以供鉴赏。 面试题&#xff1a;实现一个自定义的阻塞队列&#xff0c…

git@github.com: Permission denied (publickey).

这个问题是使用git克隆自己的私有代码仓库时发现的&#xff0c;虚拟机中执行下面的指令生成了ssh私钥和公钥 ssh-keygen并且成功将公钥上传到了github上 但是执行git clone克隆私有仓库时依旧失败&#xff0c;报错内容和标题差不多 通过这个报错最终还是锁定了问题&#xff…

牛客网 JZ53 数字在升序数组中出现的次数

思路&#xff1a; 因为是非降序数组&#xff0c;所以可以通过二分法快速排查范围&#xff0c;等mid值找到对应k时&#xff0c;只需要往前找有多少个k和往后找有多少个k&#xff0c;计算出总数即可 答案&#xff1a; int GetNumberOfK(int* nums, int numsLen, int k ) {int…

程序员必备技能----删库跑路大总结

删库跑路大总结&#xff0c;各个都是大杀器&#xff0c;破坏性太大&#xff0c;轻易不要尝试。 删除linux根目录&#xff0c;用户目录&#xff0c;其实还可以增加一个删除/etc。删除&#xff08;清除&#xff09;数据库。删除redis缓存和持久化文件。删除mongodb库。git push …

Android 沉浸式状态栏

过时的API //设置默认隐藏虚拟按键&#xff0c;虚拟按键显示后为半透明protected open fun hideNavigationBarAndFullScreen() {val flags: Int// This work only for android 4.4flags if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {// This work only for a…

Java面向对象(四)

一、面向对象特征&#xff08;三&#xff09;---多态 父类引用指向子类的对象&#xff0c;从而产生多种形态 例&#xff1a; Animal dog new Dog(); 同一种事物&#xff0c;在不同的时刻表现不同的状态前提&#xff1a;二者存在直接或间接的继承关系时&#xff0c;/*//装狗pu…

SSH tunneling 简明示例

基本概念 SSH tunneling又称为SSH port forwarding。 如果想快速了解其应用场景&#xff0c;这篇文章A short guide to SSH port forwarding 很不错。其详细解释了Client to Server的Local Forwarding。虽然没有涉及Server to Client的Remote Forwarding&#xff0c;但他也指…

高盛:日本这轮通胀是否可持续,关键看房租

租金在日本CPI中的权重高达20%&#xff0c;高盛预计短期内租金将继续拖累通胀至1.7%或以下&#xff0c;长期有望温和上行&#xff0c;使通胀稳在2%的水平。 日本正在转向“去通缩”&#xff0c;房租能否支撑通胀态势&#xff1f; 在日股今年一路高歌、有望“收复失地”时&…

STM32 4位数码管和74HC595

4位数码管 在使用一位数码管的时候&#xff0c;会用到8个IO口&#xff0c;那如果使用4位数码管&#xff0c;难道要使用32个IO口吗&#xff1f;肯定是不行的&#xff0c;太浪费了IO口了。把四个数码管全部接一起共用8个IO口&#xff0c;然后分别给他们一个片选。所以4位数码管共…

想高薪就业鸿蒙HarmonyOS 开发岗位,到底该学习些啥?

鸿蒙是什么&#xff1f; 经过十多年的发展&#xff0c;传统移动互联网的增长红利已渐见顶。万物互联时代正在开启&#xff0c;应用的设备底座将从几十亿手机扩展到数百亿 IoT 设备。GSMA 预测到 2025 年&#xff0c;全球物联网终端连接数量将达 246 亿个&#xff0c;其中消费物…

Nginx网络服务三-----(三方模块和内置变量)

1.验证模块 需要输入用户名和密码 我们要用htpasswd这个命令&#xff0c;先安装一下httpd 生成文件和用户 修改文件 访问页面 为什么找不到页面&#xff1f; 对应的路径下&#xff0c;没有这个文件 去创建文件 去虚拟机浏览器查看 有的页面不想被别人看到&#xff0c;可以做…

电脑蓝牙在哪里打开?1分钟轻松打开蓝牙!

“我在操作电脑的时候想将电脑的蓝牙打开来连接音响和键盘&#xff0c;但是不知道电脑蓝牙应该如何打开&#xff0c;有什么比较简单的方法吗&#xff1f;” 随着无线技术的日益发展&#xff0c;蓝牙已成为连接各种设备的重要桥梁。无论是传输文件、音频还是与外部设备进行通信&…

mysql优化指南之原理篇

之前碰到一个线上问题&#xff0c;在接手一个同事的项目后&#xff0c;因为工期比较赶&#xff0c;我还没来得及了解业务背景和大致实现&#xff0c;只是了解了上线发布的顺序和验证方式就进行了上线&#xff0c;在上线进行金丝雀的时候系统还没发生什么异常&#xff0c;于是我…

独家深度 | 一文看懂 ClickHouse vs Elasticsearch:谁更胜一筹?

简介&#xff1a; 本文的主旨在于通过彻底剖析ClickHouse和Elasticsearch的内核架构&#xff0c;从原理上讲明白两者的优劣之处&#xff0c;同时会附上一份覆盖多场景的测试报告给读者作为参考。 作者&#xff1a;阿里云数据库OLAP产品部 仁劼 原文地址:https://developer.ali…

【鸿蒙 HarmonyOS 4.0】路由router

一、介绍 页面路由指在应用程序中实现不同页面之间的跳转和数据传递。HarmonyOS提供了Router模块&#xff0c;通过不同的url地址&#xff0c;可以方便地进行页面路由&#xff0c;轻松地访问不同的页面。 二、页面跳转 2.1、两种跳转模式&#xff1a; router.pushUrl()&…

为全志D1开发板移植LVGL日历控件和显示天气

利用TCP封装HTTP包请求天气信息 Linux还真是逐步熟悉中&#xff0c;现在才了解到Linux即没有原生的GUI&#xff0c;也没有应用层协议栈&#xff0c;所以要实现HTTP应用&#xff0c;必须利用TCP然后自己封装HTTP数据包。本篇即记录封装HTTP数据包&#xff0c;到心知天气请求天气…

【SiamFC】《Fully-Convolutional Siamese Networks for Object Tracking》

ECCV 2016 Workshops 文章目录 1 Background and Motivation2 Related Work3 Advantages / Contributions4 Method5 Experiments5.1 Datasets and Metrics5.2 The OTB-13 benchmark5.3 The VOT benchmarks5.4 Dataset size 6 Conclusion&#xff08;own&#xff09;/ Future wo…

JS基础之JSON对象

JS基础之JSON对象 目录 JS基础之JSON对象对象转JSON字符串JSON转JS对象 对象转JSON字符串 JSON.stringify(value,replacer,space) value:要转换的JS对象 replacer:(可选)用于过滤和转换结果的函数或数组 space:(可选)指定缩进量 // 创建JS对象 let date {name:"张三…