0%

最近實在是太頹廢了,強迫自己寫些新東西
蟒蛇有些新玩具,可以加入自己的工具組合之中

PySnooper

Github Project主業
首先先安裝pysnooper這個package

1
$ pip install pysnooper

然後來個最簡單的示範,寫個比官方範例更複雜一點

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@pysnooper.snoop()
def findPrimes(number):
num = 2
primes = []
while num <= number:
div = 2
flag = True
while div * div <= num and flag:
if num % div == 0:
flag = False
div = div + 1
if flag:
primes.insert(len(primes), num)
num = num + 1
return primes
print(findPrimes(10))

然後可以列出所有中間產物的過程

輸出到Log file

可以指定到不同的Log file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pysnooper

@pysnooper.snoop("./debug1.log")
def isPrime(num):
div = 2
while div * div <= num:
if num % div == 0:
return False
div = div + 1
return True

@pysnooper.snoop("./debug.log")
def findPrimes(number):
num = 2
primes = []
while num <= number:
if isPrime(num):
primes.insert(len(primes), num)
num = num + 1
return primes
print(findPrimes(10))

用不同的Prefix區分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pysnooper

@pysnooper.snoop("./debug.log", prefix="--isPrime--")
def isPrime(num):
div = 2
while div * div <= num:
if num % div == 0:
return False
div = div + 1
return True

@pysnooper.snoop("./debug.log", prefix="--findPrimes--")
def findPrimes(number):
num = 2
primes = []
while num <= number:
if isPrime(num):
primes.insert(len(primes), num)
num = num + 1
return primes
print(findPrimes(10))

stackprinter

Github Project主業
要做的還是先裝上去

1
$ pip install stackprinter

接著我們修改上面的範例,讓它Crash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def isPrime(num):
div = 0
while div * div <= num:
if num % div == 0:
return False
div = div + 1
return True

def findPrimes(number):
num = 2
primes = []
while num <= number:
if isPrime(num):
primes.insert(len(primes), num)
num = num + 1
return primes
print(findPrimes(10))

執行之後產生以下的 Crash info

1
2
3
4
5
6
7
8
Traceback (most recent call last):
File "a.py", line 20, in <module>
print(findPrimes(10))
File "a.py", line 16, in findPrimes
if isPrime(num):
File "a.py", line 7, in isPrime
if num % div == 0:
ZeroDivisionError: integer division or modulo by zero

如果我們加上了stachpointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import stackprinter
stackprinter.set_excepthook(style='darkbg2')

def isPrime(num):
div = 0
while div * div <= num:
if num % div == 0:
return False
div = div + 1
return True

def findPrimes(number):
num = 2
primes = []
while num <= number:
if isPrime(num):
primes.insert(len(primes), num)
num = num + 1
return primes
print(findPrimes(10))

會變成這樣子

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
File a.py, line 20, in <module>
16 if isPrime(num):
17 primes.insert(len(primes), num)
18 num = num + 1
19 return primes
--> 20 print(findPrimes(10))

File a.py, line 16, in findPrimes
12 def findPrimes(number):
13 num = 2
14 primes = []
15 while num <= number:
--> 16 if isPrime(num):
17 primes.insert(len(primes), num)
..................................................
number = 10
num = 2
primes = []
..................................................

File a.py, line 7, in isPrime
4 def isPrime(num):
5 div = 0
6 while div * div <= num:
--> 7 if num % div == 0:
8 return False
..................................................
num = 2
div = 0
..................................................

ZeroDivisionError: integer division or modulo by zero

有顏色和箭頭比較容易分析哪裡出錯

既然C++20把Coroutine列為標準配備之後,必須試著了解玩法
從最簡單的範例開始

The simplest coroutine example

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
#include <iostream>
#include <experimental/coroutine>

#define CORETURN_ENABLE

struct generator {
struct promise_type {
int current_value;
auto initial_suspend() { return std::experimental::suspend_always{}; }
auto final_suspend() { return std::experimental::suspend_always{}; }
auto get_return_object() { return generator{handle_type::from_promise(*this)}; }
void unhandled_exception() { std::exit(1); }
auto yield_value(int v) {
current_value = v;
return std::experimental::suspend_always{};
}
#ifdef CORETURN_ENABLE
void return_value(int v) {
current_value = v;
}
#else
void return_void() {}
#endif
};
bool move_next() {
coro.resume();
return !coro.done();
}
int current_value() { return coro.promise().current_value; }
using handle_type = std::experimental::coroutine_handle<promise_type>;
handle_type coro;
};
generator count()
{
co_yield 42;
co_yield 56;
#ifdef CORETURN_ENABLE
co_return 999;
#endif
}

int main() {
auto g = count();
while (g.move_next())
std::cout << g.current_value() << std::endl;
#ifdef CORETURN_ENABLE
std::cout << g.current_value() << std::endl;
#endif
return 0;
}

分析

假設我們有一個Coroutine的程式碼片段,例如

1
2
3
4
5
template <typename TRet, typename … TArgs>
TRet func(TArgs args…)
{
body;
}

邊義氣會幫我們做類似這樣的處理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename TRet, typename ... TArgs>
TRet func(TArgs args...)
{
using promise_t = typename coroutine_traits<TRet, TArgs...>::promise_type;

promise_t promise;
auto __return__ = promise.get_return_object();

co_await promise.initial_suspend();

try
{ // co_return expr; => promise.return_value(expr); goto final_suspend;
body; // co_return; => promise.return_void(); goto final_suspend;
} // co_yield expr; => co_await promise.yield_value(expr);
catch (...)
{
promise.set_exception(std::current_exception());
}

final_suspend:
co_await promise.final_suspend();
}

對照Pseudo code func和上面範例的countˇ以及promise_tgenerator::promise_type明白了一些東西

– 任何包含co_yieldco_return或者co_await的程式碼都是corutine block,必須定義一個structure,而這個structure必須有個promise_type
– promeise_type需要六個函數, return_valuereturn_void不可同時存在
– 中間狀態必須保存在promise_type中
– 當co_yield交出控制權之後,必須使用coro.resume()恢復執行

以下討論另外一個關鍵字 co_await

co_await

先從古早時期的Callback說起,給出一個範例

From a callback start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <thread>
#include <iostream>
using call_back = std::function<void(int)>;
void Add100ByCallback(int init, call_back f)
{
std::thread t([init, f]() {
std::this_thread::sleep_for(std::chrono::seconds(1));
f(init + 100);
});
t.detach();
}
int main()
{
Add100ByCallback(10, [](int v) {
std::cout << v << "\n";
});
std::this_thread::sleep_for(std::chrono::seconds(5));
}

程式本身沒有什麼意義,只是模擬長時間處理的情形

Awaitable object

Coroutine版根據上面的版本修改,新增了一些東西

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
struct Task
{
struct promise_type {
auto get_return_object() { return Task{}; }
auto initial_suspend() { return std::experimental::suspend_never{}; }
auto final_suspend() { return std::experimental::suspend_never{}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
};

struct Add100AWaitable
{
Add100AWaitable(int init) :init_(init) {}
bool await_ready() const { return false; }
int await_resume() { return result_; }
void await_suspend(std::experimental::coroutine_handle<> handle)
{
auto f = [handle, this](int value) mutable {
result_ = value;
handle.resume();
};
Add100ByCallback(init_, f);
}
int init_;
int result_;
};

Task Add100ByCoroutine(int init, call_back f)
{
std::cout << "Before co_await: " <<std::this_thread::get_id() << "\n";
int ret = co_await Add100AWaitable(init);
std::cout << "After co_await: " << std::this_thread::get_id() << "\n";
f(ret);
}

上面的Task不需要多做介紹,Add100ByCoroutine也不需要多說些什麼
直接看Add100AWaitable的部分
同樣的Add100AWaitable也有必須要注意的點
– 中間值一樣存在 Awaitable object
– 必須有await_readyawait_resumeawait_suspend的存在
– 以上面的例子來看,當Callback完成之後,我們手動讓Coroutine繼續執行上去
– 呼叫Add100ByCoroutine和corutime resume的Thread不一定是同一個,需要把狀態保留到awaitable object中

Reference

How C++ coroutines work
Coroutine Theory
From Algorithms to Coroutines in C++
Coroutines and Reference Parameters

Range-V3要進C++20 Standard,看了一下他給的範例

Pythagorean triple如何印出前十組Pythagorean triple

從最簡單的方案開始看

Direct Method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

int main() {
int i = 0;
for (int z = 1; true; ++z) {
for (int x = 1; x < z; ++x) {
for (int y = x; y < z; ++y) {
if (x * x + y * y == z * z) {
printf("(%d,%d,%d)\n", x, y, z);
if (++i == 10) goto done;
}
}
}
}
done:;
}

不會很難理解,必須迴圈外部維護一個狀態,當狀態滿足之後強行離開迴圈

Callback Solution

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
#include <iostream>
#include <tuple>
#include <type_traits>

template<class F>
auto boolify(F& f) {
return [&](auto&&... args) {
using R = decltype(f(std::forward<decltype(args)>(args)...));
if constexpr (std::is_void_v<R>) {
f(std::forward<decltype(args)>(args)...);
return false;
}
else {
return f(std::forward<decltype(args)>(args)...);
}
};
}

template<class F>
void generate_triples(F f) {
for (int z = 1; true; ++z) {
for (int x = 1; x < z; ++x) {
for (int y = x; y < z; ++y) {
if (x*x + y * y == z * z) {
bool stop = boolify(f)(std::make_tuple(x, y, z));
if (stop) return;
}
}
}
}
}

template<class F>
auto take(int k, F f) {
return [f, i = k](auto x) mutable -> bool {
return (i-- == 0) || boolify(f)(x);
};
}

int main() {
generate_triples(
take(10,
[&](auto triple) {
std::cout << '('
<< std::get<0>(triple) << ','
<< std::get<1>(triple) << ','
<< std::get<2>(triple) << ')' << '\n';
}
)
);
}

這個難看懂很多
首先程式主體

1
2
3
4
5
6
7
8
9
10
11
12
13
template<class F>
void generate_triples(F f) {
for (int z = 1; true; ++z) {
for (int x = 1; x < z; ++x) {
for (int y = x; y < z; ++y) {
if (x*x + y * y == z * z) {
bool stop = boolify(f)(std::make_tuple(x, y, z));
if (stop) return;
}
}
}
}
}

是沒壁的,我們的中止條件就在 take(10, ...)這個函數之間
一旦滿足條件,整個genertat4e_tyiples就結束了
我們看看 take的實作

1
2
3
4
5
6
template<class F>
auto take(int k, F f) {
return [f, i = k](auto x) mutable -> bool {
return (i-- == 0) || boolify(f)(x);
};
}

我們把state包含在lambda之中,避免被外界汙染
至於boolify這個函數不重要,不影響分析

這個版本比起上面那個,邏輯切個更清晰
不過複雜度也跟著增加了

Coroutine Solution

隨著C++20加入stackless coroutine,這個問題也可以用coroutine來解

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 <iostream>
#include <tuple>
#include <experimental/coroutine>

template<class T>
struct generator {
struct promise_type;
using handle = std::experimental::coroutine_handle<promise_type>;
struct promise_type {
T current_value;
auto get_return_object() { return generator{ handle::from_promise(*this) }; }
auto initial_suspend() { return std::experimental::suspend_always{}; }
auto final_suspend() { return std::experimental::suspend_always{}; }
void unhandled_exception() { std::terminate(); }
void return_void() {}
auto yield_value(T value) {
current_value = value;
return std::experimental::suspend_always{};
}
};
bool move_next() { return coro ? (coro.resume(), !coro.done()) : false; }
T current_value() { return coro.promise().current_value; }
generator(generator const&) = delete;
generator(generator && rhs) : coro(rhs.coro) { rhs.coro = nullptr; }
~generator() { if (coro) coro.destroy(); }
private:
generator(handle h) : coro(h) {}
handle coro;
};

auto triples() -> generator<std::tuple<int, int, int>> {
for (int z = 1; true; ++z) {
for (int x = 1; x < z; ++x) {
for (int y = x; y < z; ++y) {
if (x*x + y * y == z * z) {
co_yield std::make_tuple(x, y, z);
}
}
}
}
}

int main() {
auto g = triples();
for (int i = 0; i < 10; ++i) {
g.move_next();
auto triple = g.current_value();
std::cout << '('
<< std::get<0>(triple) << ','
<< std::get<1>(triple) << ','
<< std::get<2>(triple) << ')' << '\n';
}
}

由於coroutine還是新玩意,目前對他的掌握度不適很高,這個方案僅供參考

Range-V3 Solution

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// A sample standard C++20 program that prints
// the first N Pythagorean triples.
#include <iostream>
#include <optional>
#include <range/v3/all.hpp>

using std::get;
using std::optional;
using std::make_tuple;
using std::cout;
using ranges::view_interface;
using ranges::iterator_t;
namespace view = ranges::v3::view;

// maybe_view defines a view over zero or one
// objects.
template<class T>
struct maybe_view : view_interface<maybe_view<T>> {
maybe_view() = default;
maybe_view(T t) : data_(std::move(t)) {
}
T const *begin() const noexcept {
return data_ ? &*data_ : nullptr;
}
T const *end() const noexcept {
return data_ ? &*data_ + 1 : nullptr;
}
private:
optional<T> data_{};
};

// "for_each" creates a new view by applying a
// transformation to each element in an input
// range, and flattening the resulting range of
// ranges.
// (This uses one syntax for constrained lambdas
// in C++20.)
inline constexpr auto for_each =
[](auto&& r, auto fun) {
return decltype(r)(r)
| view::transform(std::move(fun))
| view::join;
};

// "yield_if" takes a bool and a value and
// returns a view of zero or one elements.
inline constexpr auto yield_if =
[](bool b, auto x) {
return b ? maybe_view{std::move(x)}
: maybe_view<decltype(x)>{};
};

int main() {
// Define an infinite range of all the
// Pythagorean triples:
using view::iota;
auto triples =
for_each(iota(1), [](int z) {
return for_each(iota(1, z+1), [=](int x) {
return for_each(iota(x, z+1), [=](int y) {
return yield_if(x*x + y*y == z*z,
make_tuple(x, y, z));
});
});
});

// Display the first 10 triples
for(auto triple : triples | view::take(10)) {
cout << '('
<< get<0>(triple) << ','
<< get<1>(triple) << ','
<< get<2>(triple) << ')' << '\n';
}
}

看起來還不錯,不過編譯時間嚇死人…
目前還是先考慮方案一或二吧,三和四等掌握度高一點再說

這需求有點奇怪,不過這倒是我想要的編譯流程
先從Golang來說吧,1.11前的版本只會把dependency module都裝入GOPATH中,就算有godep之類的東西也覺得不好用

由於自己是Developer,需要分析Module間的互動,甚至改改程式碼,分析一下,放在GOPATH不是不好,只是我想要以下的需求

– 修改Dependcny Module的程式碼,由於牽一髮動全身,改動之後可能造成其他相依於此Module的全部掛點
– 希望放在Repo附近,而不是在GOPATH底下尋找

Cargo跟GoLang的行為模式差不多,Module放在.cargo底下
不利於開發研究

於是找出一套屬於自己的方式了

Golang

首先,先安裝到Golang 1.11以上
go-fastdfs舉例,他缺少go.mod,於是我們自行補上

1
2
3
4
5
6
7
8
9
10
11
$ git clone https://github.com/sjqzhang/go-fastdfs
$ cd go-fastdfs
$ rm vendor -r # 刪除掉原先的vendor目錄
$ go mod init
go: creating new go.mod: module github.com/sjqzhang/go-fastdfs
$ go mod vendor # 這下Dependcy Module都在vendor底下了
$ go build -mod=vendor # 產生go-fastdfs了
# 接著在修改vendor/github.com/astaxie/beego/httplib/httplib.go
# 使其編譯不過
$ go build -mod=vendor
./fileserver.go:264:13: undefined: httplib.BeegoHTTPSettings

看來真的是用vendor目錄下的Module下去編譯

Rust

Cargo的部分麻煩一點
首先先安裝cargo-vendor

1
$ cargo install cargo-vendor

yamux來舉例

1
2
3
4
$ git clone https://github.com/paritytech/yamux
$ cd yamux
$ cargo vendor > ~/.cargo/config
$ cargo build

如果要修改vendor裡面的東西比較麻煩
假設我們修改vendor/tokio-io/src/framed_read.rs
下build之後可能出現

1
2
3
4
5
6
$ cargo build
error: the listed checksum of `/yamux/vendor/tokio-io/src/framed_read.rs` has changed:
expected: 07e36ff58fe30bf7b0aa28a998b0bc4e2f78acd04cc1570a877b4eeac1cadcf7
actual: d10d30e1bc1f1d1abc7c28db97fd37ee374d029329aaa78df328bb076163363d

directory sources are not intended to be edited, if modifications are required then it is recommended that [replace] is used with a forked copy of the source

由於我們改了檔案,checksum算出來就不同了
簡易的方案就是在root dir的Cargo.tmol上加上

1
2
[replace]
"tokio-io:0.1.11" = { path = './vendor/tokio-io' }

注意版本號要跟vendor的目錄相同,不然無法編譯

MP4 Muxer的作法,網路上可以找到上百篇,不過On the fly的不多,只好自己動手做了
在這邊走了不少冤枉路,寫起來免得忘記
目標寫一個Library,從Raw H264 Stream和Raw G711 Stream中,打包變成MP4的故事

Libavformat / Libavcodec

大部分多媒體的問題,絕對少不了跟ffmpeg打交道的經驗,所以也在社面花了一點時間

Raw H264 to MP4

由於我們不是走正規路線使用,Google了一下找到Stackoverflow的這篇,沒有什麼問題,不過AV Sync又是另外一個問題,之後再談

G711 to AAC

這點實在搞得我很頭大啊
G711 to PCM這段其實還好,相關的程式碼 這裡
問題在於PCM轉AAC,其中有兩個困難處

– G711的Sample Format是AV_SAMPLE_FMT_S16,不考慮外接AAC Enocder的話,FFmpeg AAC Encoder的Sample Format是AV_SAMPLE_FMT_FLTP,所以需要透過Resampling轉換

– 轉換過的Sample樹不一定跟原先取樣數相同,所以還需要放到Audio Fifo Buffer,等到數目夠的才處理

大致流程可以參考FFMmpeg的範例,一整個麻煩

AV Sync

好不容易通過前兩關,卻死在第三關,也是照FFMpeg的範例來處理PTS的問題,結果怎麼調都失敗,果斷放棄這絲路了,另謀其他做法

libmp4v2 / EasyAACEncoder

不得不大力稱讚EasyAACEncoder這個Project,把G711 to AAC這段難點彌平了不少,不過還是採到坑了
libmp4v2網路上教學不少,就不特別獎了,來說一下遇到那些問題

MP4裡面要放Raw Stream

所以在 PcmToAac.cpp裡面要做如下修改

1
2
/*0 - raw; 1 - ADTS*/
pConfiguration->outputFormat = 0;

看起來都沒問題的部分最後變成夢靨啊..

AV Sync

在上面遇到的問題在這邊也遇到,不過這次找到可行解了,可喜可賀
解法在此

Quicktime issue

路出來的檔案在大部分撥放軟體都沒問題,結果在Quicktime和iOS出問題,Quicktime波一波聲音就消失了,也沒有Open Source可以參考,於是就進入鬼打牆,瞎子摸象的階段
經過多方查證之下,MP4 Info沒什麼問題,不是libmp4v2的問題
試著用ffmpeg轉檔,啜了以下實驗

– 用FFmpeg轉檔,Video / Audio 照舊,QuickTime還是不能播

– 用FFmpeg轉檔,Video照舊,Auido 用FFmpeg AAC Encoder,結果他能撥了

不過我不想走回第一條的路,那太可怕了
不過在ffmpeg轉檔時看到一行

1
Multiple frames in a packet.

這行一直被我忽略掉,因為ffplay沒抱怨,不過走投無路了只好猜是AAC的問題

Revisist EasyAACEncoder

既然懷疑一個Packet裡面有多個Fframe
那可能是

1
Easy_AACEncoder_Encode(handle, pbG726Buffer, gBytesRead, pbAACBuffer, &out_len)

有多個frame吐出來,不過沒有ADTS Header我們無法分辨出FFrame Boundary,於是我們又把ADTS家回去了

1
2
/*0 - raw; 1 - ADTS*/
pConfiguration->outputFormat = 1;

之在外面切割Frame,在一個一個填入MP4,這下Quicktime和iOS就正常了

Conclusion

走了這麼多冤枉路,終於完成一個能動的方案
用FFMpeg方案沒有不好,不過我方面的經驗不夠,之前也沒做太多涉獵
用第二個方案也是遇到了困難,有碰到問題才會知道痛

大家都知道Webpack可以打包許多的Javascript files成一個,方便取用
如果我們的Javascript使用到Webassembly的話可否比照辦理,答案是肯定的
But,我不會用Webpack打包,我也不是專門前端的人,不想花太多時間研究
於是我就改用腦殘版Parcel
零設定就產生了,有興趣的話可以參考demo
接下來的重點是如何放在如何生成Webassembly

AssemblyScript

AssemblyScript
Typescript的subset,會寫前端的話這套適用,不過我是System Language的愛好者

Rust

Rust對Webassembly的支援還真是高啊..只要Cargo.toml中定義成Shared Library
然後下

1
$ cargo build --target wasm32-unknown-unknown  --release

就好了,不過百廢待舉啊

C/C++

基本上有兩個方案
Emscripten 走原先ASM.JS的老路,不過無法直接將生成的wasm放到我的demo上跑
Minimal C/C++ language toolset for building wasm files
有潛力,不過我沒一次成功過,也不能搭配CMake一起使用,還是個半殘品

結論

在觀望吧,現在還不是成熟的時刻

C++ 的Serialization / Deserialization已經很成熟了
有了Boost 和 Cereal可以選,不過它們需要ifstreamofstream做參數傳入
如果我們需要用於網路傳輸跟Database該怎麼辦
Boost ASIO幫我們解決了這個問題

Output Wrapper

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
template <typename Container, typename SinkType, typename CharType>
class container_sink
{
public:
typedef CharType char_type;
typedef boost::iostreams::sink_tag category;

container_sink(Container& container)
: container_(container)
{
static_assert(sizeof(SinkType) == sizeof(CharType), "invalid size");
}

std::streamsize write(const char_type* buffer, std::streamsize size)
{
const auto safe_sink = reinterpret_cast<const SinkType*>(buffer);
container_.insert(container_.end(), safe_sink, safe_sink + size);
return size;
}

private:
Container& container_;
};

template <typename Container>
using byte_sink = container_sink<Container, uint8_t, char>;
using data_sink = boost::iostreams::stream<byte_sink<data_chunk>>;

如何使用

1
2
3
4
5
6
7
data_chunk data;
data_sink ostream(data);
boost::archive::binary_oarchive oa(ostream);
const gps_position g(35, 59, 24.567f);
oa << g;
ostream.flush();
return ostream;

Input Wrapper

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
template <typename Container, typename SourceType, typename CharType>
class container_source
{
public:
typedef CharType char_type;
typedef boost::iostreams::source_tag category;

container_source(const Container& container)
: container_(container), position_(0)
{
static_assert(sizeof(SourceType) == sizeof(CharType), "invalid size");
}

std::streamsize read(char_type* buffer, std::streamsize size)
{
const auto amount = container_.size() - position_;
const auto result = std::min(size,
static_cast<std::streamsize>(amount));

// TODO: use ios eof symbol (template-based).
if (result <= 0)
return -1;

const auto value = static_cast<typename Container::size_type>(result);
const auto limit = position_ + value;
const auto start = container_.begin() + position_;

const auto end = container_.begin() + limit;
std::copy(start, end, buffer);
position_ = limit;
return result;
}

private:
const Container& container_;
typename Container::size_type position_;
};

template <typename Container>
using byte_source = container_source<Container, uint8_t, char>;
using data_source = boost::iostreams::stream<byte_source<data_chunk>>;

如何使用

1
2
3
4
data_chunk data;
data_source istream(data);
boost::archive::binary_iarchive ia(istream);
istream >> g;

程式語言的Package management要做好很難
於是就有讓人吐槽的地方,這次我們的對象就是golang
談談我不喜歡的兩個點吧

Package path

當要下載相依Package的時候,預先放在${GOPATH}/src
而不適放在Repository個某的目錄底下,這種方式我不喜歡
不喜歡的點在於
– 萬一修改了相依package的程式碼,而沒在Repository沒看到任何修改,忘記這件事的機率還蠻大的
– 如果修改了相依Package的程式碼,想要Send Request給Pacagage的Maintainer,但是對方不接受,於是要自己Fork出一份,這就遇到產生另外一個問題

import url issue

在golang的世界隨處可見

1
import "github.com/xxx/yyy"

這樣的程式碼,當你決定要Fork出自己的Pacakge之後
可能就變成

1
import "github.com/zzz/yyy"

所有上面的程式碼都要跟著改掉,非常冗餘的資訊
一次commit就修改所有一模一樣的import path
Relative imports
這提案還在討論,不過我想通過的機會也不大

先決條件

首先我們先安裝必要的Python3和Nodejs

1
2
$ apt install -y python3 nodejs npm
$ npm install -g ethereumjs-testrpc

接著安裝開發版的Ethereum,由於她不識真正的Ethereum,僅限開發使用

1
$ npm install -g ethereumjs-testrpc

接著執行

1
$ testrpc

就看到開發版的Ethereum跑起來了,建立了十個帳戶
接著開另外一個Terminal來操作,首先我們安裝Truffle

1
$ npm install -g truffle

建立一個新項目

1
2
3
$ mkdir demo 
$ cd demo
$ truffle init

這會生成一個command line的應用程式,如果需要一個GUI版的可以將江最後一行改成,不過操作還是以CLI版為主

1
$ truffle unbox webpack

接著開始建立合約

第一個合約

我們首先建立第一筆合約

1
$  truffle create contract Test

會產生ontracts/Test.sol這個檔案,不過打開來什麼都沒有
因此我們手動修改合約內容

1
2
3
4
5
6
7
pragma solidity ^0.4.4;

contract Test {
function multiply(uint a) public pure returns(uint d) {
return a * 7;
}
}

接著新建migrations/2_deploy_contracts.js

1
2
3
4
var Test = artifacts.require("./Test.sol");
module.exports = function(deployer) {
deployer.deploy(Test);
};

接著編譯合約

1
$ truffle compile

產生的合約在./build/contracts/Test.json,有興趣可以打開來看
接著部屬合約,在部屬合約之前要先修改truffle.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module.exports = {
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*"
},
live: {
host: "178.25.19.88", // Random IP for example purposes (do not use)
port: 80,
network_id: 1, // Ethereum public network
}
},
};

先不用館live那組`,保留給真正的Ethereum使用,目前關心的只有development那組,接著我們把合約部屬到development network,也就是Testrpc上

1
$ truffle migrate --net development

切回Testrpc的Terminal,可以看到我們的合約被放上去了

驗證合約

1
2
3
4
$ truffle console
truffle(development)> Test.deployed().then(function(instance){contract = instance;});
truffle(development)> ontract.multiply(10)
igNumber { s: 1, e: 1, c: [ 70 ] }

另外一個方法是跑Unit Test
我們新增test/TestTest.sol,然後寫入以下資料

1
2
3
4
5
6
7
8
9
10
11
12
13
pragma solidity ^0.4.16;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Test.sol";

contract TestTest {
Test test = Test(DeployedAddresses.Test());
function testUserCanAdoptPet() public {
uint returnedId = test.multiply(10);
uint expected = 70;
Assert.equal(returnedId, expected, "10 * 7 = 70");
}
}

接著跑

1
$ truffle test

開發一個DApp

這就是前面幾個東西組合加上適合的前端
Javascript用web3.js
Python用Web3.py
Java和Android用web3j
這邊有範例
Ethereum Pet Shop
fileHash
詳細就部戲說了

Final

這邊只有說要怎麼寫Smart Contract,怎麼Deply Contract`,以及怎麼開發Dapp
至於Ethereum 的架設一個字都沒提
並且每個Blockchain的Dapp開發情形不同,只是給一個大智的輪廓

最近這段時間對區塊鏈做了一點功課,無關發幣灑幣之類的東西,純粹技術上的研究
一開始看了A blockchain in 200 lines of code
看了之後還真的以為大概就這樣,看了現實的專案卻看不懂,真是好傻好天真
遇上幾個困難的點,首先是交易這件事
上面的教學完全沒提到交易,不過Bitcoin或是Ethereum都把交易看作核心的一部分
基於Account的交易反倒容易理解,看到Bitcoin的UTXO Model真是一頭霧水
對於不懂的人可以參考
比特幣UTXO模型介紹-如何解讀比特幣交易
UTXO 与账户余额模型
除此之外,還有Persistence這點要考慮,資料要怎麼寫入硬碟保存,怎麼讀取,Serialization / /Deserialization 都是學問
不過還有一點頭大的,既然是分散式結構,就會有Role的不同
有些是Client,有些是Node,有些是Miner
Node之間如何達成Consensus,Pow?Pos?Dpos?Others?
P2P表示的是既是Server也是Client,同時需要處理兩方面的情況
這邊有個很好的範例
Building Blockchain in Go
除了Network那邊稍弱之外,對有心開發自己Blockchain的人來說,算是一個非常好的範例教學

Final

雖然技術上有不少東西可以學,不過我對Blockchain的未來不看好
倒不是技術有問題,而是我認為有價值的東西通常都需要政府來做
發幣賣錢這件事我不喜歡,不過也不是我的看法一定正確,畢竟我也不是個經濟學專家
只是覺得除了中心化解法之外,多了一種選擇