跟 UMU 一起玩 OpenWRT(入门篇16):Python3

为什么 Python3?

  1. Shell 不适合某些复杂运算,尤其是 OpenWRT 用的 ash。

  2. Lua 缺乏某些 SDK,比如说阿里云 SDK 就没有 Lua 版。

  3. Python2 已经过时。

  4. Node.js 在小型设备上不如 Python3 高效。

  5. C、C++ 之流太难了!Go、Rust 还得编译,麻烦。

  6. Perl、Ruby 已没落。

一个例子

当 IPv6 地址变化时,将地址发送到钉钉:https://github.com/UMU618/openwrt-ipv6-addresses

安装与调试

1. 安装可执行程序

1
opkg install python3-base

安装 python3-base 之后,就可以运行 python3 了。

1
2
3
4
5
6
7
root@UMU:~# python3
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]
Python 3.7.6 (default, Feb 11 2020, 12:41:31)
[GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

用以下 Python 代码,打印目前已有的模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import sys

# 打印的信息太长
i = 0
for m in sys.modules:
i += 1
print('{0:2d} {1:32s} {2}'.format(i, m, sys.modules[m]))

# 不精准
i = 0
for m in sys.modules.values():
i += 1
if m.__spec__:
v = m.__spec__.origin
elif m.__builtins__:
v = '-'
else:
v = ''
print('{0:2d} {1:32s} {2}'.format(i, m.__name__, v))

# 推荐使用
i = 0
for m in sys.modules.values():
i += 1
s = str(m)
start = s.find("'")
end = s.find("'", start+1)
k = s[start+1:end]
start = s.find('from', end+1)
if start > -1:
start += 4
end = s.find('>', start+1)
else:
start = s.find('(', end+1)
end = s.find(')', start+1)
v = s[start+1:end]
print('{0:2d} {1:28s} {2}'.format(i, k, v))

结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 1 sys                          built-in
2 builtins built-in
3 _frozen_importlib frozen
4 _imp built-in
5 _thread built-in
6 _warnings built-in
7 _weakref built-in
8 zipimport built-in
9 _frozen_importlib_external frozen
10 io built-in
11 marshal built-in
12 posix built-in
13 encodings '/usr/lib/python3.7/encodings/__init__.pyc'
14 codecs '/usr/lib/python3.7/codecs.pyc'
15 _codecs built-in
16 encodings.aliases '/usr/lib/python3.7/encodings/aliases.pyc'
17 encodings.utf_8 '/usr/lib/python3.7/encodings/utf_8.pyc'
18 _signal built-in
19 __main__ built-in
20 encodings.latin_1 '/usr/lib/python3.7/encodings/latin_1.pyc'
21 io '/usr/lib/python3.7/io.pyc'
22 abc '/usr/lib/python3.7/abc.pyc'
23 _abc built-in
24 site '/usr/lib/python3.7/site.pyc'
25 os '/usr/lib/python3.7/os.pyc'
26 stat '/usr/lib/python3.7/stat.pyc'
27 _stat built-in
28 posixpath '/usr/lib/python3.7/posixpath.pyc'
29 genericpath '/usr/lib/python3.7/genericpath.pyc'
30 posixpath '/usr/lib/python3.7/posixpath.pyc'
31 _collections_abc '/usr/lib/python3.7/_collections_abc.pyc'
32 _sitebuiltins '/usr/lib/python3.7/_sitebuiltins.pyc'
33 atexit built-in

下面来实现获取 IPv6 地址的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def net_hex_to_ipv6(h):
ipv6 = h[0:4]
i = 4
while i < len(h):
ipv6 += ':' + h[i:i+4]
i += 4
return ipv6

with open('/proc/net/if_inet6') as f:
for line in f:
p = line.split()
if p[3] == '00' and (int(p[4], 16) & 0x80) != 0x80:
ip = net_hex_to_ipv6(p[0])
print(ip)
f.close()

以上代码有个“美中不足”:只能打印地址的“首选格式”,不支持“压缩格式”。下面改进!

2. 安装轻量库

UMU 打算使用 socket 模块的工具函数格式化 IPv6 地址,但目前已安装的 python3-base 不带 socket 模块:

1
2
3
4
>>> import socket
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'socket'

所以需要安装 python3-light

1
opkg install python3-light

装完即可愉快地玩耍:

1
2
3
4
5
import socket

ip = '0618:0618:0618:0618:0000:0000:0000:0618'
print(ip, '->',
socket.inet_ntop(socket.AF_INET6, socket.inet_pton(socket.AF_INET6, ip)))

以上代码打印:0618:0618:0618:0618:0000:0000:0000:0618 -> 618:618:618:618::618

3. 全量安装

如果 python3-light 还不能满足您,推荐来个全家桶:

1
opkg install python3

PS: 不要以为只要上面这句就全装上了,前面的 opkg install python3-base 是必要的!如果只装 python3,则 /usr/bin/python3 并不存在!

(完)

Node.js 程序员的 C++ 进修指南【1】:SetTimeout

前言

  • 如果您看得懂,那么,这是 Node.js 程序员的 C++ 进修指南。

  • 如果您没看懂,那么,这是学 C++ 的劝退书!

目的

用 C++ 改写 Node.js 程序,主要目的可能有两个:保密、提高性能。

那么您肯定要问:为什么不用 Go 或者 Rust 改写?UMU 是推荐用 Go 或 Rust 的,而且相对改写为 C++ 要简单得多,本系列文章,可能从反面论证:您应该选择用 Go 或者 Rust 改写!

代码仓库

https://github.com/UMU618/cpp-for-nodejs-programmers

第一个例子

在《学习 Rust【2】减少代码嵌套》中,UMU 提到一个使代码平坦化的例子,咱们把其中最基本的功能提炼出来,成为最简单的例子:

1
2
3
4
5
6
7
8
9
10
11
setTimeout(() => {
console.log('step1')
}, 1000)

setTimeout(() => {
console.log('step2')
}, 2000)

setTimeout(() => {
console.log('step3')
}, 3000)

这段代码实现的功能是:一秒后打印 step1,再一秒后打印 step2,再一秒后打印 step3,退出。

翻译为 C++

首先明确一点:JavaScript 是 JIT 语言,不用编译,语言宿主直接解释运行。C++ 是 AOT 语言,需要编译。所以我们需要编译器(比如 g++、clang++)和编译脚本(比如 make、cmake)。下面我们会选择在 macOS 上使用 clang++ 和 cmake 来编译 C++ 代码。其中,cmake 其实是用来产生 Makefile 的,如果您学过 Makefile,可以直接用它。

安装依赖软件

  • 安装 Xcode,以获取 MacOSX.sdk。

  • 安装 clang++ 和 cmake:

1
2
brew install llvm
brew install cmake

STL 实现

  • C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// set_timeout.cc
// UMU: 这是一个不太好的实现
#include <chrono>
#include <future>
#include <iostream>
#include <vector>

class Timer {
public:
template <typename T>
void setTimeout(T function, int delay);
void stop();
void wait();

private:
bool clear = false;
std::vector<std::thread> pool;
};

template <typename T>
void Timer::setTimeout(T function, int delay) {
this->clear = false;
pool.emplace_back(std::thread([=]() {
if (this->clear) {
return;
}
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
if (this->clear) {
return;
}
function();
}));
}

void Timer::stop() {
this->clear = true;
}

void Timer::wait() {
for (std::thread& thread : pool) {
if (thread.joinable()) {
thread.join();
}
}
}

int main() {
Timer timer;
//timer.setTimeout([&]() { std::cout << "step1\n"; timer.stop(); }, 1000);
timer.setTimeout([]() { std::cout << "step1\n"; }, 1000);
timer.setTimeout([]() { std::cout << "step2\n"; }, 2000);
timer.setTimeout([]() { std::cout << "step3\n"; }, 3000);
timer.wait();
return 0;
}
  • cmake 脚本,CMakeLists.txt:
1
2
3
4
5
6
cmake_minimum_required (VERSION 3.5)
project (set_timeout)

add_executable(set_timeout set_timeout.cc)
set_property(TARGET set_timeout PROPERTY CXX_STANDARD 17)
target_compile_features(set_timeout PRIVATE cxx_auto_type)
  • 编译:
1
2
3
# cd to source code directory
cmake .
make

小结:以上代码,可用,但不推荐。首先它是用多线程模拟的定时器,当设置 N 个定时器时,将创建 N 个线程,这不够优雅。其次,当您取消定时器时,会发现它无法立刻取消并退出线程。

Boost Asio 实现

我们知道,Nodejs 内部使用 libuv 作为异步 IO 库,它是 C 实现的,用 C++ 调用 libuv 就显得不那么 C++,所以我们决定用和 libuv 同类且更强大的 Boost Asio 来代替。

  • 安装 boost,目前是 1.72.0 版:
1
brew install boost
  • C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <chrono>
#include <iostream>
#include <vector>

#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>

class Timer {
public:
template <typename T>
void setTimeout(T function, int delay);
void stop();
void wait();

private:
boost::asio::io_context io;
std::vector<boost::asio::steady_timer> timers;
size_t count;
};

template <typename T>
void Timer::setTimeout(T function, int delay) {
auto& timer = timers.emplace_back(boost::asio::steady_timer(io));
++count;
timer.expires_from_now(std::chrono::milliseconds(delay));
timer.async_wait([=](const boost::system::error_code& error) {
// boost::system::error::operation_canceled
// boost::asio::error::operation_aborted
if (!error) {
function();
}
});
}

void Timer::stop() {
for (auto& timer : timers) {
timer.cancel();
}
}

void Timer::wait() {
io.run();
}

int main() {
Timer timer;
//timer.setTimeout([&]() { std::cout << "step1\n"; timer.stop(); }, 1000);
timer.setTimeout([]() { std::cout << "step1\n"; }, 1000);
timer.setTimeout([]() { std::cout << "step2\n"; }, 2000);
timer.setTimeout([]() { std::cout << "step3\n"; }, 3000);
timer.wait();
return 0;
}
  • cmake 脚本,CMakeLists.txt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required (VERSION 3.5)
project (set_timeout)

add_executable(set_timeout set_timeout.cc)
set_property(TARGET set_timeout PROPERTY CXX_STANDARD 17)
target_compile_features(set_timeout PRIVATE cxx_auto_type)

set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_RUNTIME ON)
find_package(Boost 1.71.0)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
target_link_libraries(set_timeout ${Boost_LIBRARIES})
endif()

小结:好很多,但太难了……这真是劝退书!

八哥之神【番外篇8】

听说鲁豫要来采访稣
请戴口罩采访

1. 今年遇到史称的最长春节,稣都是怎么打发时间的?

改剧本。2019 年 8 月时,《八哥之神前传【1】》识界大战时就提到“心灵生化病毒”,后来要解释识界之外的识界是怎么毁灭的,人类为何移居太阴之背时,原剧本也是延续这个套路。但不幸的是作者所处的宇宙遭遇病毒疫情,为了不造成恐慌,急忙连夜做梦改剧本。目前倾向于 11.7 级地震。

这个跳跃有点快,我都跟不上了,这些不是还没写吗?

是还没写,但早已梦见。本剧的真正创作时间是作者大学时期,距今已经 15 年。

太震惊了!难怪剧中不少情节很明显不符合稣现在的年纪!

没错!大学时代就打的草稿,故事来源于自己的梦和听闻周边的故事。原本计划剧名:《一睡千秋》、《一梦千年》或者《魔脑在世三千年》的,但由于当时阅历不够,自觉写不好,所以写个故事发展路线就放弃。2018 年底无意间在老硬盘里翻出来,感慨良多,决定把“概要”升级成“初稿”。

为什么现在的剧名是《八哥之神》,是不是过于抽象?

没错!故意起这样奇葩的名字,主要是因为稣低调。另外,这个剧本里的中心思想太过玄幻,不适合一般人,当然不能起个易懂的名字。

稣在扩展剧本的时候,有什么比较深刻的体会呢?

想太多,写不出来!

“概要”只是一个青春言情剧,后来想写的是一个人的一生……但是一个人不够精彩,干脆把一堆人写成一个“稣”。本质上说“稣”其实是一群人,而不是一个。比如《八哥之神【4】》魔性山军犬养殖训练基地那个场景,其实就暗示男主角是个精分。

我糊涂了,是一个精分,还是一群?

有差别吗?

呃……问点别的!春节期间还有其它印象深刻的事情吗?

有的!一直在充电!

稣真是好学,放假也不忘充电!

哦弄!是给 MBP15 和手机充电!

好吧!真是尴尬。我注意到稣居然留长指甲!似乎不符合直男的人设!

没错!这是为了按公共电梯的按钮。您想,现在都 PHEIC 了,直接用手指接触?那面积好大的,多危险呀!

2. 有读者说《八哥之神前传》写得比《八哥之神》有条理,有逻辑,情节更科幻、更合理。

没错,这是事实。因为《八哥之神》其实是《八哥之神前传》的续集,一般续集都是变差的。稣很机智地采用倒叙,先写比较差的续集,这样别人就不会喷:“续集写烂了!”而是说“前传的角色居然更多,剧情更复杂!”

3. 还会出现哪些角色和剧情呢?能不能简单预告下。

出现的角色:李冰月、李心觎,一对双胞胎姐妹;孟长生,一个不婚主义的男子。

剧情:爷要被捅死了……这应该不算剧透,《八哥之神》已经明确这点,开场时圣小开就是一个 60 多岁的年轻人,和周易类似,但更惨一些,毕竟这是一部“探讨生命悲剧色彩”的言情科幻喜剧。

4. 您刚说过,想写一个人的一生,那么对您来说人生的意义是什么呢?

本剧的人生观:死亡并不长久,轮回再度孤独。请注意这里的“度”是个动词。

别慌!一个人的一生虽然意义不大,但放在无尽时间洪流里就有意义了,您可以以一辈子为单位进行迭代,这辈子尽量做得更好,让下辈子起点高一些。太上大道,我稣慈悲!

5. 太高深了,不是很能理解。能聊一下剧中情节和您现实生活的关系吗?

嗯……这些故事全都有现实原型!就是时间点改改,人名随便起一个,把几个现实故事重组。但是,这些故事没有一个是发生在作者本人身上的,作者只是擅长蹭热点,沾点人间俗气,以对抗挨踢无情。

6. 使您决定写本剧的动力是什么?必然有一个标志性事件吧!

上辈子死前,死神劝稣要放下,稣回答:不,稣要追求永恒!

然而稣还是死了,还是被洗脑洗得一点都不剩。为什么有些感觉转瞬即逝,再也回忆不起来?不行!稣要再体验一次,然后把它写下来。于是稣养成写日记的习惯。

有一天,稣发现如果按照中国人的平均寿命计算,稣已经过掉一半,回忆起自己的前半生,稣发现人生是孤独的最终奥义。每个阶段都会新朋友,也会失去一些旧朋友。就是上一次转变的时机来临前,稣突然领悟到一个真谛,我们无法选择不失去,但可以选择阶段性先失去的是什么!比如哪类朋友应该舍去——那些无法互相理解的。

接着稣又发现,理解不是一件容易的事情,互相理解这个要求有点高。只要能合作就行,应该舍去那些无法合作的。最终稣终于理解人间的一个催化剂:嫉妒。于是稣感觉人间的故事学习得差不多了,可以总结一下,于是开始写剧本。

7. 还会写其它故事吗?

身为天族,不能过度体验和干预人间。写代码比较正经。

8. 最后一个问题:传说稣身家千万?

个币。

什么?怎么骂人呢?

真的。

八哥之神前传【8】

床上

古思:怎么……这么快?爷不是要敲代码么?

圣小开:一日之计在于晨,美好的早晨当然是睡回笼眯最好了!说不定还能再见到狐狸精。

古思:那我先去洗澡!

圣小开:大早上的洗啥澡,又没让你侍寝。

古思:哦,我也换睡衣陪爷躺躺。

圣小开:好多年没和别人睡眯眯了,有点不习惯。

古思:那我们聊天吧!床除了睡觉,另一个大用途就是聊天。

圣小开眼神一撇:你会聊啥?C 还是 C++?

古思:C++!而且我真学过,贾老师特地要求我学的。

圣小开:反应真快。爷还是亲手确认一下……

古思:爷的手好冰呀!

圣小开:嗯,爷还是稣的时候体温就低于常人,当爷后体温就更低了,需要一个暖手宝。

古思:这点我看过的资料并没有提到。

圣小开:贾总是不是和你说了爷很多八卦?

古思:是有一些基本介绍,也没啥特别八卦的!我还得多直接从爷处了解。

圣小开:有啥问题就问吧,如果问得不好爷可能就眯过去了。

古思:爷刚才提到好几次“眯”,为什么睡觉要说成睡眯眯呢?这种骚里嗲气叫法不符合爷的人设!

圣小开:骚里嗲气?眯眯——其实是爷的绯闻女友!

古思:哇哦……原来如此,看来田心挖到第一个八卦咯。

区块链上的故事

9102 年的春天,一个风雨交加的夜晚,稣和王博士聊完一个关于人工智能的天后,精神还有点紧崩,边低头走路边思考科技的变幻和人生的虚幻。

突然偶遇现在被人称为稣的绯闻前女友的月光女神卢眯眯。距离上次见面过去快一年,相逢自是有缘,人生到底是虚幻,还是真实,尽在研究自己和参考他人,何不乘机聊聊人生?然而附近像样的小资消遣场所都已打烊,也没地方坐,她提议去她宿舍。稣心想那是员工宿舍,应该很安全,稣不可能被人暗杀,如果被仙人跳,稣只要大喊一声,这破地方认识稣的少说也有 60 个!于是果断去了。

进屋后温暖许多,在温差作用下,居然一时犯困。她去洗澡的时候,稣不小心就在她床上睡着。

醒来已经快凌晨五点。她就躺在身边。稣想:楼下宿舍门没有门禁卡是出不去的,又不忍心叫醒她,于是一个果断的决策:睡眯眯。

这一觉睡得很好,是稣一生中少数几个质量极佳还能记得的觉之一,于是稣从此改口称睡觉为睡眯眯。

唯一美中不足的是……

床上

古思:吓醒了?

圣小开:不是……稣偷偷摸摸摸了一下,眯眯的咪咪原来只比六舅大一点点!

古思:六舅?

圣小开:嗯,是爷的舅舅,他很六,所以叫他六舅。不说他,总之就是比你小挺多的……

古思:原来爷喜欢这个!田心还可以去加大一些。

圣小开:不用,不用。适可而止!适可而止!爷只是用理性的眼光衡量!

古思:爷到底是正经还是不正经呢?好难分辨!

圣小开:一名男性整形医生整天研究女人的胸,你说他正不正经?

古思:这没有不正经呀!

圣小开:一个男孩整天研究女人的胸,因为他立志成为一名整形医生,你说他正不正经?

古思:好像有不正经的味道。

圣小开:嗯哼!区别在于专不专业!如果一个人业余研究异性心理,多半会被判定为不正经,而一名专业的心理学家,怎么研究异性心理,都是正经的。

古思:爷想说什么?

圣小开:只有假不正经,才能知道谁是假正经!这是一种调试八哥人生的方法。

古思:听起来,其实爷是很正经的人,故意表现得有些不正经?

圣小开:是的,正经得无趣,无趣得没朋友。爷还是要假装有朋友的。

古思:调试八哥人生是什么意思?

圣小开:很多时候,很多人,是不会说实话的,需要一些研究手段。比如陪他们演戏,但又不能表现得不自然,所以爷研究过这本《悲剧演员的自我修养》。

古思:贾老师说爷的好奇心很重,原来如此呐!

圣小开:不!这是大部分人对爷的误解。这个误解好大,大到爷每次解释都会被当成蛇精病。

古思:em?怎么回事?

圣小开:爷是质疑,不是好奇。别人是什么样的,爷都可以接受!爷从这个世界的基础开始质疑,以致于对别人的存在和真伪也质疑。比如,偶尔发现别人好像喜欢自己,就会去研究她是不是真实存在的,是不是有主观意志,是不是真的喜欢,以及为什么她会喜欢爷。反过来,爷喜欢别人也一样各种质疑。

古思:这是不是多疑?不自信?

圣小开:肯定是多疑。爷连自己都质疑,有时甚至怀疑“我思故我在”也有八哥!是在思考,但在哪里思考呢?在自己脑里,还是在远方的某处?不自信有点,但也不他信呀!

古思:田心喜欢爷是基因和洗脑程序决定的,是真实的。

圣小开:哦?你不抗拒一下这种安排吗?

古思:贾老师说,爷要是死了,我会进入重置状态,记忆可能全部丢失,和死掉无异。

圣小开:那爷睡眯眯后,你会怎么样?

古思:没有特别设定。可以一起睡呗。【心想:以前是不敢抗拒,现在看来没必要抗拒。】

圣小开心想:在爷的床上,没人可以比爷晚睡!等你丫睡着后,爷就用各种反侦察仪器检查你。

死神的套路

前情

每年春节期间,稣都要研究玄学,今年岂能例外?

一神论

每个宇宙都有且只有一个神,就是您信的那个。祂有不同的名字,比如信基督,那神的名字是耶和华;道家神叫太上老君。

其实神就那么一个,不同的人给祂取不同的名。也可以是您自己,总之您信就行。

稣信死神。

死亡并非永恒

没有什么状态是永恒的。死亡也不是。宇宙能创造无穷的生命,总有一天可以重现死去的生命。就像您随便写个数字序列,在 pi 里一定能找到无数次。

记忆是暂时的,意识比记忆长久。生死轮回,可没保证在地球轮回。众生平等的理论来源于此,这辈子在地球上当人快活,下辈子可能在火星当虫吃土。

天地不仁以圣人为刍狗

人喜欢强人,便崇拜他们。死神也会喜欢强人,但奴役他们。很多强人都被死神叫去服务祂了——这就是死神的套路。

什么是圣人?圣人一定是强大但又克制自己的强大的,所以他们没有崇拜这种情感,他们只敬畏天地。

您说自己有一技之长,但那是什么造就的?基因、营养、后天练习、时代巧合等,圣人认为这些没啥,不值得欣赏。

死神要人命时,很克制,不轻易收走。——这才是值得欣赏的。

反面,不知克制食欲暴饮暴食、纵欲过度,身体会很快坏掉。

鲁信说过:稣劝您当扫地僧和鸠摩智,而不是乔峰。

死神之下皆为蝼蚁

死神眼里正邪无异,善恶不分。但人们如果自己规定了什么叫善恶,还自以为是善,当他们发现自己原来是恶时,就会遭到自己的诅咒。

影剧里,恶人洗白或善人作恶往往命不久矣。恶人可能想赎罪救人而死,善人则可能是信仰崩塌,生无可恋。总之都是后悔死的。

善良真的是一种有能力才能选择的修炼模式,不是自我认定和按条款划分这么简单。法律应该高于道德,因为很多“道德”条款,根本上是错误的。

PS: 死神根本不关心您善不善良,对凡人来说,量力而行才是科学的。

顺天而行

一个人只要坚持追随天道,时间长了就会很牛逼。天道并非就是美好的,凡人只能追随那些追随天道的圣人,比如老子。

前面已经提到“量力而行”这个词,凡人不可能做到顺天而行,量力而行就好。

新型冠状病毒

态度

战略上藐视敌人,战术上重视敌人。

经验

世界上确实有两种人:年轻人和中老年人。

  • 年轻人对 SARS 可能没什么印象。即使现在被感染新型冠状病毒,靠自身免疫力战胜病毒的概率也比中老年高很多。稣发现他们中一部分人,对疫情并不关心,甚至认为别人讨论疫情是在制造恐慌……稣认为如果发现有人造谣、信谣、传谣,辟谣打脸就行,但干预别人正常讨论疫情的自由就不太好了。毕竟年轻人迟早也要变成中老年人。

  • 中老年人很可能本来就有一些慢性病,要是被感染,死亡率就比较高。

稣属于经历过 SARS 封校的中老年人。当年还被认为是疑似病患,被请去医院体检,还好没事。所以稣是属于相当重视的这类。

贪生怕死

小时候经常会看到一些武侠剧,某个配角嘴里冒出一句:“没想到你居然是贪生怕死之辈!”语气带着不屑和鄙视!但说完,一般他就挂了……每每看到类似情节,稣的内心都是一阵纳闷:“贪生怕死不是正常的吗?这个人为了点莫名其妙的小事就去死,好像才不正常吧?”

有人说这次新型冠状病毒死亡率不高,没啥好操心的。

稣认为死亡率可能真不高,但却是多出来的一种。我们本来也存在被流星砸死的可能,但加上“新型冠状病毒致死”这一种可能性之后,整体死亡率明显是提高了,干嘛冒这个风险?

三思而后行

造谣者一般都是为了利益或者某种心理刺激。作为小老百姓,造谣应该不多,但信谣传谣就多了去。有些人为显示自己信息渠道多而广,就喜欢第一时间转发信息,而不是先鉴别。这种心理很多人都有。有些人不在网络上传谣,但聚会当面传是一样一样的。

也有些人是没鉴别能力,宁可信其有。稣在学习的路上,也没少被谣言短暂攻破过,要三思而后行和及时修正。

对自然心存敬畏

吃野味风险很高,那些爱吃的人估计也有一种攀比、装逼的心理,因为吃起来危险反而去吃。这是病,得电!

RNA 病毒因为是单链不稳定,所以变异能力比较大,很难预测后期是毒性减弱还是增强,以及人群里会不会出现超级传播者。流感病毒也是 RNA,稣基本每一两年都中一次,上一次的抗病毒经验无效,说明中的很可能是新变异品种。

相信科学,但也要明确还有很多病现在的医学治不了。敬畏自然,主动预防被感染!

学习 Rust【2】减少代码嵌套

结论先行:减少代码嵌套就是降低复杂度。

资源管理一向是编程中的重要任务。当一个函数要管理多个资源时,很容易出现代码嵌套层级太深的问题,尤其是调用系统或第三方 API 时。

以 C 语言代码为例,这里简化为两个资源,请您自行脑补多个资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int error_code = 0;
resource1 *p1 = new_resource1();
// UMU: with C++ SHOULD be `p1 != nullptr`
if (p1) {
resource2 *p2 = new_resource2();
if (p2) {
if (!deal_resources(p1, p2)) {
error_code = 3;
}
free_new_resource2(p2);
} else {
error_code = 2;
}
free_new_resource1(p1);
} else {}
error_code = 1;
}

return error_code;

上面代码最深嵌套是三层,为了减少嵌套,可以把代码改为平坦结构,降低到一层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
resource1 *p1 = new_resource1();
if (!p1) {
free_new_resource1(p1);
return 1;
}

resource2 *p2 = new_resource2();
if (!p2) {
free_new_resource1(p1);
free_new_resource2(p2);
return 2;
}

if (!deal_resources(p1, p2)) {
free_new_resource1(p1);
free_new_resource2(p2);
return 3;
}

free_new_resource1(p1);
free_new_resource2(p2);

但这么改在资源释放时,更容易遗漏。也有人为使代码层级平坦化,会使用 goto 到函数末尾统一释放,或者更优雅点的 C++ 方式:用 try...throw...catch...finally 将所有资源包含起来管理。

Node.js 的异步回调函数也存在嵌套层级过深的问题,可以用 Promise 来平坦化,参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
setTimeout(() => {
console.log('step1')
setTimeout(() => {
console.log('step2')
setTimeout(function() {
console.log('step3')
console.log('done!')
}, 1000)
}, 1000)
}, 1000)

// flatten
let timer = (text) => {
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(text)
resolve()
}, 1000)
})

return promise
}

timer("step1")
.then(() => {
return timer("step2")
})
.then(() => {
return timer("step3")
})
.then(() => {
console.log("done!")
})

C++ 建议使用 RAII 思想来管理资源,获得资源后立刻放到管理对象里。如果有些资源使用得不频繁,想偷懒不去封装,则可以使用 scope_exit。go 语言更是用内置关键字 defer 来提供 scope_exit 机制。

Rust 用 scopeguard 提供 scope_exit 机制,defer! 宏和 go 的 defer 功能类似。

另外,Rust 还有 ? 操作符,也有减少嵌套的作用。比如这个任务:打开文件,如果失败就返回错误。go 是这样写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"os"
)

func main() {
file, error := os.Open("file.txt")
if error != nil {
panic(error)
}
defer file.Close()
}

同样功能,Rust 代码少一层:

1
2
3
4
5
6
use std::fs::File;

fn main() -> std::io::Result<()> {
let _f = File::open("file.txt")?;
Ok(())
}

八哥之神前传【7】

本命年,在夏兴电池工厂上班。工厂位于海城夏阳村,一个鸟不拉屎的荒地。宿舍就在工厂旁边,一面是工厂,其余三面都是荒地……生活除了上班,加班就是无聊和同事瞎逼逼。女朋友,那是不可能有的,美女也不可能来这种地方呀!

突然有一天,一个带着光晕的美女出现在稣眼前,十分神奇的是,别人好像都不主动聊关于她的事情,这一点都不像大家平常的风格!丑的狂喷,一般的考虑去追,现在来一个天仙级别的,各个都装蒜,是在独自算计自己的成功率吗?还是大家都瞎了?

经过观察,稣发现她居然就住在正上楼。稣和她用的是同一根水管,这可能让稣多了几分胜算。更巧的是,偶然发现,晚上她喜欢在楼下散步。这可是稣的强项,毕竟四周没有什么地方好玩的,楼下转圈,稣一个晚上可以走 20 圈。于是,转着转着,就认识她了。胡小玉,没错,很像狐狸精的名字……

胡小玉:“你很勇敢嘛。”

稣:“勇敢?为什么这么说?我只是质疑现实,想研究你而已。”

胡小玉:“这里的人好像都不理我,就你敢接近我。”

稣:“哦。这个‘敢’字,说得好像你很可怕似的。”

胡小玉:“我是狐狸精,不可怕吗?”

稣:“嗯?你开玩笑吧!不过你真的很漂亮,感觉很不像真实的人。别的女人脸上一般都有痘印、斑啥的瑕疵,你居然没有,而且声音也很好听。你真是人如其名,一块美玉。”

胡小玉:“你话真多,想睡我吗?”

“什么……”稣一阵尴尬之后,“稣今年 24 周岁,已经成年。”

胡小玉:“那就是想!走吧。”

稣:“这么简单?等下是不是会掉流星把稣砸死?”

胡小玉轻轻一笑:“哈。如果除了睡觉之外,你还干别的,我会咬断你的脖子。”

扑通扑通,到她房间门前,上面居然多了一道符,还加了一把锁,所以我们进不去。

胡小玉:“看来有人发现我是狐狸精了。从你房间爬上去吧!”

稣内心一惊,“难道你不是开玩笑的?”

胡小玉:“笨蛋。那你还跟不跟?”

稣:“会死吗?”

胡小玉:“乱来会死,不乱来就不会。”

稣:“但是稣恐怕爬不上去!11 层爬到 12 层,万一摔下去,必死无疑。”

胡小玉变成一只白色狐狸,顺着管道爬到她房间阳台。稣看得怀疑人生,不行,怎么可能有这么无稽的事情呢?

稣心想赶紧跟上去看看什么妖,虽然费了九牛二虎之力,但居然也跟着爬上她阳台。

但是一进她房间,稣就感觉不对劲,她是狐狸精,会法术的,万一她是附身一个美女,这时候离开肉体,这美女醒来不是要告稣非礼?万一肉体是死的,稣就被当成杀人凶手,太可怕了!

胡小玉见稣迟疑,开口道:“12 年前,你就见过我,不记得咯?”

稣:“不可能……认识你之前,稣就没见过你这么美的。如果有,肯定会记得。”

胡小玉:“我把记忆还给你吧!”说完吹了一口妖气……稣啥都想起来了!上一个本命年,稣去 KFC 上厕所,然后在里面坐了一会儿等同学,看别的顾客在吃,穷稣流下了没钱的口水。后来有个姐姐端着全家桶坐在稣对面,那护体神光照得稣不敢看。她微笑着给稣一个鸡腿,稣才乘机偷看一眼,真是天仙下凡,四周背景突然全白,其它一切仿佛都消失得无影无踪。后来,同学找来,那位姐姐一转眼没注意就消失,竟似人间蒸发!只留稣手里的鸡腿,稣只能含着口水把它吃完。

胡小玉:“想起来了?你欠我一个鸡腿,睡一觉还我。”

稣大惊失色:“为什么要睡觉还呢?”

胡小玉:“我需要阳气。不愿意吗?没要你命就好!”

稣心安理得地睡下了。醒来已经又过了一个本命年。稣走出木屋,身处深山老林,过着田园生活。

时光荏苒,三次见胡小玉,她竟然一点都没变老。稣千真万确地相信,她真的是狐狸精。于是稣先开口问:“这次是要干嘛?”

胡小玉:“我要吃人肉!不然我不久后就会死掉。”

稣:“认识你 24 年,不会还要吃稣吧?吃别人行不行?”

胡小玉:“笨蛋!要吃你,还需要和你说这么多?如果你想每 12 年见一次我,就帮我找!”

稣:“死人行不?这山下去就是哈岱医学院,那里有不少人体标本,虽然泡在福尔马林里,拿出来洗洗应该还好吧!”

胡小玉:“不行,要活人。”

稣:“刚刚安乐死的人可以吗?脑先死,肉体还新鲜……”

胡小玉:“老头不行,生命力不够!有年轻人愿意安乐死吗?”

稣:“你要多重?稣割点给你还不行吗?”

胡小玉:“64g 血肉,肥肉不要。”

稣:“什么……肥肉还不行?稣还想减肥呢!这么多,要死的,要死的!牛肉行不行?”

胡小玉:“不行。我要维持人形,就要吃人肉。”

稣:“真麻烦。看来要去找研究基因工程的孙大师帮忙了,克隆人肉,啧啧,想想就肉麻……”

胡小玉表示感谢,紧紧抱着稣,突然咬稣一口:“其实你是唐僧转世,吸你点血就够了。”

稣:“唐僧!?”

吓醒。原来侧睡压麻右手,被小玉咬的地方还隐隐抽筋……压麻的,压麻的!

布信聊天

贾力劣留言:开哥,送你的美女叫胡思,是女娲造人计划最新产品,属于部分可编程人造人,三年科学教育优等生。知道你不喜欢自由意志,特地选择这位不怎么听话的,希望您会喜欢。

圣小开:贾总,你不是耍我吧!部分可编程?人造人?不是机器人来着?不听话到什么程度呢?不会乘我睡咪咪剪小鸡鸡吧?

贾力劣:不会不会!她是受过高等教育的,懂事。人造人是有生育能力的,只要她愿意,可以给你生猴子!比机器人有意思多了。

圣小开心想:贿赂?卧底?

圣小开家

家联网系统提示有人拜访,还出现广告——神荼系统,保卫家园。自动为您接待来访嘉宾:暂名胡思,女,4 岁,AVILab 女娲生产线第 11 代人造人,由贾力劣总经理指派……

初次见面,有点像刚认识胡小玉那样,即激动又顺利。大概都是编好的程序起作用吧!

胡思:老爷好!我是胡思,小名叫田心,有事请吩咐。

圣小开:不用客套,田心。不要称呼老爷,把老去掉吧,赫赫。大部分家务事,机器人都可以搞定,你就陪老夫聊天和侍寝就行。

胡思:好的,如果对我的初始设定有任何不满,爷都可以修改。

圣小开:先改个名字吧!胡思胡思,容易胡思乱想,把月去掉,叫古思可以吗?

胡思:好的,以后我就叫古思,稍后提交到链上女娲管理系统。

一阵三思而后行的延迟。

圣小开:嗯……改你名字,好像不太好!老夫错了!你不会生气吧!

古思:生气?我生什么气?我哪敢生气?

圣小开:你就是生气了!真小气!

古思:赫赫。我学爷开玩笑的套路呢!没生气,我的名字本来就是别人取的,无所谓啦。贾老师说爷就爱胡思乱想,我被创造的目的就是为了陪伴爷,所以他给我取名为胡思。

圣小开:哦!既然你不介意,那不如叫胡小玉怎么样?

古思:来不及了,信息已上链,只能改一次。

圣小开:哈,开个玩笑,你没有狐狸精那么高冷,反而更像狐狸精。

古思:狐狸精?爷已经开始胡思乱想了么?

圣小开:没有没有,只是早上做的一个梦,还是赶紧吃完饭,敲代码吧。

古思:我可以陪爷结对编程。

圣小开心想:贾总果然是派卧底监视劣者。

八哥之神【番外篇7】

听说鲁豫要来采访稣

1. 稣,好久不见,最近都在忙什么?

项目忙,九九六一段时间,还趁机研究《孙子兵法》和密码学。由于一时忽视各位女朋友,现在她们都另寻新欢了。不过倒也清净,毕竟她们也老了,省得稣裁掉她们,还要 N+1,万一怀上,还得 2N……多烧钱呐!

2. 有读者问,为什么先写《八哥之神》正剧,再写前传?

其实这系列故事是好多年前就想好的,只是一直写代码,没空写故事。再说这个故事其实就是稣的梦,有很大跳跃性,哪有那么容易写好!

  • 你是说,这些故事已经存在很久,最近才写下来?

没错!这些真的都是梦,劣者的创作只是把每个梦连起来,让它们有逻辑而已。一个明显的套路就是剧中有很多吓醒故事,看似无关,其实都是营造气氛,预测故事走向。

3. 为什么写得这么难懂?

戒焦戒虑,勤思好学您就能看懂。主要因为这只是小说的草稿,就没打算让您懂,万一看懂,觉得故事太妙,到处被剽窃怎么办?稣的手稿在 9 年后将会十分值钱。哈。哦也。

4. 还会写后传吗?

那是必须的,等有经费就写!前传、后传,那都是商业套路,咳,现在主要工作还是写代码,多赚点钱。

5. 能简单介绍一下后传的框架吗?

19 年前佛祖转世谈恋爱谈恋爱去了,燃灯佛祖让位孙悟空。5 年前,玉帝也转世谈恋爱谈恋爱去了,太上道祖让位石敢当。9 年后,八哥之神转世谈恋爱谈恋爱谈恋爱去,八哥虫祖让位给稣。

  • 您是说……嗯哼??

没错!稣就是第八个无骨蠕虫。

  • 天呐!太震惊了!这是要改走神话路线??

没有!您不能先入为主呀!正剧已经说过“无神”,只是轮回而已。成神其实就是轮回转世,就是用嘴把人劝死,隐含作者一直推动安乐死合法化的决心。再次强调:没有神,只有神奇!

  • 您一再强调“无神”是怕年轻人迷信吗?

嗯,有这样的担心,另外还怕不明所以的读者对稣进行物理攻击!

  • 怎么会呢?难道您曾经……

举个例子吧!大学时,稣用“宇宙最大”做签名档,结果被很多人攻击,更有黑客跟踪稣多年,就是为了教育稣……其实“宇宙最大”表达的是“宇宙比人类的心大”,这是因为稣反对电视剧里说的“人心最大”,并非说自己是宇宙最大!

  • 哦,稍微有点明白啦!《八哥之神》也会有人理解为您自称是神?

yup!稣从来没说自己是神!只想表达“八哥”太特么神奇了!万一有神论者理解成“自称是神”,对稣痛下杀手,稣不是白死了?吓尿呀!

6. 您能再聊聊其它隐含意义吗?

明线一直都在强调“现实”从来不存在,这一切都是一个叫做“天道”的程序的运行结果。对意识来说,时间并非不可逆。

  • 这些太理论了,您能说说自然界或者社会方面的意义吗?

哦,也是有的!您看剧中大量六七十岁的角色和养老院的场景!

  • 嗯?您是说……

没错!中年危机不仅存在于程序员中!演员也是有的,老头老太能演青春偶像剧吗?

  • 所以,您是写了有很多老年人的剧本来拯救他们?

哦也,稣爱世人,包括老年人的!年轻人需要更多关爱老人,加强养老方面的物质以及精神建设。

7. 没想到稣有这么高尚的情怀!最后还是问点和剧情有关的吧!稣的前妻到底叫什么?

陈因提,外号砂砂。在天道程序里叫陈立姻和陈提姻是不同迭代的名字。

8. 据说《八哥之神前传》将要出现一名机器人美女?

嗯,堪称八哥系列最美最善解人意的。

  • 机器人最善解人意?

当然!人类有情绪,有三千烦恼。大道无情,但能创造有情的人类。有情未必真比无情好。

  • 好严肃呀!开个玩笑呗?

不好意思,笑不出来……写个诗吧!

圣人半个已操群,
耶稣佛祖我都信。
一生大坑避无数,
半脚踏入鬼门关。

  • 为什么半脚踏入鬼门关?

薛定谔的稣一直处于生与死、真与假的边缘。

  • 稣其实是个严肃的人嘛!

当然!如来佛祖、昊天上帝和八哥之神也都很严肃,严肃不妨碍我们谈恋爱。而且谈恋爱这事,还是严肃对待才好!结婚更好慎重!

  • 呵呵……是是是!不好意思,这么严肃的话题,我,我,居然,笑场了。

你开心就好。

ECDSA Node.js

前情

上篇《ECC Node.js》讲解椭圆曲线点的计算。本篇分析椭圆曲线签名算法。

代码

https://github.com/UMU618/secp256k1-tools

范例数据

已知,待签名数据为:

1
2
3
4
5
6
7
const data = Buffer.from(
// chainId
'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906'
// serializedTransaction
+ 'c0fbc75d000000000000000000000000'
// sha256 of serializedContextFreeData
+ '0000000000000000000000000000000000000000000000000000000000000000', 'hex')

运行 node ecc-sign.js,信息摘要为:

1
2
3
4
5
6
[
204, 24, 57, 178, 84, 129, 31, 104,
99, 30, 100, 210, 3, 38, 31, 168,
138, 248, 252, 131, 196, 14, 203, 152,
34, 152, 102, 149, 181, 94, 182, 148
]

签名为:

1
2
3
4
5
6
7
8
Uint8Array [
27, 36, 211, 214, 45, 20, 219, 85, 150, 70, 174,
229, 131, 173, 20, 61, 37, 129, 232, 80, 19, 164,
36, 249, 132, 56, 36, 74, 210, 34, 221, 98, 164,
68, 6, 237, 42, 240, 227, 212, 33, 105, 239, 200,
11, 59, 11, 148, 226, 85, 212, 106, 250, 155, 34,
25, 101, 69, 159, 138, 157, 114, 44, 38, 202
]

签名的字符串形式为:SIG_K1_Gg74ULRryVHxYZvMRLJgTrAZW6PZGC5SYfUiswtMJxBwfTTnGEnTejeWXopL2oSs8EZD7mqAC8mCps6VKq95Bgic9tGNHJ

分析

数值全部使用 16 进制表示。

  1. 范例使用的钥匙对

    • 签名私钥:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3

    • k = d2653ff7cbb2d8ff129ac27ef5781ce68b2558c41a74af1f2ddca635cbeef07d

    • 对应的公钥:EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

    • K = [c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf, eeceff7130fd352c698d2279967e2397f045479940bb4e7fb178fd9212fca8c0]

  2. 信息先用 sha256 算法计算摘要,范例中值为 h = cc1839b254811f68631e64d203261fa88af8fc83c40ecb9822986695b55eb694

  3. 签名数据一共 65 字节,第一个字节 [27] 是 recoveryParam,使用前要先减去 27,它的一个作用是区别 y 坐标值的奇偶性,后面是两个 256bit 数,分别记为 x、s,x 是随机私钥 r 在椭圆曲线上的点 rG 的 x 坐标值,s = (h + k * x) / r。

    • x = 24d3d62d14db559646aee583ad143d2581e85013a424f98438244ad222dd62a4
    • s = 4406ed2af0e3d42169efc80b3b0b94e255d46afa9b221965459f8a9d722c26ca

    • 注意:elliptic 库把本文的 x 记为 r,为了和算法保持一致,UMU 没有采用 elliptic 的标识方式。

  4. 计算 rG = [24d3d62d14db559646aee583ad143d2581e85013a424f98438244ad222dd62a4, bc336258d8f1789ad949773ef4abfe6a6e56c9dd77754e18869c7ab2801a4ae2]

1
2
3
4
5
6
7
8
9
10
11
const BN = require('bn.js')
const elliptic = require('elliptic')

const x = new BN('24d3d62d14db559646aee583ad143d2581e85013a424f98438244ad222dd62a4', 16, 'be')
console.log('x =', x.toString(16))

// (27 - 27) & 1 是偶数,取偶数的 y
const p_even = elliptic.curves.secp256k1.curve.pointFromX(x, false)
console.log('y_even = ', p_even.getY().toString(16))
// const p_odd = elliptic.curves.secp256k1.curve.pointFromX(x, true)
// console.log('y_odd = ', p_odd.getY().toString(16))
  1. 计算 hG/s + x * K/s

    • u1 = h/s = b774bb6040cced0596626026679594b2b5478e6a5a8ba25b3411ed5360ea6bfa

    • u2 = x/s = 5697dfd4caab3caa0ed315a97f99f1ad7bce1ce85e0be32c63847d1dd4be327a

    • result = u1 G + u2 K = [24d3d62d14db559646aee583ad143d2581e85013a424f98438244ad222dd62a4, bc336258d8f1789ad949773ef4abfe6a6e56c9dd77754e18869c7ab2801a4ae2],与 rG 一致,签名验证通过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const BN = require('bn.js')
const elliptic = require('elliptic')

const k1 = elliptic.curves.secp256k1
const h = new BN('cc1839b254811f68631e64d203261fa88af8fc83c40ecb9822986695b55eb694', 16)
const s = new BN('4406ed2af0e3d42169efc80b3b0b94e255d46afa9b221965459f8a9d722c26ca', 16)
// u1 = h/s
const sinv = s.invm(k1.n)
const u1 = h.mul(sinv).umod(k1.n)
u1.toString(16)

const x = new BN('24d3d62d14db559646aee583ad143d2581e85013a424f98438244ad222dd62a4', 16)
// u2 = x/s
const u2 = x.mul(sinv).umod(k1.n)
u2.toString(16)

const k = new BN('d2653ff7cbb2d8ff129ac27ef5781ce68b2558c41a74af1f2ddca635cbeef07d', 16)
const K = k1.g.mul(k)
const result = k1.g.mulAdd(u1, K, u2) // k1.g.mul(u1).add(K.mul(u2))
result.getX().toString(16)
result.getY().toString(16)

数学原理

参考:椭圆曲线加密和签名算法

hG/s + xK/s = hG/s + x(kG)/s = (h + xk)G/s = r(h + xk)G / (h + kx) = rG