0%

最近幫朋友幫忙寫一個可以Demo的Protottype,有三個角色
Server,Client,Observer
許多Client會送資料給Server,Server處理這些資料之後匯集給Observer
說來不難的東西,因為用了我不熟的技術,golang + javascript,浪費太多時間
果然隔行如隔山啊,寫寫在路上遇到的問題

SSL / TLS Transport

由於想要練習,所以直接用SSL來當作傳輸協定,跟TCP大同小異,不過困難的是Certificate的產生
Server端

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
cert, err := tls.LoadX509KeyPair("certs/server.pem", "certs/server.key")
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{Certificates: []tls.Certificate{cert}}
config.Rand = rand.Reader
service := "0.0.0.0:8000"
listener, err := tls.Listen("tcp", service, &config)
if err != nil {
log.Fatalf("server: listen: %s", err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("server: accept: %s", err)
break
}
defer conn.Close()
log.Printf("server: accepted from %s", conn.RemoteAddr())
tlscon, ok := conn.(*tls.Conn)
if ok {
log.Print("ok=true")
state := tlscon.ConnectionState()
for _, v := range state.PeerCertificates {
log.Print(x509.MarshalPKIXPublicKey(v.PublicKey))
}
go handleClient(conn)
} else {
log.Print("reject connection")
}
}

Client端

1
2
3
4
5
6
7
8
9
10
cert, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
if err != nil {
log.Fatalf("server: loadkeys: %s", err)
}
config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
conn, err := tls.Dial("tcp", "127.0.0.1:8000", &config)
if err != nil {
log.Fatalf("client: dial: %s", err)
}
clientFunc(conn)

至於Certificate是用Openssl產生的,於是產生第二個問題

Websocket

這個坑實在死的不明不白
Server端原先這樣寫

1
2
3
4
5
6
7
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
log.Print("New websocket arrive")
serveWs(hub, w, r)
})
if err := http.ListenAndServeTLS("localhost:8888", "certs/server.pem", "certs/server.key", nil); err != nil {
log.Fatal("ListenAndServe:", err)
}

而Observer端則是

1
ws = new WebSocket("wss://127.0.0.1:8888/ws")

看起來沒什麼問題,不過不然,目前的websocket不能接受 Self hosted certificate,然後就怎麼連都連不上了
為了省事,直接用wshttp傳輸了

Aggregate Json

我想要做的功能是假設
Client A傳送

1
[{Name: 'aaa', Age: 18}, {Name: 'bbb', Age: 81}]

Client B傳送`

1
[{Name: 'ccc', Age: 28}, {Name: 'bbb', Age: 82}]

Oberver會看到

1
[{Name: 'aaa', Age: 18}, {Name: 'bbb', Age: 81}, {Name: 'ccc', Age: 28}, {Name: 'bbb', Age: 82}]

我不太熟悉golang對json的操作,於是寫了醜陋無比的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
type keyvalue map[string]string
var keyvalueslice []keyvalue
for _, v := range Map {
var m1 []keyvalue
if err := json.Unmarshal([]byte(v), &m1); err != nil {
log.Print(err)
}
for _, obj := range m1 {
keyvalueslice = append(keyvalueslice, obj)
}
}
b, _ := json.Marshal(keyvalueslice)

實在感到羞恥啊…

React / Webpack

雖然知道React跟Webpack,不過實際拿來用是另外一回事
React還好,因為我沒用到太深的東西,不過webpack.config.js很複雜
要找Websocket Bug的時候,SourceMap還生不出來,只好土法煉鋼

Conclusion

隔行如隔山,不跳脫舒適圈去搞些平時不會搞的東西,什麼時候死都不知道
不過這次經驗真是讓我充滿挫折 Orz

原先的NodeJS要跑Addon,通常都是寫Native C++ Code
不過現在Rust的影響力日漸增加,路也不只一條
在我的Github分別提供了兩種做法

C++

搭配node-gyp使用
程式碼就不列了,寫出其他細節

binding.gyp的寫法如下

1
2
3
4
5
6
7
8
{
"targets": [
{
"target_name": "fib",
"sources": [ "fib.cc" ]
}
]
}

編譯產生出fib.node,它的本質就是shared object

1
$ node-gyp configure build

至於Nodejs的使用方式

1
2
const Fib = require('fib');
console.log(Fib.fib(40));

Rust

Rust的產生端比較簡單,重點在於Cargo.toml
注意crate-type那欄指定dylib,也就是shared object

1
2
3
4
5
6
7
8
9
10

[package]
name = "fib_rs"
version = "0.1.0"
authors = ["hm"]

[lib]
name = "fib"
path = "src/fib.rs"
crate-type = ["dylib"]

編譯產生libfib.so

1
$ cargo build --release

Nodejs的使用方式就比C++版複雜一點,需要node-ffi

1
2
3
4
5
const ffi = require('ffi');
const rust = ffi.Library('libfib', {
fib: ['int', ['int']]
});
console.log(rust.fib(40));

隨著LLVM 3.3版本出來,記錄一下如河編譯,編譯環境是Mint 15。
使用CMake來幫助編譯。

CMake是用來產生個平台(Windows/MacOSX/Linux/FreeBSD等)建置專案的解決方案,在Visual Studio產生Solutions,在Linux產生正統的Makefile。 更多的CMake用法可以在網路找到。

前期作業

先把所有Code下載下來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
$ cd llvm/tools
$ svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
$ svn co https://llvm.org/svn/llvm-project/lld/trunk/ lld
$ svn co https://llvm.org/svn/llvm-project/lldb/trunk/ lldb
$ cd clang/tools
$ svn co http://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra
$ cd ../../../projects/
$ svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
$ svn co https://llvm.org/svn/llvm-project/openmp/trunk/ openmp
$ svn co https://llvm.org/svn/llvm-project/libcxx/trunk/ libcxx
$ svn co https://llvm.org/svn/llvm-project/libcxxabi/trunk/ libcxxabi
$ svn co https://llvm.org/svn/llvm-project/libunwind/trunk/ libunwind
$ cd ..

Build Swig

編譯

建立一個build目錄放置CMake的設定黨,由於CMake支援Out-of-source Build技術,原先的目錄可以不遭受污染,就算失敗就只要砍掉build的目錄即可。我們使用Out-of-source Build的方式來建置Makefile。

1
2
3
4
$ mkdir build && cd build
$ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr/local ..
$ make -j 4
$ make install

這裡的cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr/local 等價於傳統的 ./configure --prefix=/usr/local/方式,預設的LLVM跟Clang是使用Debug Mode,這裡使用Release Mode。

看了知乎這篇
之後才知道inline博大精深

先看程式碼

1
2
3
4
5
inline void f(void) { }
int main()
{
f();
}

用g++編譯這段程式,不管下-O2或是-O0都編譯的過
而如果用gcc編譯的話,-O0會出現unrefernce error,但如果下-std=gnu89又沒問題

根據知乎上面寫的,正確的修復方式應該為

1
extern void f(void) {}

C++跟C99之後,語意分歧點越來越多啊

Rollback changelist是很常做的事情,不過在git這種分散式控制系統就有點麻煩了
假設我們現在有這樣的Commit history

1
2
3
4
5
$ git log --oneline
e922d2b (HEAD -> master) ver 4
1b01602 ver 3
10dd293 ver 2
72d57f9 ver 1

而我們現在要退回ver 2該怎麼做

危險的作法

直接開大絕

1
2
$ git reset 10dd293 --hard
$ git push origin master

然而自己一個人玩玩還行,團體行動絕對沒有人建議這麼做

安全的作法

1
2
3
4
5
6
7
8
$ git checkout 10dd293 -b v2 # 切換新分支
$ git merge -s ours master
$ git log --oneline
ce6c1ea (HEAD -> v2) Merge branch 'master' into v2
e922d2b (master) ver 4
1b01602 ver 3
10dd293 ver 2
72d57f9 ver 1

可以看見HEAD指向v2的new commit了,比較一下

1
$ git diff HEAD..10dd293

然後可以把v2推向remote了

1
2
3
$ git push origin master
$ git push origin v2:master
$ git push origin HEAD:master

三者等價

Reference

安全地回滚远程分支

再不寫點東西,這邊就長草了
這幾天在看go-redis專案,顧名思義就是在golang當中對redis操作的程式庫

其中有一段程式碼是這樣

1
2
3
4
5
6
func ExampleClient() {
err := client.Set("key", "value", 0).Err()
if err != nil {
panic(err)
}
}

結果去redis.go裡面查看,找不到Set這個函數的實作
只好用grep去找哪邊可能實作這個函數
最後讓我在command.go找到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (c *cmdable) Set(key string, value interface{}, expiration time.Duration) *StatusCmd {
args := make([]interface{}, 3, 4)
args[0] = "set"
args[1] = key
args[2] = value
if expiration > 0 {
if usePrecise(expiration) {
args = append(args, "px", formatMs(expiration))
} else {
args = append(args, "ex", formatSec(expiration))
}
}
cmd := NewStatusCmd(args...)
c.process(cmd)
return cmd
}

在同一個檔案中找到cmdable的定義

1
2
3
type cmdable struct {
process func(cmd Cmder) error
}

回頭看我們的redis.go,發現一樣的東西

1
2
3
4
type baseClient struct {
// Ignore unrelated fields
process func(Cmder) error
}

因為有同樣的Singature,所以可以把baseClient當cmder`來用

所以?

雖然找到了我想要的答案,不過我不喜歡這方法
由於我找不到Set這函數,於是我需要grep找到可能的實作 => 發現baseClient和cmadble的相似處
那為什麼不直接用繼承關係就好了,這樣可以找到相依性

1
2
3
4
5
6
struct ICmdable {
virtual std::error process(...) = 0;
};
struct baseClient : ICmdable {
std::error process(...) override;
};

這樣可以看出baseClient必須繼承’ICmdable’這個介面
不過可能引申出多重繼承的問題,老話一句,沒有什麼方法一體適用

現在在科技業沒講個深度學習會被翻白眼,Prototype是一回事,放進Product又是另一回事
之前一般來說通用信的選擇是Tensorflow,雖說Prototype跟Product可以一起完成
不過C++那端難寫就算了,Python那邊也麻煩的要死,沒太多精力搞這個
後來看到PyTorchOnnx,以及Caffe2改變了想法
用易學易用的PyTorch建構出Onnx Mdoel,透過Onnx轉換成Caffe2 Model,加上對終端最佳化的Caffe2 Library
變成另外一種可行的解法
我自己的實驗結果就放在 GitHub
其中C++ Demo的部分是從Caffe2 Android Example那邊學來的
雖然看起來不多,不過真正讓他可以動倒是花了不少時間

過年前有個工作需求,需要跨平台的AES硬體加速功能,研究了一下幾種方案
– Openssl (增加相依性,加上編譯時需要設定改來改去)
– Runtime JIT (先判斷CPU種類,然後根據CPU類型生成組合語言,小小的玩意哪需要玩這麼大)
libkcapi – Linux Kernel Crypto API User Space Interface Library
覺得最後一種方式不錯,不過我只需要AES,其他地方可以拿掉
因此對這Project進行二次加工,產生了KCAES這個專案
不過在進行AES CBC運算時,發現長度超過64K就會報錯,只好對超過64K的Block進行二次加工
不需要其他的相依性,只要把檔案放進自己的專案,加入編譯即可

去英國晃了一圈,回來還是寫點東西,免得生疏了

static_assert

沒什麼好說的,就是static_assert改成允許單參數,直接看程式碼

Before C++17

1
static_assert(sizeof(short) == 2, "sizeof(short) == 2")

After C++17

1
static_assert(sizeof(short) == 2)

Inline Variables

對Header-Only library特別有用,不過我不喜歡Header-Only library
原先如果要定義一個變數,要在header宣告,在source code裡面定義
現在可以直接寫在header裡了

Before C++17:

1
2
3
4
5
// foo.h
extern int foo;

// foo.cpp
int foo = 10;

After C++17:

1
2
// foo.h
inline int foo = 10;

constexpr labmda

原先C++14辦不到,C++17允許的能力

1
2
3
4
5
constexpr int Func(int x)
{
auto f = [x]() { return x * x; };
return x + f();
}

不過我還沒想到這東西可以做啥

capture [*this]

原本我還搞不懂capture [this]capture [*this]有什麼不同,自己寫了一個範例之後搞懂了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <string>
#include <iostream>
struct Obj {
Obj() = default;
Obj(const Obj&) { std::cout << "Copy Constructor\n"; }
void f() {}
void g() {}
void h() {}
void func() {
auto lambda1 = [this]() mutable { f(); };
auto lambda2 = [self = *this]() mutable { self.g(); };
auto lambda3 = [*this]() mutable { h(); };
lambda1();
lambda2();
lambda3();
}
};
int main()
{
Obj o;
o.func();
}

capture [*this]相當於上面的[self = *this],會將原有的物件複製一份
[this]不會

More Attributes

C++11引進了Attribute,在C++17增加了更多attributes,如[[fallthrough]]等
就是把GCC/VC的attribute標準化,不多做解釋了

Rest

至於STL的加強就不特別寫了, Guaranteed Copy Elision可能要另外寫
就先寫到這了

Reference

cpp17_in_TTs
C++17

if constexpr

以往我們可能寫出類似這樣的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <string>
template <typename T>
int func(T) {
return -1;
}
template <>
int func(std::string v)
{
return 12;
}
template <>
int func(int)
{
return 34;
}
int main()
{
return func(std::string("123"));
}

如今我們可以寫成這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <string>
template <typename T>
int func(T) {
if constexpr (std::is_same_v<T, std::string>) {
return 12;
} else if constexpr(std::is_same_v<T, int>) {
return 34;
} else {
return -1;
}
}
int main()
{
return func(std::string("123"));
}

省去了很多的冗於
如果配合variant來使用,程式碼可以寫成這樣

1
2
3
4
5
6
7
8
9
10
11
12
#include <string>
#include <variant>
using unionType = std::variant<std::string, int>;
int main()
{
unionType v = 3;
return std::visit([](const auto &v) {
using T = std::decay_t<decltype(v)>;
if constexpr (std::is_same_v<T, int>) return 12;
else return 34;
}, v);
}

Class Deduction guide

在c++17之前,寫了很多這樣子的程式碼

1
2
std::mutex m;
std::lock_guard<std::mutex> lock(m);

為什麼函數可以推導出型別,而類型不行,於是C++17放寬了這條件

1
2
std::lock_guard lock(m);
std::vector v{1, 2, 3};

當然,也是有explicit class deduction guide的,請參考reference

template <auto>

目前看起來沒什麼用的feature
未用c++17前程式碼長這樣

1
2
3
4
5
template <typename T, T v>
struct integral_constant {
static constexpr T value = v;
};
integral_constant<int, 1024>::value;

用了C++17後

1
2
3
4
5
template <auto v>
struct integral_constant {
static constexpr auto value = v;
};
integral_constant<1024>::value;

nested namespace

早該有的東西, 結果拖到這麼後面才加進來

1
2
3
4
5
namespace X {
namespace Y {
struct Foo;
}
}

現在可以寫成

1
2
3
namespace X::Y {
struct Foo;
}

Fold expression

以加法為範例
在c++17之前的寫法

1
2
3
4
5
6
7
8
template<typename T>
T sum(T v) {
return v;
}
template<typename T, typename... Args>
T sum(T first, Args... args) {
return first + sum(args...);
}

把sum寫成兩部分,雖然不是不行,不過總覺得被切割加重學習負擔
用上if constexpr

1
2
3
4
5
6
7
8
template<typename T, typename... Args>
T sum(T first, Args... args) {
if constexpr (sizeof...(Args) == 0) {
return first;
} else {
return first + sum(args...);
}
}

好一點了,用上Fold expression會變成怎樣

1
2
3
4
template<typename ...Args>
auto sum(Args&&... args) {
return (args + ...); // OK
}

if constexpr方法比是更精簡了一點,不過多了一堆語法規則,實在不太划算

Reference

A Tour of C++ 17: If Constexpr
C++17中的deduction guide
C++17 Fold Expressions