使用 OrangePi 4 LTS 做旁路由

前言 最近同居的学长从香港带了个最新版的 Apple TV 回来,但是 Apple TV 在国内用不了,必须要代理才可以,所以考虑到了路由器代理的方案。但是现在家里在用的路由器是 Redmi AX3000,没法刷固件,也不想再买个 R2S 这种软路由了,因此就计划将公司之前用完的 香橙派(OrangePi) 4 LTS 利用一下。它的芯片是 RK3399,用来做路由器绰绰有余,不用可惜了。 为什么是旁路由? 不熟悉旁路由的同学可以看篇指南:从听说到上手,人人都能看懂的旁路由入门指南 找了一些常见的固件,貌似只有针对于 OrangePi R1 和 OrangePi R1 Plus 编译的软路由固件,并且讯龙官方也有放出针对这两个板子的 Openwrt 固件1,可以说是就是为软路由而生,价格也比较便宜,200 左右,性能和 R2S 差别不大。 试了一下在 4 LTS 上跑 R1 和 R1 Plus 的固件2,都无法正常点亮,可能可以自己编译一个对应架构的 OpenWrt,但是有点麻烦,还是没尝试。 考虑到 4 LTS 上可以使用 Docker,因此可以在上面用 Docker 跑一个 OpenWrt 然后做旁路由,到时候只需要把 Apple TV 或者路由器的网关和 DNS 改一下就可以,因此最终选择使用旁路由方案。 具体方案 这里直接参考了小苏的教程:https://mlapp.cn/376.html,写的很清晰,而且很好的考虑了没有基础的同学,赞一个! 里面写的很详细,但是需要注意的是,因为香橙派4 LTS是 ARMv8 架构的,但是直接拉教程里面的默认 latest 镜像是 ARMv7 的,因此需要指定一下镜像的版本,具体的镜像版本可以看 Docker Hub 中的介绍。 docker pull registry.cn-shanghai.aliyuncs.com/suling/openwrt:armv8 --- docker run --restart always --name openwrt -d --network macnet --privileged registry.cn-shanghai.aliyuncs.com/suling/openwrt:armv8 /sbin/init 除此以外,根据它的指导来基本就不会有问题,其中如果需要给路由器下所有设备代理就把路由器的网关和 DNS 改为旁路由的 IP,如果是单独给某个设备(例如只给 Apple TV 终端),就可以只给单个设备设置 DNS 和 网关。 ...

December 31, 2022 · zzsqwq

C++ 类使用注意事项

前言 本文包含一些在使用 C++ 类时的注意事项,可避免一些常见问题,并能让你在写代码时更加自信。 因为目前我使用的是 C++ 17,所以仅保证以下内容在 C++ 17 中正确。 构造函数 对于单个参数的构造函数,推荐添加 explicit 关键字,防止隐式转换错误调用构造函数。 class DemoClass { explicit DemoClass(int test) { this->test_ = test; } } 显式声明有参构造函数后,编译器不会自动生成无参构造函数,若需要,请添加 DemoClass() = default; class DemoClass { explicit DemoClass(int test) { this->test_ = test; } DemoClass() = default; } 若子类中没有显式调用父类的构造函数,子类会默认调用父类的无参构造函数,如果父类没有无参构造函数,会报错。 析构函数 基类析构函数应该声明为 virtual,这样可以防止子类无法正确的析构。 由于基类析构函数为 virtual,派生类的析构函数应该显式的 override。 class DemoClass { virtual ~DemoClass() = default; } class ChildClass: public DemoClass { ~ChildClass() override { xxx; } } 成员变量默认值 对于类或局部作用域中未明确指定默认值的成员变量,其值遵循以下规则: 对于原生类型(primitive types),即 int、float、int*、string* 等,默认值为随机的脏数据。 对于对象(objects),例如自定义类、或 std::string 等,会调用默认(无参)构造函数,若没有默认(无参)构造函数,则报错。 对于引用(reference)类型,例如 std::string&,必须赋初始值或在构造函数中初始化,若为初始化,则报错。 float age; // 脏数据、随机值(无意义),此时访问会出现未定义行为 int *ptr; // 脏数据、随机值(无意义),此时访问会出现未定义行为 string name; // 调用默认构造函数,对于 std::string 来说为空字符串 "" DemoClass demo; // 若 DemoClass 存在无参构造函数,则调用,否则编译错误 string *pname; // 脏数据、随机值(无意义),此时访问会出现未定义行为 string &rname; // 编译错误,必须显式初始化 const string &crname; // 同上,编译错误 成员变量初始化方式 初始化成员变量一般而言有四种方式,一般而言第二种和第四种方式为等价的,不过在下面我们会看到一种例外: ...

November 9, 2022 · zzsqwq

关于美的一些思考

楔子 昨天听了学弟们关于博客搭建的一些教程,在听的过程中,也想起了我之前对于博客的种种尝试,发现整体的趋势就是,简约再简约。 这引发了我对我过去几年的审视,感觉自己的审美一直在变化,对于美的认识和感受也是一直在变的,故写此文来记一些我对美认识的思考。 简约是我最终的归宿 遥记得我还在小学和初中时,非常迷恋 QQ飞车 这款游戏,这款游戏里面有一个虚拟人物,人物可以搭配各种装饰,包括但不限于上衣、裤子、发型、发饰、翅膀等,起初我是喜欢要多花哨有多花哨造型,各种发饰、翅膀、脸部特效搞的飞起,现在想起来真是非常搞笑,而且喜欢五颜六色的,当时觉得,真的是太好看了。 但是过了一段时间,就开始感觉不对劲了,这么花哨,这么丑,这怎么可能是我当时亲手选择的造型。后来就逐渐向简约范靠拢,装饰越简单,色调越单一越喜欢。 下面的几张图大致可以窥见这个过程: 而长大后,对于 Blog 的搭建貌似也是这个过程。一开始喜欢相对花哨的,包括各种炫酷的背景、动效等,因此一开始选择了高定制度、有各种花哨特效的 Hexo 框架,不过当时已经在飞车中喜欢上了简约风格,因此主题也选择了 Hexo 中相对简洁的 NexT 主题。 后来发现 Hexo 实在操作过程有些过于繁杂,从操作的意义上来说,他无法做到足够的简洁、简约和高效。 故而我选择了操作更加方便(例如发表博文、操作主题元素)更加方便的动态博客——Typecho。 起初,我选择延续了在 Hexo 上的 NexT 主题,后来发现了更加符合我审美的 Handsome 主题,第一眼就是非常喜欢,后来忍痛买了主题,用了一段时间后,又逐渐觉得主题没有那么好看,有些看腻了,而且动态博客的升级、安装插件,也是十分繁杂。 最后,偶然之下,发现了现在在使用的 Hugo 框架,它生成迅速、无需升级、也有着很多简约风的主题,完美符合我的需求,果断迁移了过来,具体流程可见:记一次博客迁移记录 - Zs’s Blog ,一直用到现在,还是对这个框架和主题算是相对满意的。 总体而言,无论是操作还是页面,都是越来越简约、统一了。不知道未来,我会不会觉得这个也不够简约和统一,再去找寻其他的框架呢。 各种产品的变迁 除了上面我自己对美认识的变化,我觉得整个社会对美的认识也是存在变化的。 往大了讲,是各种建筑、各种设施外观的变化。 往小了讲,我们用的手机、手机上的软件(例如 QQ、微信),他们的外观每年都在迭代,可能每次变化时有人吐槽有人叫好,但是整体而言,至少对我来说,他是整体都是越来越好看的,虽然逐渐变得臃肿,但是 UI 确实是“实打实”的在进步。下图是 QQ 的变化(左侧是网友仿的一个老版 QQ 界面1,右图是最新版 Mac QQ),可见一二: 那就引发了我一个思考,大家对于美的认识是被塑造了呢,还是说这个东西就是客观上的变好看了,美毕竟是一个比较主观的东西。 如果他是主观上变好看了,那么他是真的好看了吗?而且为什么可以做到如此的统一,让大多数人都觉得变好看了。 如果他是客观上变好看了,那么到底是什么因素的存在让人觉得他好看呢? 我觉得美这个东西,很不像生活中常见的发展规律,例如芯片的研发,需要数学、物理、化学等学科实际理论的支持。 2001 年的人是没有办法设计出 Apple M2 的芯片的,因为受到了各种硬件的制约。而 2001 年的设计师有没有可能设计出最新版 Mac QQ 这样的界面呢,如果不能,是什么制约着它呢?如果能,为什么没有出现这种设计,是因为当时大家觉得他不好看吗? 因为我不是学设计和美术等专业的,并且这种很很直觉的东西我不知道该怎么去搜索,因此想在这里和大家探讨一下。如果有已经相对成熟的理论,还请大家能够告知与我,解答困扰我好久的疑问。 AI 绘图 看网上有人调侃说,愿意称 2022 年为 AI 绘图元年。 ...

October 30, 2022 · zzsqwq

如何善用搜索引擎?

前言 我们使用搜索引擎可能是为了搜集信息、或者是解决一些问题。但如果不能正确的使用搜索引擎,使用关键字等,可能搜到的答案就与问题相关不大,或者与我们的问题相差甚远。 这篇文章意在分享我个人在学习、工作中使用搜索引擎的一些技巧与思考,可能后续会不断的更新,如有纰漏,还请大家指正。 应该使用什么搜索平台? 想要提高搜索的效率,选择一个好的搜索引擎或者内容平台颇为重要。 下面列举的是我平时会使用的一些平台,列出的顺序代表了我个人对于他们的推荐程度: 通用搜索引擎:Google、Bing、DuckDuckGo、Baidu 编程相关问题:Stack Overflow、Github Issue、segmentfault、稀土掘金、腾讯云开发者社区、CSDN、华为云开发者社区 日常问题:V2EX 第一类为通用搜索引擎,例如 为什么最近头发掉的更多了? 在其上可以搜索任何问题,其中 Google 的使用门槛相对较高,而 Bing 针对国内用户来说是一个相对不错的选择,不建议常用 Baidu,因为其上广告非常多,有用信息密度相对较小。 第二类为编程相关问题,例如 CMake 为什么报错了? 其中 Stack Overflow、Github 虽均为国外的平台,但是国内是可以访问的,不过在 Baidu 搜索引擎中词条相对靠后,同时也需要使用英文来进行问题检索。(Btw,Stack 系列还有例如 Stack Exchange 平台,它的模式与前者相同,但是更偏日常一些。)除此以外,后面的平台均为国内平台,所列是我平时常见的一些,整体而言,前面的文章质量普遍会比后面的高一些,其中腾讯云开发者社区、CSDN、华为云开发者社区三者内容重叠度较高。 第三类为日常问题,例如 Mac 上有什么好用的 App ?可以在 V2EX 上寻找答案,当然,它也是包罗万象的,只不过我常用它来解决日常各类小问题。 如何正确的知道问题所在? 我们遇到一个问题,要首先能够定位这个问题的关键。 例如编译中出错了,可能有很多 log,你必须能够精确定位到到底哪一条 log 才是这次编译出错的关键,进而才能解决这个问题。 以 CMake 为例,编译时报错 log 一般为红色,警告 log 一般为黄色,普通 log 一般为白色。得益于这种等级/颜色分明的 log ,我们可以快速的定位报错。我们日常编程中,为了便于 Debug,最好也遵循这个规范,例如在 C++ 中打印错误 log 时推荐使用 std::stderr 而不是 std::stdout 。 见过很多身边的同学遇到了报错,就一股脑粘贴所有的报错 log,然后贴到搜索引擎中,发现相关的错误基本没有。这就是没有其中错误的关键,报错时的 log 可能 100 行只有 10 行是关键的,甚至只有一行是关键的,例如 100 行 log 中只有 未定义的引用(undefined reference to ‘xxx’) 这一句是最关键的。 ...

October 30, 2022 · zzsqwq

为什么要使用条件变量?

为什么要使用条件变量? 前言 最近看了很多与线程有关的 C++ 新特性,条件变量是见的比较多的一个特性。 看的时候我发现,想要理解一个新的特性,关键的要看它的引入到底解决了哪些问题,没有什么特性我们要实现相同的功能要怎么做? 以我的理解来看,条件变量是一个线程间互相同步与通知的手段,他通过主动唤醒的方式减小了各个线程的开销,取代了简单但是消耗较大的一直被动循环检验与等待。 没有条件变量我们如何实现相同的需求? 这里采用现代C++教程1 中关于条件变量的一个例子作为基础: 不使用条件变量版本 #include <queue> #include <chrono> #include <mutex> #include <thread> #include <iostream> #include <condition_variable> int main() { std::queue<int> produced_nums; std::mutex mtx; // 生产者 auto producer = [&]() { for (int i = 0; ; i++) { std::this_thread::sleep_for(std::chrono::milliseconds(900)); std::unique_lock<std::mutex> lock(mtx); std::cout << "producing " << i << std::endl; produced_nums.push(i); } }; // 消费者 auto consumer = [&]() { while (true) { { std::unique_lock<std::mutex> lock(mtx); if(produced_nums.empty()) continue; } std::unique_lock<std::mutex> lock(mtx); // 短暂取消锁,使得生产者有机会在消费者消费空前继续生产 lock.unlock(); // 消费者慢于生产者 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); lock.lock(); while (!produced_nums.empty()) { std::cout << "consuming " << produced_nums.front() << std::endl; produced_nums.pop(); } } }; // 分别在不同的线程中运行 std::thread p(producer); std::thread cs[2]; for (int i = 0; i < 2; ++i) { cs[i] = std::thread(consumer); } p.join(); for (int i = 0; i < 2; ++i) { cs[i].join(); } return 0; } 使用条件变量版本 #include <queue> #include <chrono> #include <mutex> #include <thread> #include <iostream> #include <condition_variable> int main() { std::queue<int> produced_nums; std::mutex mtx; std::condition_variable cv; bool notified = false; // 通知信号 // 生产者 auto producer = [&]() { for (int i = 0; ; i++) { std::this_thread::sleep_for(std::chrono::milliseconds(900)); std::unique_lock<std::mutex> lock(mtx); std::cout << "producing " << i << std::endl; produced_nums.push(i); notified = true; cv.notify_all(); // 此处也可以使用 notify_one } }; // 消费者 auto consumer = [&]() { while (true) { std::unique_lock<std::mutex> lock(mtx); while (!notified) { // 避免虚假唤醒 cv.wait(lock); } // 短暂取消锁,使得生产者有机会在消费者消费空前继续生产 lock.unlock(); // 消费者慢于生产者 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); lock.lock(); while (!produced_nums.empty()) { std::cout << "consuming " << produced_nums.front() << std::endl; produced_nums.pop(); } notified = false; } }; // 分别在不同的线程中运行 std::thread p(producer); std::thread cs[2]; for (int i = 0; i < 2; ++i) { cs[i] = std::thread(consumer); } p.join(); for (int i = 0; i < 2; ++i) { cs[i].join(); } return 0; } 这两段代码在效果上是等效的,都是一个生产者两个消费者。 ...

August 24, 2022 · zzsqwq