比特幣的數據儲存方式 blk.dat

blk.dat
Raw blockchain data files 原始區塊鏈數據文件: https://learnmeabitcoin.com/guide/blkdat

Magic Bytes: https://learnmeabitcoin.com/guide/magic-bytes


完整解析,包括程式讀寫細節:
https://zhuanlan.zhihu.com/p/31924911
https://blog.csdn.net/t46414704152abc/article/details/90174727


几乎所有数据都是以小端类型(Little-Endian)存储的,但是 Transaction Input/Output 里的script除外。它们是以大端方式(Big-Endian)存储的。

magic number是一个固定的数,其值为 0xD9B4BEF9

variable integer本身的长度不定,在 1 到 9 字节之间。具体的解析规则如下:

如果第一个字节小于 253,那么直接返回该字节所代表的数值

如果第一个字节等于 253,那么往后再读一个字节作,将其值作为返回值

如果第一个字节等于 254,那么往后再读四个字节作,将其值作为返回值

如果第一个字节等于 255,那么往后再读八个字节作,将其值作为返回值

如果我们把之前的数据按照上述规则带入的话可以得到如下图表。



MySQL 的 varchar 到底可以存幾個中文字或幾個英文字呢

utf8 編碼為 1~3 個 字节
utf8mb4 編碼為 1~4 個 字节

UTF-8:一个汉字 = 3个字节,英文是一个字节

varchar(n) 表示n个字符,无论汉字和英文,MySql都能存入 n 个字符,仅实际字节长度有所区别。

MySQL检查长度,可用SQL语言
SELECT LENGTH(fieldname) FROM tablename


以下為網友使用 UTF-8 的實驗

>> "我爱Ruby".size
=> 6
>> "I Love Ruby!".size
=> 12

mysql> select * from str_test;
+------------+--------------+
| name_chn   | name_en      |
+------------+--------------+
| 我爱Ruby   | I Love Ruby! |
+------------+--------------+
1 row in set (0.02 sec)


mysql> select length(name_chn) from str_test;
+------------------+
| length(name_chn) |
+------------------+
|               10 |
+------------------+
1 row in set (0.01 sec)

3[一个汉字三字节] * 2 + 1[一个英文一字节] * 4 = 10

mysql> select length(name_en) from str_test;
+-----------------+
| length(name_en) |
+-----------------+
|              12 |
+-----------------+
1 row in set (0.00 sec)

10[一个英文一字节] * 1 + 2[空格一字节] * whitespace = 12


关于varchar 最多能存多少值

mysql的记录行长度是有限制的,不是无限长的,这个长度是64K,即65535个字节,对所有的表都是一样的。

MySQL对于变长类型的字段会有1-2个字节来保存字符长度。

当字符数小于等于255时,MySQL只用1个字节来记录,因为2的8次方减1只能存到255。

当字符数多余255时,就得用2个字节来存长度了。

在utf-8状态下的varchar,最大只能到 (65535 - 2) / 3 = 21844 余 1。


結論:

從 MySQL 8 開始,預設的 CHARSET 為 utf8mb4,COLLATE 為 utf8mb4_0900_ai_ci

所以 VARCHAR 理論上可以儲存大約 (65535-2)/4 = 16383,
實測後最大為 VARCHAR(16331)




參考資料:
https://21st.each1.net/2017/03/mysql.html
https://ruby-china.org/topics/24920

廣達將佈局量子電腦

廣達董事長林百里對量子電腦看法大翻盤,他13日出席媒體說明會,首度透露已指派幾位工程師投入量子電腦研究,為十年後更大量的雲端與IoT(物聯網)商機做準備。

時間回到今年4月,林百里出席員工家庭日活動時,談到量子電腦仍保守以對,認為演算法及材料使用都具不確定性,談量子電腦言之過早,那是15-20年後的事。

不過,半年後他的態度丕變,似乎跟台積電董事長劉德音透露台積電不會缺席量子電腦機會有關,也與重要客戶Google與IBM近期在量子電腦頻頻較勁上難脫關係。

「假如有一天,每一個人的口袋都有一個量子電腦,我想TSMC(台積電)一定不會缺席。」台積電董事長劉德音9月在一場論壇中表示,市場預期這將是一奈米以下製程的產品。

林百里:台積電是上游,我們下游

「我其實很怕十年後量子計算出來,這世界又將天翻地覆。」林百里說。他坦言:「我們(廣達)開始研究量子計算這回事,但我其實很怕。」他指的是量子電腦帶來的世界變局,科技業者需隨之應變。

「目前量子電腦運算機器都很不穩定,因為這是超導體、超低溫的挑戰,這也不是我們(廣達)可以做的,」林百里說,十年後他都80歲了,應該退休了,所以他現在先找幾個工程師研究(量子電腦),他判斷自己退休前應該都還不會真正生產量子電腦。

不過儘管林百里預言,他退休前廣達還不會生產量子電腦,但他看好十年後,量子時代將對廣達有百利,帶來的是雲端跟IoT裝置商機。

「量子時代對廣達更有利,因為運算能量更大,(處理器)裡面很快,需要接收資料量更大,因為接收太快了,一奈米或兩奈米能不能做到都還不知道,也因為運算很快,對外傳輸要用光網路。」林百里描繪量子運算的架構,顯然已經做過一番研究。

林百里說,廣達不會做引擎(量子晶片),但其他東西數量太大,比方應用,比方量子時代的雲端商機(量子電腦伺服器),這些廣達都非常樂觀,因為「世界上能做好的伺服器的公司不多,越來越難做,對我們(廣達)就會越來越有興趣。」

廣達副董事長梁次震也補充,「當運算越來越快,一定會想出應用之處,或許有一天人不用手跟嘴巴,頭上擺兩個量子就可以。」他打趣的說。

林百里舉當年雲端時代來臨,市場開始擔心NB會完蛋,但後來不僅不會,反而延伸出很多IOT應用,「量子是更大的應用,台積電也碰到量子問題,屆時若半導體需求更多,晶片需求更多,廣達做箱子(伺服器)的需求也就更多。」

遵循台積電的腳步,林百里對量子電腦轉為開放擁抱,他直言「台積電是上游,我們是下游,上游水多,我們就水多,供應鏈一致!我們兩家觀點一致!十年後量子電腦時代出來,預估是一奈米以下製程,我們箱子(伺服器)也要做(量子伺服器),箱子越來越難搞,有新的東西當然好玩。

十年內,飛機理論看好四大技術三大商機

至於十年之內,林百里看好的是AI、雲端、5G、IoT四者整合機會,13日他再度拿出一張「飛機理論」圖片描繪四者間的關係,「AI是機師,5G是噴射引擎,延伸出雲端技術,結果是要靠IoT賺錢,這是大整合機會,要四種都會做,提供解決方案才能賺錢!」

在這大技術融合趨勢中,廣達將有幾個佈局方向,首先是智慧醫療,其次是智慧工廠,第三是5G時代的雲端升級需求。

智慧醫療是最難做的事情,因為關乎人命,商業模式要考慮很多,涉及醫療倫理、品質、效率,林百里透露,廣達正在做研發病房及醫護平台,希望2020~2021年產品能落地試營運,但最大考驗仍然是行銷跟商業模式。

林百里解釋,因為若不做ODM,作品牌必須有行銷力,光做台灣市場太小,難以規模化,但若做全世界生意,醫療的品牌是最困難的,不僅要取得醫療認證及醫療機構認可,生意模式怎麼做?銷售人才如何培養?都是問題。

不過,林百里反而最看好AI與5G在三至五年內帶來的機會,他指出,5G背後的雲端機房跟4G不同,當各國發展5G將帶來非常大的需求,而當AI服務上路,Google等雲端客戶業務也將大成長,5G需要大量雲端,靠數量眾多的IoT收集資料,所以雲端跟IoT也將大成長。

在智慧製造方面,廣達林口總部又將興建新大樓,預計2020年第四季完工,這也成為智慧工廠實驗場域,林百里表示,廣達導入自動化機器人,加上AI檢驗,若未來導入5G,把感測器跟機器人都串連起來,能做大數據即時分析,即時決策,生產效率將提升20%,是非常大的商機。

「機器人也有聰明跟笨的!」林百里笑說,未來連線AI的是聰明機器人,而笨的機器人只會說「你好!你好!」毫無用途。

林百里早在20年前就看好行動聯網商機,率先投入筆電開發,現在則大力倡議AI戰略,梁次震補充:其實林百里早在31年前就已經想到廣達(Quanta)會做量子電腦(Quantum),所以創業取名廣達,林百里則笑著回應「廣達」就是取意產品既廣又能無線通達,兩位創辦人一番對談,讓外界多少能一窺科技前瞻領袖的先見之明。

醫師:癌症關鍵死因是它!每天1動作戰勝癌細胞

https://www.edh.tw/article/22819

有做有保佑!運動不能少,傳授你超簡單「強體力運動」

江坤俊醫師表示,除了飲食,運動對癌症病患來說也很重要,但正在接受化療的癌症病患,可能因為身體太過虛弱沒辦法做室外運動,或是怕走出室外容易被病菌感染。為此,江醫師教大家幾個簡易的室內運動,給癌症病患參考。江醫師教的運動叫做「拍手運動」,非常簡單:

  1. 踏步
  2. 每踏一步你的手就拍打一下。

這麼簡單的運動只要持續進行30分鐘,體力就會得到很大的改善。「千萬不要坐著不動,坐著不動癌細胞就會戰勝你喔!」

江醫師最後再次呼籲運動的重要性:「你只要有做(運動)就一定有保佑,多運動對你的身體一定有幫助。」



長庚醫院副教授級主治醫師江坤俊舉一個例子:曾有一位乳癌病患在化療期間測肝功能時,發現肝功能飆到2000多,本來醫生們以為是化療藥物的副作用,但換了藥物後肝功能還是沒有降下來。後來才發現,該病患有個遠房親戚買了很昂貴的補品給他補身子,正是那個補品傷了肝臟...

飲食對癌症病患來說是非常重要的大事,不僅不能隨便給他們吃補品,以免傷肝、傷腎造成治療的麻煩,營養攝取本身就是癌症治療會否成功的一大關鍵。

癌症很可怕,但為什麼可怕?一般人可能不夠清楚。江坤俊醫師講解癌症的二大致命原因:


  1. 癌細胞會轉移、侵害器官如果得到肝癌,癌細胞把肝臟都佔據了,肝功能就沒有了,失去了解毒功能,人當然就會死亡。若是得到肺癌,癌細胞把肺都佔據了,人沒辦法呼吸當然也會死。至於乳癌,雖然乳房不是維持生命的器官,但乳癌細胞會轉移,若轉移到肝、肺等重要器官就可能致人於死。
  2. 癌細胞會與人體搶營養,使人虛弱癌細胞生長迅速,會與正常細胞搶奪營養,如果癌症病患沒有好好補充營養,常常會產生「癌症的惡體質」,變得非常虛弱。江醫師指出:「其實有很多癌症病患並非死於癌症本身,而是死於癌症的惡體質。」約有20%的癌症病患是死於營養不良。江醫師強調:「飲食對癌症病患來說真的非常重要!」曾有一位乳癌四期的病患,雖然她有求生意志,但當時對她真的是愛莫能助,因為她虛弱到沒辦法下床的程度。在這種狀況下,化療做下去身體也禁受不住。那位病人最後就眼睜睜地死於虛弱,連治療的機會也沒有。


大家總覺得吃紅肉、喝豆漿會致癌…醫師說:大錯特錯,反而鼓勵多吃肉、喝豆漿補充蛋白質!

關於癌症病患的飲食,民眾還是有一些迷思,江醫師舉出3個例子:

  1. 罹癌期間能不能吃肉、喝豆漿?大家總覺得吃紅肉不太好、認為吃紅肉會致癌;加上肉沒有煮熟的話怕有吃進寄生蟲的疑慮,對於做化療、抵抗力差的病患來說,可能會造成敗血症等疾病。因此,很多癌症病患以為在罹癌期間,特別是在化療時不能吃肉、尤其是紅肉。但這些觀念都是錯的。江醫師表示,化療最大的副作用是病患的白血球數量降低,白血球數量降低病患非常容易受到感染,所以一定要讓白血球數量回復,這時需要大量的蛋白質。因此,醫師非但不會禁止癌症病患吃肉,反而「強烈建議」一定要多吃肉,特別是紅肉。因為紅肉不但有助於生成白血球,而且紅肉裡面有鐵,也有助於生成紅血球。江坤俊醫師特別提到了豆漿。他指出,很多人以為得到乳癌後,含有類似女性荷爾蒙的食品不要去碰;但江醫師指出:「豆漿裡面的荷爾蒙是大豆異黃酮,它跟動物性的荷爾蒙是不一樣的。」所以他會建議病患,特別是乳癌的病患,如果喜歡喝豆漿,在化療的期間攝取豆漿。
  2. 飢餓療法有效嗎?癌細胞會跟身體搶營養,且搶得比正常細胞還快。有人認為病患吃東西補的是癌細胞而不是自己的身體,所以採用飢餓療法,想餓死癌細胞。江坤俊醫師「飢餓療法對癌細胞有沒有效?有效,只是對不起!你會先死才是癌細胞死!」江醫師強調,癌症病患一定要有充足的營養,有充足的營養才有充足的體力去接受化療。
  3. 生酮飲食可以抗癌?生酮飲食近年很流行,很多人利用生酮飲食來減肥,有一部分的人則利用生酮飲食來抗癌。為什麼生酮飲食會跟癌症連上關係呢?江坤俊醫師表示,因為國外有些研究團隊確實利用生酮飲食來治療腦部的癌症;但江醫師強調:第一,只有幾個報導證明生酮飲食抗癌有效;第二,現在只證實生酮飲食對腦癌有一定的幫忙,其他的癌症跟生酮飲食都沒有關係。

Golang: for .... range ...

https://play.golang.org/p/xDH3hPfYYwa

package main

import "fmt"

func main() {
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)

for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}

kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}

for k := range kvs {
fmt.Println("key:", k)
}

for i, c := range "go" {
fmt.Println(i, c)
}
}

Javascript: 函數如何返回多組變數 Return multiple values in JavaScript?

https://stackoverflow.com/questions/2917175/return-multiple-values-in-javascript

Q. I am trying to return two values in JavaScript. Is that possible?

A.
No, but you could return an array containing your values:
function getValues() {
    return [getFirstValue(), getSecondValue()];
}
Then you can access them like so:
var values = getValues();
var first = values[0];
var second = values[1];
With the latest ECMAScript 6 syntax*, you can also destructure the return value more intuitively:
const [first, second] = getValues();
If you want to put "labels" on each of the returned values (easier to maintain), you can return an object:
function getValues() {
    return {
        first: getFirstValue(),
        second: getSecondValue(),
    };
}
And to access them:
var values = getValues();
var first = values.first;
var second = values.second;
Or with ES6 syntax:
const {first, second} = getValues();
* See this table for browser compatibility. Basically, all modern browsers aside from IE support this syntax, but you can compile ES6 code down to IE-compatible JavaScript at build time with tools like Babel.




JavaScript: Function 前面加上小括弧

https://freedomknight.me/javascript-ni-ming-han-shu/

如果沒有那個小括號,他就像是最一開始的宣告函數的樣子,直譯器期待你宣告一個函數的名稱。
而小括號對 js 來講,算是個強迫運算,像我們平時運算 (2 + 3) * 5 會用小括號先強迫把 2 + 3 算出來。

對這個小括弧而言,則是強迫生出個匿名函數並且呼叫

具有名稱時我們是如此呼叫:
add(1, 2);

現在是匿名函數:
(匿名函數)(1, 2);

其實形式上還是一樣,單純只是變形的呼叫。這時使用匿名的最大大大優點就是「船過水無痕」。 任何你在裡頭做的事情,所佔的任何空間,到最後會馬上釋放。

在小型 js 看不出來,但是在大型一點的程式,大家一股腦的宣告了一堆全域函數跟變數,如果瀏覽器還沒離開這個頁面,這些空間就一直被佔著,無法釋放。



JavaScript: ECMAScript 相容性


腳本引擎參考應用程式相容性[11]
ES5[12]ES6[13]ES7[14]較新(2016+)[14][15]
ChakraMicrosoft Edge 18100%96%100%58%
SpiderMonkeyFirefox 63100%98%100%78%
Chrome V8Google Chrome 70、Opera 57100%98%100%100%
JavaScriptCore(Nitro)Safari 1299%99%100%90%


JavaScript: SHA256, byte array, 16進制字串 轉換等

JavaScript SHA-256 demo: https://geraintluff.github.io/sha256/


https://github.com/emn178/js-sha256

到 https://cdnjs.com/ 搜尋
=> https://cdnjs.cloudflare.com/ajax/libs/js-sha256/0.9.0/sha256.min.js

A simple SHA-256 / SHA-224 hash function for JavaScript supports UTF-8 encoding.

It also supports byte `Array`, `Uint8Array`, `ArrayBuffer` input



https://github.com/brix/crypto-js
https://code.google.com/archive/p/crypto-js/
https://blog.zhengxianjun.com/2015/05/javascript-crypto-js/

<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/hmac-sha256.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/enc-base64.min.js"></script>

var hash = CryptoJS.SHA256("Message").toString();

https://github.com/brix/crypto-js/issues/157

var hash = CryptoJS.HmacSHA256("Message", "key");

// Message 是按照使用者的規則拼接的字串
// key為設置密鑰




https://github.com/digitalbazaar/forge

<script src="https://cdn.jsdelivr.net/npm/node-forge@0.7.0/dist/forge.min.js"></script>
<script type="text/javascript">
"use strict";

var md = forge.md.sha256.create();
md.update('The quick brown fox jumps over the lazy dog');
console.log(md.digest().toHex());

console.log(hexToBytes(md.digest().toHex()));

console.log(bytesToHex(hexToBytes(md.digest().toHex())));

// Convert a hex string to a byte array
function hexToBytes(hex) {
    for (var bytes = [], c = 0; c < hex.length; c += 2)
    bytes.push(parseInt(hex.substr(c, 2), 16));
    return bytes;
}

// Convert a byte array to a hex string
function bytesToHex(bytes) {
    for (var hex = [], i = 0; i < bytes.length; i++) {
        var current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i];
        hex.push((current >>> 4).toString(16));
        hex.push((current & 0xF).toString(16));
    }
    return hex.join("");
}

</script>




//十六进制字符串转字节数组
function StrToBytes(str) {
var pos = 0;
var len = str.length;

if(len %2 != 0) {
return null;
}
len /= 2;
var hexA = new Array();

for(var i=0; i<len; i++) {
var s = str.substr(pos, 2);
var v = parseInt(s, 16);
hexA.push(v);
pos += 2;
}
return hexA;
}

//字节数组转十六进制字符串
function BytesToStr(arr) {
var str = "";

for(var i=0; i<arr.length; i++) {
var tmp = arr[i].toString(16);

if(tmp.length == 1) {
   tmp = "0" + tmp;
}

str += tmp;
}
return str;
}



javascript 使用 toString 10進位轉2或8或16進位.

toString():預設是10進位轉其它進位,同時,配合正則,可匹配字符。

範例:

<script language="javascript">
var x = 100;
var y1 = x.toString(2);
var y2 = x.toString(8);
var y3 = x.toString(16);
var z = "2進位 = " + y1 + "<br>8進位 = " + y2 + "<br>16進位 = " + y3;
document.write(z);
</script>





Golang 同時監聽 TCP 與 HTTP 多個端口 提供服務

https://javasgl.github.io/golang-listen-tcp-and-http-port/

项目入口 main.go 中,之前是监听 TCP 连接,代码大致如下:

func main(){
 server,err:= net.Listen("tcp","host:port")
 if err!=nil{
  return
 }
 defer server.Close()

 for{
  conn,err:= server.Accept() 
  if err!=nil{
   continue
  }
  go handleConn(conn)
 }
}

func handleConn(conn net.Conn){
 //do somethings
}

现在需要在此基础之上监听 http 连接,由于 http.ListenAndServe() 方法是阻塞的,所以需要新开goroutine进行监听,代码示意如下:

func main(){

 http.HandleFunc("/hi", Router)
 //因为会阻塞,所以需要新开goroutine进行监听
 go http.ListenAndServe("host:http_port", nil)

 server,err:= net.Listen("tcp","host:tcp_port")
 if err!=nil{
  return
 }
 defer server.Close()

 for{
  conn,err:= server.Accept() 
  if err!=nil{
   continue
  }
  go handleConn(conn)
 }
}

func handleConn(conn net.Conn){
 //do somethings
}



Golang 学习笔记:HTTP, TCP/IP, UDP
http://blog.wuxu92.com/go-lang-notebook-2/

應詳閱出處,相當詳細

import (
 . "fmt"
 "net/http"
)

const (
 PORT = ":1024"
 MSG = "hello, gopher"
)
func main() {
 http.HandleFunc("/hello", Hello)
 go func() {
  http.ListenAndServe(PORT, nil)
 }()
 
 go func() {
  http.ListenAndServeTLS(":443","cert.pem", "key.pem", nil)
 }()
 for {}
}
func Hello(w http.ResponseWriter, r *http.Request) {
 w.Header().Set("Content-Type", "text/plain")
 Fprintf(w, MSG)
}

这段代码启动了一个server,开启了http和https支持;分别监听在1024端口和443端口。
有几个需要注意的地方。
  1. http.HandleFunc(string, func(http.ResponseWriter, *http.Request)) 绑定某个请求路径的处理方法
  2. 使用goroutine执行监听与服务动作。启动了两个goroutine
  3. 最后使用一个for循环保证主线程不会退出。因为主线程退出的话goroutine也会结束。
  4. 其中的TLS支持的HTTPS服务的cert和key文件可以使用Go源码中带的文件生成go run $GOROOT/src/crypto/tls/generate_cert.go -ca=true -host="localhost"
最后一个无限循环可以使用channel代替,可以使用sync.WaitGroup实现, sync.WaitGroup持有一个计数器,其Add(int)方法增加计数器,其Done()方法减小计数器,其Wait()方法会让线程阻塞等待计数器恢复0。其实现如下。
import (
 . "fmt"
 "net/http"
 "sync"
)

const (
 PORT = ":1024"
 MSG = "hello, gopher"
)
var servers sync.WaitGroup

func main() {
 http.HandleFunc("/hello", Hello)
 Launch(func() {
  http.ListenAndServe(PORT, nil)
 })
 
 Launch(func() {
  http.ListenAndServeTLS(":443","cert.pem", "key.pem", nil)
 })
 
 servers.Wait()
}

func Launch(f func()) {
 servers.Add(1)
 go func() {
  defer servers.Done()
  f()
 }()
}

func Hello(w http.ResponseWriter, r *http.Request) {
 w.Header().Set("Content-Type", "text/plain")
 Fprintf(w, MSG)
}
使用上面的方法可以添加任意多的监听端口。可以通过Server做更多的配置。
s := &http.Server{
 Addr:           ":8080",
 Handler:        myHandler,
 ReadTimeout:    10 * time.Second,
 WriteTimeout:   10 * time.Second,
 MaxHeaderBytes: 1 << 20,
}
log.Fatal(s.ListenAndServe())

HTTP与Go程结合

我们对Goroutine和HTTP都比较熟悉了,我们可以设置一个任务队列,让每一个请求都进入这个队列然后使用一定数量的Goroutine去执行逻辑。这样效率比上面的实现要高很多。

发送GET/POST请求

http保重提供了非常方便的发发送get请求和post请求的封装。
resp, err := http.Get("http://example.com/")
...
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
...
resp, err := http.PostForm("http://example.com/form",
 url.Values{"key": {"Value"}, "id": {"123"}})

defer resp.Body.Close()  // 不要忘记defer关闭资源
body, err := ioutil.ReadAll(resp.Body)  // 读取资源
strCont := string(body)

环境变量

使用os包中的Getenv(string)Setenv(string, string)方法获得和设置环境变量。要注意Setenv的作用域只是当前运行进程,结束后或者重新运行后环境变量会重置为系统设置值。
Println(os.Getenv("PATH"))
os.Setenv("GOOO", "gofast")
os.Setenv("PATH", ".")
Println(os.Getenv("PATH"))

操作系统信号

当程序在命令行中运行的时候,一般使用Ctrl+c可以终结程序运行。对于操作系统有一定了解的话应该知道,Ctrl+c向运行的进程发送了一个结束进程的signal。这种方式很有用,但是有点时候暴力结束进程执行不是我们想要的逻辑,我们可以捕获这个termination signal,然后对他做一些自己的处理。注意,windows下不能捕获这些信号,只在*nix下有效。
使用os/signal包提供的封装。
package main

import (
 "fmt"
 "os"
 "os/signal"
 "time"
)
var i int

func init() {
 go SignalHandler(make(chan os.Signal, 1))
}
func main() {
 i = 0
 for {
  i++
  time.Sleep(time.Second)
 }
}

func SignalHandler(c chan os.Signal) {
 signal.Notify(c, os.Interrupt)
 for s:= <-c;; s= <-c {
  switch s {
   case os.Interrupt:
    fmt.Println("interrupt received", i)
    os.Exit(0)  // 去掉这一行,那么Ctrl+c的快捷键将不能中断进程运行
   default:
    fmt.Println("default case")
  }
 }
}
运行上面的代码,在Ctrl-c时可以看到输出 interrupt received 字符串。
除了接收系统的信号,还可以向其他的进行发送信号。
syscall.Kill(syscall.Getpid(), syscall.SIGABRT)
这一部分可能暂时用不上,只要知道有这种用法就可以了。

TCP/IP

使用net提供的TCP/IP封装可以很方便的监听与请求端口。
server(src/wuxu/tcpserver/main.go):
package main

import (
 "net"
 "fmt"
)
var i int
func main() {
 i = 0
 if listener, e := net.Listen("tcp", ":1024"); e == nil {
  for {
   if conn, e := listener.Accept(); e==nil {
    go func(c net.Conn) {
     defer c.Close()
     fmt.Fprintln(c, "hihi", i)
     i++
    }(conn)
   }
  }
 } else {
  fmt.Println("error")
 }
}
client(src/wuxu/tcpclient/main.go)
package main

import (
 "fmt"
 "net"
 "bufio"
)

func main() {
 if conn, e := net.Dial("tcp", "localhost:1024"); e == nil {
  defer conn.Close()
  if text, e := bufio.NewReader(conn).ReadString('\n'); e ==nil {
   fmt.Println(text)
  }
 } else {
  fmt.Println("error", e)
 }
}

加密的TCP通信

还可以很方便地简历加密的连接,为了加密连接我们需要两套密钥对,即服务器的公钥与私钥,客户端的公钥与私钥,使用之前的方法再在服务器和客户端的目录下,各生成一对密钥。go run $GOROOT/src/crypto/tls/generate_cert.go -ca=true -host="localhost"
修改代码如下:
server:
package main

import (
 "fmt"
 "crypto/tls"
 "crypto/rand"
)
var i int
func main() {
 i = 0
 var config tls.Config
 if certificate, e := tls.LoadX509KeyPair("cert.pem", "key.pem"); e ==nil {
  config = tls.Config{
   Certificates: []tls.Certificate{certificate},
   Rand: rand.Reader,
  }
 }
 if listener, e := tls.Listen("tcp", ":1024", &config); e == nil {
  for {
   if conn, e := listener.Accept(); e==nil {
    go func(c *tls.Conn) {
     defer c.Close()
     fmt.Fprintln(c, "secure hihi", i)
     i++
    }(conn.(*tls.Conn))
   }
  }
 } else {
  fmt.Println("error")
 }
}
可以看出,引入tls包后不需要net包了,tls实现了相关得了协议的加密版本。
client:
package main

import (
 "fmt"
 "bufio"
 "crypto/tls"
)

func main() {
 var config tls.Config
 if certificate, e := tls.LoadX509KeyPair("cert.pem", "key.pem"); e == nil {
  config = tls.Config{
   Certificates: []tls.Certificate{certificate},
   InsecureSkipVerify: true,
  }
 }
 if conn, e := tls.Dial("tcp", "localhost:1024", &config); e == nil {
  defer conn.Close()
  if text, e := bufio.NewReader(conn).ReadString('\n'); e ==nil {
   fmt.Println(text)
  }
 } else {
  fmt.Println("error", e)
 }
}
和使用net包的整体流程差不多,使用tls包的实现替换net包。
这样我们就实现了一个使用tls密钥加密的传输通道了。

UDP

UDP的使用和TCP类似也包括一种加密的UDP传输。具体UDP和TCP的区别就不详细说了,这部分是考试面试的重点,一般都能说个123出来。
与TCP实现不一样的是,UDP要先使用net.ResolveUDPAddr(string, string)来获得一个*net.UDPAddr,然后使用net.ListenUDP("udp", addr)监听这个端口的UDP通信。一个简单实现如下:
import (
 . "fmt"
 "net"
 "os"
)

var MSG = ([]byte)("hello golong udp\n")

func main() {
 if addr, e := net.ResolveUDPAddr("udp", ":1026"); e == nil {
  if server, e := net.ListenUDP("udp", addr); e == nil {
   // infinit loop
   for buffer := MakeBuffer(); ; buffer = MakeBuffer() {
    if n, client, e := server.ReadFromUDP(buffer); e == nil {
     go func( c *net.UDPAddr, packet []byte) {
      if n, e := server.WriteToUDP(MSG, c); e == nil {
       Printf("%v bytes written to %v\n", n, c)
      }
     }(client, buffer[:n])
    } else {
     Println("error1", e)
    }
   }
  } else {
   Println("error2", e)
  }
 } else {
  Println("error3", e)
 }
}

func MakeBuffer() ([]byte) {
 return make([]byte, 1024)
}
作为server需要一个无限循环保持监听,每当收到一个客户端请求(ReadFromUDP),就启动一个Goroutine服务这个请求(WriteToUDP)。
UDP不想TCP服务可以用telnet连接,因为它是一个无连接的通信,我们需要自己写一个请求的客户端。client也比较简单,其实现如下:
import (
 . "fmt"
 "net"
 "bufio"
)

var CRLF = ([]byte)("\n")

func main() {
 if addr, e := net.ResolveUDPAddr("udp", "localhost:1026"); e == nil {
  if server, e := net.DialUDP("udp", nil, addr); e == nil {
   defer server.Close()
   // 随意发送一些内容到server等待回应
   // 这里可以多次(for 循环写都可以)写内容到服务器
   if _, e := server.Write(CRLF); e == nil {
    if text, e := bufio.NewReader(server).ReadString('\n'); e == nil {
     Printf("%v", text)
    }
   }
  } else {
   Println("error2", e)
  }
 } else {
  Println("error3", e)
 }
}
这里注意注释里面的话,使用Write()方法向UDP Server发送一些内容,然后才会受到Server的返回,返回使用bufio读取。一个UDP的Dial实例可以多起发送数据和读取返回。

使用加密的UDP

Golang提供了很方便的加密UDP连接,只需要提供一个密钥,就可以对传输的数据进行加密了,当然我们需要对server和client的数据都进行修改。
rsa encrypt server:
import (
 . "fmt"
 "bytes"
 "crypto/rand"
 "crypto/rsa"
 "crypto/sha1"
 "encoding/gob"
 . "net"
)

var MSG = []byte("hello go scure udp")
var RSA_LABEL = []byte("server")

func main() {
 Serve(":1025", func(conn *UDPConn, c *UDPAddr, packet *bytes.Buffer) (n int) {
  var key rsa.PublicKey
  if e := gob.NewDecoder(packet).Decode(&key); e==nil {
   if resp, e := rsa.EncryptOAEP(sha1.New(), rand.Reader, &key, MSG, RSA_LABEL); e==nil {
    n, _ = conn.WriteToUDP(resp, c)
   }
  }
  return
 })
}

func Serve(addr string, f func(*UDPConn, *UDPAddr, *bytes.Buffer) int) {
 Launch(addr, func(conn *UDPConn) {
  for {
   buffer := make([]byte, 1024)
   if n, client, e := conn.ReadFromUDP(buffer); e == nil {
    go func(c *UDPAddr, b []byte) {
     if n := f(conn, c, bytes.NewBuffer(b)); n!=0 {
      Println(n, "write to ", c)
     }
    }(client, buffer[:n])
   }
  }
 })
}

func Launch(addr string, f func(*UDPConn)) {
 if a, e := ResolveUDPAddr("udp", addr); e == nil {
  if server, e := ListenUDP("udp", a); e == nil {
   f(server)
  }
 }
}
rsa used client
import (
 . "fmt"
 "bytes"
 "crypto/rand"
 "crypto/rsa"
 "crypto/sha1"
 "crypto/x509"
 "encoding/gob"
 "encoding/pem"
 "io/ioutil"
 . "net"
)

var RSA_LABEL = []byte("server")

func main() {
 Connect("localhost:1025", func(server *UDPConn, priKey *rsa.PrivateKey) {
  cipherText := MakeBuffer()
  if n, e := server.Read(cipherText); e == nil {
   Println("cipher text", string(cipherText))
   // cipherText是加密的数据,需要解密
   if plainText, e := rsa.DecryptOAEP(sha1.New(), rand.Reader, priKey, cipherText[:n], RSA_LABEL); e == nil {
    Println("receive decrypt string:", string(plainText))
   }
  }
 })
}

func Connect(address string, f func(*UDPConn, *rsa.PrivateKey)) {
 LoadPrivateKey("key.pem", func(pk *rsa.PrivateKey) { // pk is private key
  if addr, e := ResolveUDPAddr("udp", address); e ==nil {
   if server, e := DialUDP("udp", nil, addr); e == nil {
    defer server.Close()
    SendKey(server, pk.PublicKey, func() {
     f(server, pk)
    })
   } else {
    Println("err1", e)
   }
  } else {
   Println("err2", e)
  }
 })
}

func LoadPrivateKey(file string, f func(*rsa.PrivateKey)) {
 if file, e := ioutil.ReadFile(file); e == nil {
  if block, _ := pem.Decode(file); block != nil {
   if block.Type == "RSA PRIVATE KEY" {
    if key, _ := x509.ParsePKCS1PrivateKey(block.Bytes); key !=nil {
     f(key)
    }
   }
  }
 } else {
  Println(e)
 }
 return
}

func SendKey(server *UDPConn, publicKey rsa.PublicKey, f func()) {
 var encodedKey bytes.Buffer
 if e := gob.NewEncoder(&encodedKey).Encode(publicKey); e == nil {
  if _, e = server.Write(encodedKey.Bytes()); e == nil {
   f()
  }
 }
}

func MakeBuffer() (r []byte) {
 return make([]byte, 1024)
}
需要注意的几点:
  • RSA_LABEL 是rsa加密使用的一个label,需要客户端和服务器端一致
  • 请求流程是客户端读取一个密钥,然后获取一个publickey,使用gob序列化后发送到server, server读取出key,使用这个publicKey对返回的数据进行加密
  • 客户端读取返回后,使用密钥进行解密,读取明文
  • 客户端代码使用了大量的回调,方法字面量,理解起来有点麻烦,可以用javascript的回调的思路去理解


程式語言編年史

程式語言編年史原文 下面這張圖片描繪了整個程式語言的歷史。包括各種程式語言的發明人、程式語言的特點和適用領域、被什麼網站或公司使用等 (檢視 完整高清圖 )。 之所以會有那麼多不同的程式語言是因為設計程式語言的初衷不同、對語言學習曲線的追求不同、不同程式之間的執行成本差異...