Jex’s Note

Golang Basic

宣告

[string]{ .. }

elements := map[string]map[string]string{
    "A": map[string]string{
        "field1": "val1",
        "field2": "val2",
    },
    "B": map[string]string{
        "field1": "val1",
        "field2": "val2",
    },
}

[string]int

map := map[string]int{"apple": 5, "lettuce": 7}

[string]interface{}

x := map[string]interface{}{
    "foo": []string{"a","b"},
    "bar": "foo",
    "baz": 10.4,
}

[]interface{}

hnapCommandMap := make(map[string]interface{})
hnapCommandMap["ID"] = "06"
hnapCommandMap["Info"] = map[string]string{
    "Name":       "Jex"
}

resque2 := map[string]interface{}{
    "class": "hnap",
    "args":  []interface{}{hnapCommandMap},
}

map[string]interface{}

方法1
a := map[string]interface{}{
    "int":    1,
    "string": "xx",
}

方法2
t := map[string]interface{}{}
t["id"] = 312
t["type"] = "realtime"
t["data"] = []map[string]string{
    {
        "did":    "did1",
        "action": "action1",
    },
    {
        "did":    "did2",
        "action": "action2",
    },
}

但不可以, 無法這樣給值
var t map[string]interface{}
t["xxx"] = "xxx"

宣告 slice struct ([]struct)

type Target struct {
    Topic   string `json:"topic"`
    Message string `json:"message"`
}
type Payload struct {
    Targets []Target `json:"targets"`
    From    string   `json:"from"`
}

var payload = Payload{
    Targets: []Target{
        Target{
            Topic:   "topic1",
            Message: "message1",
        },
        Target{
            Topic:   "topic2",
            Message: "message2",
        },
    },
    From: "api",
}

宣告空 slice, var VS :=

宣告出來為 nil, 長度 0 (建議)

var q []string

宣告出來為 [], 長度 0, 如果要 json encode 想避免 null 的話, 建議使用這個

q := []string{}

其他

var t *T = new(T)   // or  t := new(T)
var a uint64 = 22   // 等於 a := uint64(22)
a := 0x12           // 等於 18

const 對應 int (有點像 enum)

const (
    LevelCritical = iota    // 0
    LevelError
    LevelWarning
    LevelNotice
    LevelInfo               // 4
    LevelDebug
)

指標

  • & : 對變數取址
  • * : 對指針取值, 將指標取回一般變數

Example :

package main
import "fmt"
func main() {
    var a int = 1
    var b *int = &a
    var c **int = &b
    var x int = *b
    fmt.Println("a = ",a)                           // 1
    fmt.Println("&a = ",&a)                         // &a =  0xf840037100
    fmt.Println("*&a = ",*&a)                       // *&a =  1
    fmt.Println("b = ",b)                           // b =  0xf840037100
    fmt.Println("&b = ",&b)                         // &b =  0xf840037108
    fmt.Println("*&b = ",*&b)                       // *&b =  0xf840037100
    fmt.Println("*b = ",*b)                         // *b =  1
    fmt.Println("c = ",c)                           // c =  0xf840037108
    fmt.Println("*c = ",*c)                         // *c =  0xf840037100
    fmt.Println("&c = ",&c)                         // &c =  0xf840037110
    fmt.Println("*&c = ",*&c)                       // *&c =  0xf840037108
    fmt.Println("**c = ",**c)                       // **c =  1
    fmt.Println("***&*&*&*&c = ",***&*&*&*&*&c)     // ***&*&*&*&c =  1
    fmt.Println("x = ",x)                           // x =  1
}

將指標轉成實體, 讓 code 更乾淨

func ArgsToJob(Args *map[string]interface{}) Job {
    args := (map[string]interface{})(*Args)
    job := Job{
        ID:          args["ID"].(string),
    }
    return job
}

將 interface{} 轉成 map[string]interface{}

a := (map[string]interface{})(args[0].(interface{}).(map[string]interface{}))

什麼時候用指標?

參考 code :

type User struct {
  Name string
}

func main() {
  u := &User{Name: "Leto"}
  println(u.Name)
  Modify(u)
  println(u.Name)
}

func Modify(u *User) {
  u = &User{Name: "Paul"}
}

如果是比較大的結構,在宣告時可以用指標以節省記憶體, 但高階語言慢慢不用指標了,所以其實也可以不用特別宣告用指標, 用結構 call function 用結構傳遞,傳遞參數給 function 的話則不需要特別用指標,

指標行為

code :

func main() {
    a := QQ{}
    b := &a
    c := *b

    fmt.Printf("a: %p\n", &a)
    fmt.Printf("b: %p (address same as a) \n", b)
    fmt.Printf("c: %p (different address from a, b)\n", &c)

    d := xx()
    fmt.Printf("d: %p (different address from x)\n", &d)

    e := zz()
    fmt.Printf("e: %p (different address from z)\n", &e)
}

func xx() QQ {
    x := QQ{}
    fmt.Printf("x: %p (return instance)\n", &x)
    return x
}

func zz() *QQ {
    z := QQ{}
    fmt.Printf("z: %p (return pointer)\n", &z)
    return &z
}

如果 struct 定義沒有欄位, 結果 :

type QQ struct {}

a: 0x1127a88
b: 0x1127a88 (address same as a)
c: 0x1127a88 (different address from a, b)
x: 0x1127a88 (return instance)
d: 0x1127a88 (different address from x)
z: 0x1127a88 (return pointer)
e: 0xc42000c030 (different address from z)

如果 struct 定義有欄位, 結果 :

type QQ struct {
    Name string
}

a: 0xc42000e290
b: 0xc42000e290 (address same as a)
c: 0xc42000e2a0 (different address from a, b)
x: 0xc42000e2e0 (return instance)
d: 0xc42000e2d0 (different address from x)
z: 0xc42000e2f0 (return pointer)
e: 0xc42000c030 (different address from z)

關於回傳 instance or pointer 的記憶體 : call func 拿到的都是新的記憶體, 不管 func 裡面回傳的是不是指標

swtich

map + switch

m := map[string]int{"foo":1}
f := func(key string) bool { _, ok := m[key]; return ok }
switch {
    case f(key):
        // whatever

or

switch category {
case
    "auto",
    "news",
    "sport",
    "music":
    return true
}

斷言 (type assertion)

分辨型別

var anything interface{} = "string"
switch v := anything.(type) {
case string:
    fmt.Println(v)
case int32, int64:
    fmt.Println(v)
case interface{}:
    fmt.Println(v)
default:
    fmt.Println("unknown")
}

已知道是什麼型別

value, ok := a.(string)
if !ok {
    fmt.Println("It's not ok for type string")
    return
}

// or
if str, ok := a.(string); ok {

轉型

int to int64

i64 := int64(23)

int to float64

i := 5;
f := float64(i)

int to string

s := strconv.Itoa(123)

int64 to string

s := strconv.FormatInt(int64(5), 10)

int64 to float64

float64(1)

int64 to uint64

u, err := strconv.ParseUint(strconv.FormatInt(int64(123), 10), 10, 64)

float64 to string

s64 := strconv.FormatFloat(v, 'E', -1, 64)

float64 to int64

var f float64 = 55.3
i = int64(f)

float64 to uint

uint(user["age"].(float64))

string to byte

dd := "dcf"
fmt.Println([]byte(dd))

result : [100 99 102]

string to int

v, err = strconv.Atoi(s)

string to int64

v, err := strconv.ParseInt(s, 10, 64)

string to uint32/uint64

v := "42"
if s, err := strconv.ParseUint(v, 10, 32); err == nil {
    fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseUint(v, 10, 64); err == nil {
    fmt.Printf("%T, %v\n", s, s)
}

string to float64

v, err := strconv.ParseFloat("55.74", 64)

*string to string

value := *pointer_str

byte to string

s := string(byteArray)
s := string(byteArray[:])

array to slice

x[:]

指標(pointer)轉實體

*f is msg := sqs.Message(*m)

(*f)["cc"]
or
(map[string]interface{})(*f)["cc"]
or
msg := sqs.Message(*m)

interface{} to int

a := job["retryTimes"].(int)

[]interface{} 轉成 interface{}

interface{}([]interface{}{reactor, sensor})

[]interface{} to []int ref : InterfaceSlice

var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
    interfaceSlice[i] = d  // 或用 append 的方式
}

interface{} conver to bytes.Buffer

switch v := any.(type) {
case bytes.Buffer:          // return as is
    return v.String()       // Here v is of type bytes.Buffer
}

interfacer{} (Server) to (Server)

type Server struct {
    Name string
}

var v interface{}
if v == nil {
    v = &Server{Name: "xx"}
}
fmt.Println(reflect.TypeOf(v))      // *main.Server
s := v.(*Server)
fmt.Println(s.Name)                 // xx

map[string]interface{} 變成陣列 []interface{}

[]interface{}{reactor, sensor}

struct to another struct

type A struct{ Name string }
type B struct{ Name string }
a := A{"aa"}
b := B(a)
b.Name = "bb"
fmt.Println(a)
fmt.Println(b)

bytes to io.Reader

bytes.NewReader(b)

strings to io.Reader

strings.NewReader(s)

file to bytes

b, err := ioutil.ReadFile("/tmp/ff.tmp")

file to io.Writer

file, err = os.Open("/tmp/ff.tmp")
defer file.Close()

loop

Infinite

for {

}

Range, like other languages

for i:=1; i<=5; i++ {

}

Until the specific time

for time.Now().Unix() < 1481089195 {
    time.Sleep(1 * time.Second)
}

slice

slice結構

結構

     []byte
     ---------
ptr  | *elem |
     ---------
len  |  int  |
     ---------
cap  |  int  |
     ---------

a slice with five elements

s := make([]byte, 5)  // 指定 len, 不指定 cap

     []byte
     ---------
ptr  |       |  -> [5] bytes | 0 | 0 | 0 | 0 | 0 |
     ---------
len  |  5    |
     ---------
cap  |  5    |
     ---------

nil slice

var s []byte

     []byte
     ---------
ptr  |  nil  |
     ---------
len  |   0   |
     ---------
cap  |   0   |
     ---------

基本操作

append item

list = append(list, item)

append slice

list = append(list, list2...)

其他

重新切一個 slice, 新 slice 會使用原本 slice 的底層, 改成使用 copy 才會是一個新的 slice

func main() {
    data := get()
    fmt.Println(len(data), cap(data), &data[0])
}

func get() []byte {
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0])
    return raw[:3]
}
// 10000 10000 0xc42005e000
// 3 10000 0xc42005e000     (記憶體位置一樣)

func get() (res []byte) {
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0])
    res = make([]byte, 3)
    copy(res, raw[:3])
    return
}
// 10000 10000 0xc42005e000
// 3 3 0xc42000e280

傳遞 slice 是傳遞記憶體位置

感謝 chris:

  • 記得 slice map 的變數 只是一個參照
  • slice 跟 map 的變數 參照的 值都是隔開的
  • 但是底層運作是使用 指標
  • 裡面真正的值 是存在記憶體
  • 關鍵字: 淺拷貝/深拷貝

example

func main() {
    d := []string{"xxx"}
    fmt.Println("original:", d)
    nonPointer(d)
    fmt.Println("non-pointer:", d)
    change(d)
    fmt.Println("change:", d)
    pointer(&d)
    fmt.Println("pointer:", d)
}

func nonPointer(d []string) {
    d = append(d, "vvvv")
}

func change(d []string) {
    d[0] = "ccc"                // 可修改到外部
}

func pointer(d *[]string) {
    *d = append(*d, "vvvv")
}

結果

original: [xxx]
non-pointer: [xxx]
change: [ccc]
pointer: [ccc vvvv]

宣告獨立乾淨的 slice

不論是參數傳遞 (call by value) 還是 assign 給一個新的變數, *elem (pointer) 都會指向同一個, 而改其中一個也會改動到另一個

如果沒有 append 新的 item, 只改變原本 slice 長度內的值 e.g. s[2] = 3 (如果s總長度是5), 都會一直是共用指標

如果要打破這個規則, 就將其中一個 append 新的 item 那麼 *elem (pointer) 就會是不同的

如果要將 slice 指給一個新的變數, 但又不想要將原本的 *elem (pointer) 參照到原本的變數, 除了用 make 也可以 new 全新的再用 append 給值

code

s := []int{1, 2, 3}

// Old *elem
s1 := s

// New *elem
s2 := s
s2 = append(s2, 4)

// New *elem
s3 := []int{}
s3 = append(s3, s...)

// New *elem
s4 := make([]int, 0)
s4 = append(s4, s...)

s[1] = 999

fmt.Println(s)      // [1 999 3]
fmt.Println(s1)     // [1 999 3]
fmt.Println(s2)     // [1 2 3 4]
fmt.Println(s3)     // [1 2 3]
fmt.Println(s4)     // [1 2 3]

array vs slice

差別主要是 array 是有固定長度, slice 沒有

code:

// array
var List = [2]int{1, 2}  // or [...]int{1,2}
copyList := List
copyList[1] = 4
fmt.Printf("new=%v, old=%v\n", copyList, List)

// slice
var oldSlice = []int{1, 2, 3, 4}
newSlice := oldSlice
newSlice[3] = 10
newSlice = append(newSlice, 100)
fmt.Printf("new=%v, old=%v\n", newSlice, oldSlice)

result:

new=[1 4], old=[1 2]
new=[1 2 3 10 100], old=[1 2 3 10]

array 是直接操作記憶體位置

map misc

傳入接收 actionData map[string]interface{} 參數的 func

send_var(jobData["ActionData"].(map[string]interface{}))

func send_var(actionData []interface{}) (err error) {
    fmt.Println(actionData[0].(map[string]interface{})["Did"])

[]map[string]interface{} 傳入型態接受 []interface{} 的 func

test(&map[string]interface{})
...
func test(rec *[]interface{})

用指標方式傳入可避免型態轉換發生的問題

map[string]interface{} 一直輸出它的 index 順序不一定會一樣

qq := map[string]interface{}{
    "0": "111111",
    "1": "xxxxxxxx",
}

[]interface{} 一直輸出它的 index 順序會一樣

用 mi 定義 type, 讓 code 更乾淨

type mi map[string]interface{}
res["Envelope"].(interface{}).(mi)["Body"].(interface{}).(mi)[actionData["Name"].(string)+"Response"].(interface{})

assignment to entry in nil map : 會發生此原因是你在賦值給 map 時沒有初始化

var d []map[string]interface{}

(Wrong)
    var f map[string][]map[string]interface{}
    f["ss"] = d

(Correct)
    f := map[string][]map[string]interface{}{}
    f["ss"] = d

(Correct)
    var f map[string][]map[string]interface{}
    f = make(map[string][]map[string]interface{})
    f["ss"] = d

map 是否為空

if len(map) == 0 {
    ....
}

判斷 key 是否存在, 如果不存在不會造成 error

a := map[string]interface{}{
    "a": "A",
}

if t1, matched := a["a"]; matched {
    fmt.Println(t1.(string))
}

if t2 := a["a"]; t2 != nil {
    fmt.Println(t2.(string))
}

判斷 key 及型態

a := map[string]interface{}{"fff": "xx"}
ff, ok := a["fff"].(int)   // 即使來源跟對象的型態不一樣, 不會造成 panic
if !ok {
    fmt.Println("no")
    // return
}
fmt.Println(ff)

// result
no
0

Slice Tricks

判斷 slice key 是否存在

a := []string{"zero", "one", "two"}
fmt.Println(a, len(a))
x, v := 3, "nothing"
if len(a)-1 >= x  {
    v = a[x]
}
fmt.Printf("%s", v)

type *map[string]interface {} does not support indexing

func tt(dd *map[string]interface{}) {
    (*dd)["qq"] = "qqqq"
}

傳遞 map 是傳址非傳值

func main() {
    dd := map[string]interface{}{
        "ff": "ffff",
    }
    fmt.Printf("original, mem: %p val: %v\n", &dd, dd)
    nonPointer(dd)
    fmt.Printf("non-pointer, val: %v\n", dd)
    change(dd)
    fmt.Printf("change, val: %v\n", dd)
    pointer(&dd)
    fmt.Printf("pointer, val: %v\n", dd)
    newMap(dd)
    fmt.Printf("newMap, val: %v\n", dd)
    makeMap(dd)
    fmt.Printf("makeMap, val: %v\n", dd)
}

// 會修改到外部
func nonPointer(dd map[string]interface{}) {
    fmt.Printf("non-pointer, mem: %p\n", &dd)
    dd["dd"] = "ddd"
}

// 會修改到外部
func change(dd map[string]interface{}) {
    fmt.Printf("non-pointer, mem: %p\n", &dd)
    dd["ff"] = "cccc"
}

// 會修改到外部
func pointer(dd *map[string]interface{}) {
    fmt.Printf("pointer, mem: %p\n", &dd)
    (*dd)["qq"] = "qqqq"
}

// 即使 qq 裡, new 一個新變數 ff, 並將 dd 的值給 ff, 修改仍會改到外面傳進來的 map
func newMap(dd map[string]interface{}) {
    ff := dd
    ff["ff"] = "zzzz"
}

// 必須要用 make new 一個實體, 才不會參照到原本的記憶體位址
func makeMap(dd map[string]interface{}) {
    cc := make(map[string]interface{})
    for k, v := range dd {
        cc[k] = v
    }
    cc["bbb"] = "bbb"
}

結果

original, mem: 0xc42000c028 val: map[ff:ffff]
non-pointer, mem: 0xc42000c038
non-pointer, val: map[ff:ffff dd:ddd]
non-pointer, mem: 0xc42000c040
change, val: map[ff:cccc dd:ddd]
pointer, mem: 0xc42000c048
pointer, val: map[ff:cccc dd:ddd qq:qqqq]
newMap, val: map[dd:ddd qq:qqqq ff:zzzz]
makeMap, val: map[dd:ddd qq:qqqq ff:zzzz]

func params

傳入/接收 map pointer

error: (type *map[string]interface {} does not support indexing)

solution:

func main() {
    a := map[string]interface{}{
        "xxx": "XXX",
    }

    qq(&a)
}

func qq(tmp *map[string]interface{}) {
    fmt.Println((*tmp)["xxx"])
}

傳入/接收 map pointer

var args = map[string]interface{}{
    "A": "1",
    "B": "2",
}
foo(&args)
func foo(args *map[string]interface{}) {
    options := (map[string]interface{})(*args)
    fmt.Println(options["A"])
}

foo(args)
func foo(name string, ...args map[string]interface{}) {
    options := (map[string]interface{})(args[0])
    fmt.Println(options["A"])
}

傳入 Optional 參數

T1("A", "B")
func T1(str ...string) {
    fmt.Println(str[0]) // A
}

T2([]string{"A", "B"})
func T2(str ...[]string) {
    fmt.Println(str[0]) // [A B]
}

map

test("test")
test("test", map[string]interface{}{"dd": "DD", "cc": "CC"})

func test(required string, optional ...map[string]interface{}) {
    fmt.Println(required)
    fmt.Println(len(optional))
    if len(optional) > 0 {
        if val, existed := optional[0]["dd"]; existed {
            fmt.Println(val)
        }
    }
}

將 slice params 傳入 args…

func echo(strings ...string) {
    for _, s := range strings {
        fmt.Println(s)
    }
}

func main() {
    strings := []string{"a", "b", "c"}
    echo(strings...) // Treat input to function as variadic
}

interface & struct

[struct] 成員大小寫

跟 func 一樣,大寫代表 public,小寫是 private

[struct] map

type AWS struct {
   SQS  map[string]*aws_sqs.SQS
}

var qq AWS
qq.SQS = make(map[string]*aws_sqs.SQS)
qq.SQS[xx] = &ws_sqs.SQS{ ... }

[struct] function pointer

type Job struct {
    Done func() error
}

var job Job
job.Done = done

func done() error {

}

[struct] 定義 + 賦值

resp := Music{
    Genre: struct {
        Country string
        Rock    string
    }{
        Country: "Taylor Swift",
        Rock:    "Aimee",
    },
}

Assign a struct to an interface

type Interface interface{}

type Struct struct{}

func main() {
        var ps *Struct
        var pi *Interface
        pi = new(Interface)
        *pi = ps

        _, _ = pi, ps
}

關於 interface 及 struct 的用法

package main

import "fmt"

# 用 interface 定義一個抽象層,只負責說這個類別有什麼動作
type PeopleAction interface {
    AnimalAction
    Stand()
}

# 本身也可以被繼承
type AnimalAction interface {
    Run()
    Eat()
}

# 定義資料結構
type Animal struct {
    Name string
}

func (animal *Animal) Run() {
    fmt.Println(animal.Name + " is running")
}

func (animal *Animal) Eat() {
    fmt.Println(animal.Name + " is eatting")
}

func (animal *Animal) Stand() {
    fmt.Println(animal.Name + " is standing")
}

func main() {
    var jex PeopleAction = &Animal{"Jex"}
    var bob AnimalAction = &Animal{"Bob"}
    jex.Run()               // Jex is running
    jex.Eat()               // Jex is eatting
    jex.Stand()             // Jex is standing
    bob.Run()               // Bob is running
    bob.Eat()               // Bob is eatting
    // bob.Stand()          // bob.Stand()  Error : bob.Stand undefined (type AnimalAction has no field or method Stand)
}

Type T satisfies interface I defined in the first snippet. Values of type T can be f.ex. passed to any function accepting I as a parameter

type I interface {
    M() string
}
type T struct {
    name string
}
func (t T) M() string {
    return t.name
}
func Hello(i I) {
    fmt.Printf("Hi, my name is %s\n", i.M())
}
func main() {
    Hello(T{name: "Michał"}) // "Hi, my name is Michał"
}

single type can implement many interfaces

type I1 interface {
    M1()
}
type I2 interface {
    M2()
}
type T struct{}
func (T) M1() { fmt.Println("T.M1") }
func (T) M2() { fmt.Println("T.M2") }
func f1(i I1) { i.M1() }
func f2(i I2) { i.M2() }
func main() {
    t := T{}
    f1(t) // "T.M1"
    f2(t) // "T.M2"
}

same interface, different structs, call their own function with same name

type I interface {
    M() (string, error)
}
type T1 struct{ Name string }
type T2 struct{ Name string }

func main() {
    // Different structs call its own func
    t1, _ := M(&T1{Name: "T1"})
    fmt.Println(t1)
    t2, _ := M(&T2{Name: "T2"})
    fmt.Println(t2)

    // Declare an interface and assign different sturcts to it
    var Q I
    v1 := &T1{Name: "T3"}
    Q = v1
    t3, _ := Q.M()
    fmt.Println(t3)

    v2 := &T2{Name: "T4"}
    Q = v2
    t4, _ := Q.M()
    fmt.Println(t4)
}

func M(i I) (string, error)      { return i.M() }
func (t *T1) M() (string, error) { return t.Name, nil }
func (t *T2) M() (string, error) { return t.Name, nil }

same interface, different structs, call same func

No, you can’t!

struct + json

json.Marshal(struct {
        Data     *MyStruct   `json:"data,omitempty"`
        Status   string      `json:"status,omitempty"`
        Reason   string      `json:"reason,omitempty"`
    }{
        Data:   &r.Data,
        Status: r.Status,
        Reason: r.Reason,
    })
}

Assign func

type Job struct {
    ID string
    Do func(*Job)
}

func main() {
    var j1 = &Job{ID: "J01", Do: do}
    var j2 = &Job{ID: "J02", Do: do}
    j1.Do(j1)
    j2.Do(j2)
}

func do(j *Job) {
    fmt.Println("Job ID:", j.ID)
}

巢狀

type Resp struct {
    UserID struct {
    } `json:"user_id"`
    Data struct {
        Info struct {
            Name        string   `json:"name"`
            Friends     []Friend `json:"friends"`
        } `json:"info"`
    } `json:"data"`
}

輸出

"user_id": {},
"data": {
    "info": {
        "name": "XXX",
        "friends": [
            { },
            { },
        ],
    },
},

JSON tag 補充

// 接收跟輸出都會忽略
Field int `json:"-"`

// Field appears in JSON as key "myName" and the field is omitted from the object if its value is empty, as defined above.
Field int `json:"myName,omitempty"`

// Field appears in JSON as key "Field" (the default), but the field is skipped if empty.
Field int `json:",omitempty"`

type Ticket struct {
    Places []Place `json:"places"`  // json 傳過來時的 places 也要是陣列
    Name string `json:"name"`       // 單純接收字串
    UID string                      // 注意! 即使沒設定 tag, 如果傳進來是 "uid": "123" 這樣也會 map 得到
    FriendID string                 // 如果傳進來是 "friend_id": "123" 這樣 map 不到
}

Nested struct 即使有設 omitempty 也 json 仍會輸出 "field": {}, 解決方法是指定 pointerstruct

type A struct { Val  L2 `json:"val,omitempty"` }
type B struct { Val *L2 `json:"val,omitempty"` }        // pointer 是關鍵
type L2 struct { Val string `json:"val,omitempty"` }

json.Marshal 輸出 :

a := &A{Val: L2{Val: ""}}   // {"val":{}}
a := &A{}                   // {"val":{}}
a := &B{}                   // {}

避免空的 slice json marshal 後為 null

var users []map[string]interface{}
if users == nil {
    users = make([]map[string]interface{}, 0)
}

import

運作流程

當 import A 時, 會先執行 A 的 init(), 但如果 A 裡面還有 import B, 則會優先執行 B 的 init(),

當 package 的 init() 都執行完後, 才會執行 main 的 init(), 最後才會執行 main()

import 的幾種類型

1) 正常引入

import(
    "fmt"
)

這是我們平常最常引入的方法

2) 省略前綴的 package name

import (
    . "fmt"
)

ex : fmt.Println("hello world") 可以省略寫成 Println("hello world")

3) Alias

import (
    f "fmt"
)

ex : f.Println("hello world")

4) 不引用 package 內部函數

import (
    _ "fmt"
)

當引入這個 package 只會執行它的 init()

套件管理

dep (官方)

  • dep init: 第一次才會用到, 會產生 Gopkg.toml, Gopkg.lock (不要理它), vendor (安裝好 package)
  • dep ensure: install, Gopkg.toml 裡面設定的 package. 另外如果要新增沒有 tag 的版本到 Gopkg.toml 也是用這個指令, 後面不用指定套件, 它會自動去掃程式
  • dep ensure -update: 升級所有套件到最新的
  • dep ensure -add github/…/../errors@{tag or commit id}: 新增套到 Gopkg.toml / Gopkg.lock and vendor
  • dep status: 顯示你目前的版本跟最新的版本
  • 升級某個套件版本要改 Gopkg.toml, 再執行 dep ensure

Vendor

govendor,用起來挺直覺的,如果使用過 npm 或 gem 的經驗,它的概念是一樣的

govendor cheat sheet

第一次使用

govendor init               # 會產生 vendor/vendor.json, 但裡面是空的
govendor add +external      # 幫你把目前使用到 GOPATH 套件版本填到 vendor.json,也會產生 github.com, etc. 第三方套件

第 N 次使用, 更新 govendor.json

govendor remove +v          # 清空 `vendor.json` 及刪除 `github.com`, .. etc.
govendor add +external      # 再重加有使用到的套件

依據 vendor/vendor.json 使用到的套件從 local 的 GOPATH 加到 vendor/, 如果 local 沒有要先下 go get

govendor sync

如果 github 某個套件更新了, 想更新 vendor 這個套件

govendor fetch golang.org/x/net/context
Error: Remotes failed for

如果有個 local repo (private repo) 加不進 vendor/

$ govendor sync
Error: Remotes failed for:
        Failed for "relog" (failed to ping remote repo): unrecognized import path "relog"

直接 add 那個 local repo

govendor add relog
升級某一套件版本
govendor fetch github.com/satori/go.uuid

(不要採用, 僅保留)

  1. 先從 govendor 移除掉 govendor remove github.com/parnurzeal/gorequest
  2. 移除 local 的 github.com/parnurzeal/gorequest
  3. go get github.com/parnurzeal/gorequest
  4. govendor add +external 更新到 vendor.json
不管怎麼重 build, code 一直是舊的

有可能你將目前專案加到 vendor/ 下了, 把它刪掉, 再重編就可以了

Panic and Recover

攔截並印出 error

如果不想因為程式發生 index out of range 或由 panic 擲出的 error 導致整個程序強制中止的話可以用 recover 來攔截, 另外有時候程式的 error message 不夠明確時, 用 recover 有時候可以得到較明確的訊息

defer func() {
    if e := recover(); e != nil {
        // e is the interface{} typed-value we passed to panic()
        fmt.Println("Whoops: ", e) // Prints "Whoops: boom!"
    }
}()
var qq []string
fmt.Println(qq[0]) // 或直接用 panic("boom!")
fmt.Println("This will never be called")

recover() 要寫在最前面,否則會無法攔截到

將 stack 裡的東西印出來

func main() {
    defer func() {
        if e := recover(); e != nil {
            for i := 1; ; i++ {
                _, file, line, ok := runtime.Caller(i)
                if !ok {
                    break
                }
                log.Println(fmt.Sprintf("%s:%d", file, line))
            }
        }
    }()
    qq() // :21
    log.Println("Done!")
}

func qq() {
    ff() // :26
}

func ff() {
    panic("ff") // :30
}

Result:

2017/09/20 19:16:22 /usr/local/go/src/runtime/asm_amd64.s:514
2017/09/20 19:16:22 /usr/local/go/src/runtime/panic.go:489
2017/09/20 19:16:22 /tmp/qq.go:30
2017/09/20 19:16:22 /tmp/qq.go:26
2017/09/20 19:16:22 /tmp/qq.go:21
2017/09/20 19:16:22 /usr/local/go/src/runtime/proc.go:185
2017/09/20 19:16:22 /usr/local/go/src/runtime/asm_amd64.s:2197

其他

Pointer guildeline

  • Slices, maps and channels are reference types that do not require the extra indirection of an allocation with new
  • If the receiver is large, a big struct for instance, it will be much cheaper to use a pointer receiver.
  • If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used.
  • For types such as basic types, slices, and small structs, a value receiver is very cheap so unless the semantics of the method requires a pointer, a value receiver is efficient and clear.

盡量減少在其他子 package 用 var 定義全域變數

多使用全域來定義 struct, func, const,不得以才用 var 定義全域變數;如果有共用的 func 都會用到共同的變數,就用 struct 定義,否則容易產生 Memory Leak 的問題

go env

$ go env
GOARCH="amd64"
GOBIN=""
GOCHAR="6"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH=""
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
CC="gcc"
GOGCCFLAGS="-g -O2 -fPIC -m64 -pthread -fno-common"
CGO_ENABLED="1"

show variable type

import "reflect"
fmt.Println(reflect.TypeOf(tst))

判斷 interface{} 是否是 slice

if reflect.TypeOf(test["slice"]).Kind() == reflect.Slice {

}

debug

如同 PHP 的 print_r(), var_dump(), var_export(), 如果型態是 map 它也會把 key value 印出來

fmt.Printf("%v", whatever)

用上面的不會 struct 的欄位名稱,如果印出 struct 的欄位及值要用 :

fmt.Printf("%+v", whatever)

string literals are character sequences

string("Hello"[1])                  // ASCII only       e
string([]rune("Hello, 世界")[1])    // UTF-8            e
string([]rune("Hello, 世界")[8])    // UTF-8            界

指令

  • syntax checking : gofmt -e xx.go
  • 透過傳遞參數的方式改變 main.go 裡面 var 變數 : go build -ldflags '-X main.BUILD_ID=1'
  • go build 時太多 error 時你只會看到幾行然候其他就被省略成 too many errors, 改用 go build -gcflags="-e" 就可印出全部的 error

在 if 裡面 new 參數無法傳遞到外面

裡面有 new

var a bool
if true {
    a := true
    fmt.Println(a)
}
fmt.Println(a)

true
false

裡面沒 new

var a bool
if true {
    a = true
    fmt.Println(a)
}
fmt.Println(a)

true
true

MySQL NULL

null

string sql.NullString
int64 sql.NullInt64
float64 sql.NullFloat64

time 加上 tag

time.Time `sql:"default:null"`

Golang 安裝

Linux / Mac

(最後更新: 2016-03-28)

[1] download golang source

Linux : https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz

Mac : https://storage.googleapis.com/golang/go1.6.darwin-amd64.tar.gz

[2] Install

Linux : sudo tar -C /usr/local -xzf go1.6.darwin-amd64.tar.gz

Mac : sudo tar -C /usr/local -xzf go1.6.darwin-amd64.tar.gz

如果要重安裝直接 remove /usr/local/go 再執行上面指令就可以了

[3] Create essential folders

在家目錄下建立 mygo, 並且將 GOPATH 指向這裡, 這是 golang 一個很重要的設定, 可以將外部套件與原始程式分離, 你還必須在 mygo 下建立 3 個資料夾, src、pkg 及 bin

  • src 是放置外部套件(go get github.com/xx/cc 會載至這裡)及開發程式的地方
  • pkg 是程式編譯時產生的 .a 檔, 一般開發可以不用理會它
  • bin 是程式編譯後產生的 binary 執行檔

.bashrc(linux) / .bash_profile(mac) :

export GOROOT=/usr/local/go
export GOPATH=$HOME/mygo
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

Linux exec : source ~/.bashrc

Mac exec : source ~/.bash_profile

解壓縮完且 .bashrc 設定好以上就算安裝完成了, 可用 go version 測試是否安裝成功

Upgrade go version

  1. 移除 /usr/local/go
  2. 將官網載下來的 go 解壓縮到 /usr/local
  3. Done!
Mac upgrade go version 後還是顯示舊的版本

可能是曾經有用過 brew 安裝過 go, 即使你更新完 /usr/local/go version 還是顯示舊版的

使用 which go 可以發現它的指令是指到 /usr/local/bin/go 而它又是 link 到 /usr/local/Cellar/go/{舊版號}/bin/go

建議直接執行 brew uninstall go, 它會幫你把 /usr/local/Cellar/go 移除掉 (Uninstalling /usr/local/Cellar/go/1.7... (6,435 files, 250.6MB))

go version 後的版本就會是正常了

[註] 編譯

在本機編譯 :

go build

假如要編譯給 windows 的電腦執行 :

# 32 位元
GOOS=windows GOARCH=386 go build -o test.exe

# 64 位元
GOOS=windows GOARCH=amd64 go build -o test.exe

假如你的 project 有引入 C 程式則需要使用 CGO 來編譯 :

CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -o test.exe

windows :

go 下載頁面 按照電腦的 OS 選擇你要下載的, 下載完直接安裝

安裝好 GO 後, 還需要 GCC 編譯環境, 因為 Mac 的 Xcode 本身就有 GCC, windows 沒有, 所以選擇了 mingw 這個輕量型的 GCC

參考安裝 MinGW


設定 GOPATH, 這是放一些你自己或 go get from github 存放的地方

  1. c:\Go 下建立 mygo 資料夾, 並且在 mygo 下建立三個資料夾 src, pkg, bin
  2. 設定環境變數

GOROOT : C:\Go GOPATH : C:\Go\mygo PATH : 在最後面加上 ;%GOROOT%\bin;%GOPATH%\bin;C:\Program Files (x86)\Git\bin

除了加上 GOROOT\bin、GOPATH\bin, 也可視需求加上 git bin 的路徑

重新啟動 cmd 使變數生效


編譯本機系統 GO 的編譯環境, 直接以滑鼠點擊執行 C:\Go\src\all.bat

跑完後, 建立自定義檔名的 .bat 檔, 如 C:\Go\src\jex.bat :

set CGO_ENABLED=0
set GOARCH=386
set GOOS=windows
call make.bat --no-clean
pause

雙擊它執行

然候在 C:\Go\bin 建立自定義檔名的 .bat 檔, 如 exec.bat :

set GOARCH=386
set GOOS=windows
go build

然候新增一個 hello.go 的檔案測試

package main
import (
    "fmt"
    "bufio"
    "os"
)
func main() {
    fmt.Printf("Hello, world\n")
    bufio.NewReader(os.Stdin).ReadBytes('\n') // 在 windows 結束後讓它停留在最後一行, 不要直接關掉
}

雙擊 exec.bat, 應該會在 C:\Go\bin 下出現 bin.exe 這個可執行檔

或者是在 cmd 到 c:\Go\bin, go build hello.go

執行它如果出現 Hello, world 恭喜你~設定完成!

Hello World!

hello.go :

package main

import "fmt"

func main() {
    fmt.Printf("Hello, world\n")
}

Package fmt implements formatted I/O with functions analogous to C’s printf and scanf. The format ‘verbs’ are derived from C’s but are simpler. Read detail

execute :

$ go run hello.go
Hello, world

編譯成執行檔

除了使用 go build 自動幫我們生成好執行檔, 我們也可以手動編譯及連結

ubuntu 手動編譯

/usr/lib/go/pkg/tool/linux_amd64/6g -o hello.6 hello.go
/usr/lib/go/pkg/tool/linux_amd64/6l -o hello hello.6
./hello

Mac 手動編譯

/usr/local/go/pkg/tool/darwin_amd64/6g -o hello.6 hello.go
/usr/local/go/pkg/tool/darwin_amd64/6l -o hello hello.6
./hello

編譯器

5g : ARM 6g : AMD 64 (x86-64) 8g : Intel 386 (X86)

386 : x86 架構的 32 位元 cpu 指令集, 簡稱 i386 AMD 64 : AMD 擴展 x86 的 64 位元指令集

[20140309] golang編譯器 :

  • 6g(X86-64)、8g(X86)、5g(arm) (及其支援工具,總稱為 gc)用 C 寫成,使用 yacc/Bison 作為解析器。Gccgo 是一個使用標準GCC作為後端的Go編譯器
  • 6g 是amd64的go編譯器,它生成的是 .6 檔案
  • 而 386 使用 8g 命令,它生成的一般是 .8 格式的檔案
  • 5g 是用於 arm 的 cpu,同理 amd64 用 6l,386 用 8l, arm 用 5l 的鏈接器!)

使用 vim 開發

必用套件vim-go

Golang - Native Package

fmt

輸出固定長度

fmt.Printf("ID : %-10s", id)    // 十個字元的長度, 向左對齊
fmt.Printf("ID : %10s", id)     // 十個字元的長度, 向右對齊
fmt.Printf("ID : %.10s", id)    // 印出頭十個字元
fmt.Printf("%q", []string{"a","b"}) // 印 slice `["a" "b"]`

前面補 0

fmt.Sprintf("%02d:%02d", 5, 3)  // 05:03

接收 command 輸入的值

fmt.Print("Enter a number: ")
var input float64
fmt.Scanf("%f", &input)

印出顏色

fmt.Println("\x1b[31;1mHello, World!\x1b[0m")

最簡單的方法, 不過有個缺點是它會是 string

fmt.Sprintf("%.7f", 123.123456789)                              // 123.1234568

不改變本身的型態

val := 123.123456789
pow10_n := math.Pow10(7)                                        // 1e7 = 10000000
d := math.Trunc(val*pow10_n) / pow10_n                          // 123.1234567

如果已知位數, 可簡化

math.Trunc(123.1234567*1e5) / 1e5                               // 123.12345

不改變本身的型態且4捨5入

val := 123.123456789
pow10_n := math.Pow10(7)
d := math.Trunc((val+0.5/pow10_n)*pow10_n) / pow10_n            // 123.1234568

log

儲存成 file

// open a file
f, err := os.OpenFile("dev.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
    log.Printf("Create log file error: %v", err)
}

// don't forget to close it
defer f.Close()

// assign it to the standard logger
log.SetOutput(f)

// 以下的 log 都會進到 log file 裡
log.Println("test")
log.Println("testr2")
log.Println("testr3")

將 log 內容暫存在 buffer

var buf bytes.Buffer
logger := log.New(&buf, "logger: ", log.Lshortfile)
logger.Print("Hello, log file!")

fmt.Print(&buf)

攔截 log 的內容

var buf bytes.Buffer
logger := log.New(&buf, "", log.Lshortfile)
go func() {
    for {
        if buf.Len() > 0 {
            fmt.Print(&buf)
            buf.Reset()
        }
    }
}()
fmt.Println(0)
logger.Print("Hello, log file!1")
logger.Print("Hello, log file!2")
logger.Print("Hello, log file!3")

dd := make(chan int)
<-dd

寫到 syslog

// Configure logger to write to the syslog. You could do this in init(), too.
logwriter, e := syslog.New(syslog.LOG_NOTICE, "myprog")
if e == nil {
    log.SetOutput(logwriter)
}

// Now from anywhere else in your program, you can use this:
log.Print("Hello Logs!")

/var/log/system.log :

Nov  4 08:19:33 Jexde-MacBook-Pro myprog[90449]: 2016/11/04 08:19:33 Hello Logs!

net

取得本機上的 Mac

interfaces, err := net.Interfaces()
if err != nil {
    panic("Poor soul, here is what you got: " + err.Error())
}
for _, inter := range interfaces {
    fmt.Println(inter.Name, inter.HardwareAddr)
}

header / status / etc.

header

resp, _ := http.Get(url)
// &{200 OK 200 HTTP/1.1 1 1 map[Connection:[keep-alive] Server:[nginx] Accept-Ranges:[bytes] Content-Length:[994291] Date:[Sat, 25 Jan 2014 19:13:51 GMT] Etag:["4e28a21e-f2bf3"] Content-Type:[video/x-flv] Last-Modified:[Thu, 21 Jul 2011 22:03:10 GMT]] 0xf8400c2a00 994291 [] false map[] 0xf8400c0240}

Status

resp.Status             // 200 OK
resp.StatusCode         // 200

size

resp.Header.Get("Content-Length")
// 994291

body

source, _ := ioutil.ReadAll(resp.Body)
// source 為 131 239 216 100 96 184 2 221 171 162 131 49 17 33 17 39 152 176 194 18 19 62 40 124 93 230 48 58 9 (..略..)

len(source) 結果會和 header 的 Content-Length 一樣

net/url 解析 URL

s := "postgres://user:pass@host.com:5432/path?k=v#f"
u, err := url.Parse(s)
fmt.Println(u.Scheme)                   // postgres
fmt.Println(u.User)                     // user:pass
fmt.Println(u.User.Username())          // user
p, _ := u.User.Password()
fmt.Println(p)                          // pass
fmt.Println(u.Host)                     // host.com:5432
h := strings.Split(u.Host, ":")
fmt.Println(h[0])                       // host.com
fmt.Println(h[1])                       // 5432
fmt.Println(u.Path)                     // /path
fmt.Println(u.Fragment)                 // f
fmt.Println(u.RawQuery)                 // k=v
m, _ := url.ParseQuery(u.RawQuery)
fmt.Println(m)                          // map[k:[v]]
fmt.Println(m["k"][0])                  // v

u, _ := url.Parse("http://www.test.com/xxx.mp3?foo=bar&foo=baz#this_is_fragment")
fmt.Println("full uri:", u.String())  // full uri: http://www.test.com/xxx.mp3?foo=bar&foo=baz#this_is_fragment
fmt.Println("scheme:", u.Scheme)      // scheme: http
fmt.Println("opaque:", u.Opaque)      // opaque:
fmt.Println("Host:", u.Host)          // Host: www.test.com
fmt.Println("Path", u.Path)           // Path /xxx.mp3
fmt.Println("Fragment", u.Fragment)   // Fragment this_is_fragment
fmt.Println("RawQuery", u.RawQuery)   // RawQuery foo=bar&foo=baz
fmt.Printf("query: %#v\n", u.Query()) // query: url.Values{"foo":[]string{"bar", "baz"}}

url 組 query

var Url *url.URL
Url, err = url.Parse("https://" + hostname)
Url.Path += "/api/test"
url_p := url.Values{}
url_p.Add("a", 123)
url_p.Add("b", 456)
Url.RawQuery = url_p.Encode()

urlencode

params := url.Values{}
params.Add("p", p_64)
params.Add("iv", iv_64)
fmt.Println(params.Encode())

自組 post body 要 escape

Post body 規則像 url, 要 url encode, 否則 + 等等的符號會被當成是 space

body := fmt.Sprintf("p=%s&iv=%s", url.QueryEscape(p_64), url.QueryEscape(iv_64))

net/http Download file

out, err := os.Create("output.txt")
defer out.Close()
resp, err := http.Get("http://example.com/")
defer resp.Body.Close()
n, err := io.Copy(out, resp.Body)

只取得 response 的 Header

res, _ := http.Head(url)
maps := res.Header

它連完線馬上就會斷了,不需要手動關掉

Get filename from url path

import (
    "net/url"
    "path/filepath"
)

s := "https://talks.golang.org/2012/splash/appenginegophercolor.jpg"
u, _ := url.Parse(s)
fmt.Println(filepath.Base(u.Path))

GET method

import (
  "http"
  "io/ioutil"
  "os"
)

response, _, err := http.Get("http://golang.org/")
if err != nil {
    fmt.Printf("%s", err)
    os.Exit(1)
} else {
    defer response.Body.Close()
    contents, err := ioutil.ReadAll(response.Body)
    if err != nil {
        fmt.Printf("%s", err)
        os.Exit(1)
    }
    fmt.Printf("%s\n", string(contents))
}

ref : https://gist.github.com/ijt/950790

Get 另一種寫法,指定 header

client := http.Client{
  Timeout: time.Second * 5,
}
req, err := http.NewRequest("GET", "http://www.google.com/dd", nil)
req.Close = true        // Note !!  避免發生 POST EOF 問題
req.Header.Set("Content-Type", "text/plain")
req.Header.Add("Content-Type", "text/plain")
resp, err := client.Do(req)
if err != nil {
    return err.Error()
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
  err = errors.New("Server return non-200 status")
  return
}
contents, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(contents))
return

post :

http.NewRequest("GET", "http://www.google.com/dd", strings.NewReader("name=cjb"))

Post (form-data / json)

// New request
req, err := http.NewRequest("POST", c.URL, bytes.NewBuffer([]byte(c.PostData)))
req.Close = true
for k, v := range c.Header {
    req.Header.Set(k, v)
}

// 根據不同 Post type 加上對應的 header
switch strings.ToUpper(c.PostType) {
case "FORM":
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
case "JSON":
    req.Header.Set("Content-Type", "application/json")
}

// Send request
resp, err = client.Do(req)
if err != nil {
    return
}
defer resp.Body.Close()

Post (file)

// 模擬 form 的一個 選項, 現在是空的
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)

// 設定要上傳的檔案到 form 裡
fileWriter, err := bodyWriter.CreateFormFile(c.FileFieldName, c.FileFieldName)  // 一個是 file name, 一個是 field name, 都用 field name 就好了
if err != nil {
    err = errors.New("Failed to create form file")
    return
}

// 支援 file path 及 file bytes
var file *os.File
if c.FilePath != "" {
    // File path
    file, err = os.Open(c.FilePath)
    if err != nil {
        err = errors.New("Failed to open " + c.FilePath)
        return
    }
    defer file.Close()

    if _, err = io.Copy(fileWriter, file); err != nil {
        return
    }
} else {
    // Bytes
    r := bytes.NewReader(c.Bytes)
    if _, err = io.Copy(fileWriter, r); err != nil {
        return
    }
}

// 設定其他 form-data 的欄位跟值
// 它一定要在 bodyWriter.Close() 前, 因為這 close 完畢就是 boundary 的結尾, 有些在實作這功能時看到這個結尾就不會再繼續 parse 接下來 form-data 的欄位了 (Go Echo 上是這樣, 但 php 沒問題)
for key, val := range c.Params {
    if err = bodyWriter.WriteField(key, val); err != nil {
        return
    }
}

bodyWriter.Close()

// New request
req, err := http.NewRequest("POST", c.URL, bodyBuf)
// req.Close = true
for k, v := range c.Header {
    req.Header.Set(k, v)
}
req.Header.Set("Content-Type", bodyWriter.FormDataContentType()) // 取得上傳檔案的 Content-Type 的值, e.g. multipart/form-data; boundary=001e5fc50cf83d32c170bfef235709ba9016b0468c93ad2f108551a9f48c

// Send request
resp, err = client.Do(req)
if err != nil {
    return
}
defer resp.Body.Close()

Http 建立 API

http.HandleFunc("/", Index)
err := http.ListenAndServe(":5555", nil)
if err != nil {
    log.Fatal("ListenAndServe: ", err)
}

func Index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, string("index"))
    // w.Write([]byte("Time: " + time.Now().Format(time.RFC1123)))
}

也可以改由這樣連線, 先建立 linstener

http.HandleFunc("/", Index)
s := &http.Server{}
l, err := net.Listen("tcp", ":5555")
if err != nil {
    panic(err)
}
panic(s.Serve(l))

ConnState 可以截取 connection 的狀態

總共有這五種狀態 StateNew StateActive StateIdle StateHijacked StateClosed

func main() {
    http.HandleFunc("/", myHandler)
    s := &http.Server{
        Addr:           ":8081",
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
        ConnState:      ConnStateListener,
    }
    panic(s.ListenAndServe())
}

func ConnStateListener(c net.Conn, cs http.ConnState) {
    fmt.Printf("CONN STATE: %v, %v\n", cs, c)
    // 如果想要直接結束 connection : c.Close()
}

ref : http://siddontang.com/2015/01/25/stop-server-gracefully/

handle connection (created at 27 Dec 2013)

ln, err := net.Listen("tcp", ":8080")
if err != nil {
    log.Fatal("Starting error", err)
}
conn, err := ln.Accept() // this blocks until connection or error
if err != nil {
    fmt.Println("Create connection failure!")
}
handleConnection(conn)

func handleConnection(conn net.Conn) {
    reader := bufio.NewReader(os.Stdin)
    for {
        fmt.Print("Please enter a message : ")
        str, err := reader.ReadString('\n')
        if err != nil {
            break
        }
        if str != "" {
            str := fmt.Sprintf("HTTP/1.1 200 OK\nContent-Length: %d\nContent-Type: text/html\n\n%s", len(str) - 1 , str)
            conn.Close() // shut down the connection
            break
        }
    }
    bufio.NewReader(os.Stdin).ReadBytes('\n')
}

API Custom Handler

type timeHandler struct {
  format string
}

func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  tm := time.Now().Format(th.format)
  w.Write([]byte("The time is: " + tm))
}

func main() {
  mux := http.NewServeMux()

  th := &timeHandler{format: time.RFC1123}
  mux.Handle("/time", th)

  log.Println("Listening...")
  http.ListenAndServe(":3000", mux)
}

net/http - x509: certificate signed by unknown authority

Post 到一個 https 的 URL 得到 x509: certificate signed by unknown authority

會發生這個原因主要是 golang 的 client 端會自動地對 server 傳來的 certificate 做驗證,但因為它是由不知名的 CA 簽發的,所以有 error

網路其中解法之一 : To fix this problem, you must add certificates of “COMODO RSA Domain Validation Secure Server CA” to your certififcate served from server, also see full report at https://www.ssllabs.com/ssltest/analyze.html?d=catchchat.catchchatchina.com

另一個解法是不要去驗證它

import ("net/http"; "crypto/tls")

tr := &http.Transport{
    TLSClientConfig: &tls.Config{InsecureSkipVerify : true},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://someurl:443/)

更好的解法是對 server 的 certificate 做驗證, 這邊有點超越我的能力了, 請參考這

net/http client request 得到 EOF 原因

Post https://xxxxx.com/wait/: EOF

有可能是這個 request 需要比較久的時間,即使你的 http client 的 timeout 設很長, 也會因為 server timeout 太短先把你踢掉,所以你會得到這個 Error

Get lan ip

func GetLocalIP() string {
    addrs, err := net.InterfaceAddrs()
    if err != nil {
        return ""
    }
    for _, address := range addrs {
        // check the address type and if it is not a loopback the display it
        if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
            if ipnet.IP.To4() != nil {
                return ipnet.IP.String()
            }
        }
    }
    return ""
}

Proper way to test CIDR membership of an IP 4 or 6 address example

ref: https://www.socketloop.com/tutorials/golang-proper-way-to-test-cidr-membership-of-an-ip-4-or-6-address-example

package main

import (
        "fmt"
        "net"
        "os"
)

func main() {
        // generate a range of IP version 4 addresses from a Classless Inter-Domain Routing address
        ipAddress, ipNet, err := net.ParseCIDR("123.45.67.64/27")

        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }

        // generate a range of IPv4 addresses from the CIDR address
        var ipAddresses []string

        for ipAddress := ipAddress.Mask(ipNet.Mask); ipNet.Contains(ipAddress); inc(ipAddress) {
                //fmt.Println(ipAddress)
                ipAddresses = append(ipAddresses, ipAddress.String())
        }

        // list out the ipAddresses within range
        for key, ipAddress := range ipAddresses {
                fmt.Printf("[%v] %s\n", key, ipAddress)
        }

        //test for IP version 4 address for membership

        // WRONG WAY!!
        fmt.Println("Contains 123.45.67.69 : ", ipNet.Contains([]byte("123.45.67.69")))

        // CORRECT / PROPER WAY!
        fmt.Println("Contains 123.45.67.69 : ", ipNet.Contains(net.ParseIP("123.45.67.69")))

}

func inc(ip net.IP) {
        for j := len(ip) - 1; j >= 0; j-- {
                ip[j]++
                if ip[j] > 0 {
                        break
                }
        }
}

output :

[0] 123.45.67.64
[1] 123.45.67.65
...略...
[9] 123.45.67.73
...略...
[31] 123.45.67.95
Contains 123.45.67.69 :  false
Contains 123.45.67.69 :  true

io/ioutil/bufio

將下載的資料轉成 io.Reader 型態

var source io.Reader
source = resp.Body
buffer := make([]byte, 1024)
for {
    cBytes, _ := source.Read(buffer)
    (...略...)

等同於

buffer := make([]byte, 1024)
for {
    cBytes,_ := resp.Body.Read(buffur)
    (...略...)

Read File

var img64 []byte
img64, _ = ioutil.ReadFile("/home/ubuntu/mygo/src/pushImage/google.png")

Read last line of file (ref: here)

func readLastLine(filename string) {
    var previousOffset int64 = 0

    file, err := os.Open(filename)
    if err != nil {
            panic(err)
    }

    defer file.Close()

    reader := bufio.NewReader(file)

    // we need to calculate the size of the last line for file.ReadAt(offset) to work

    // NOTE : not a very effective solution as we need to read
    // the entire file at least for 1 pass :(

    lastLineSize := 0

    for {
            line, _, err := reader.ReadLine()

            if err == io.EOF {
                    break
            }

            lastLineSize = len(line)
    }

    fileInfo, err := os.Stat(filename)

    // make a buffer size according to the lastLineSize
    buffer := make([]byte, lastLineSize)

    // +1 to compensate for the initial 0 byte of the line
    // otherwise, the initial character of the line will be missing

    // instead of reading the whole file into memory, we just read from certain offset

    offset := fileInfo.Size() - int64(lastLineSize+1)
    numRead, err := file.ReadAt(buffer, offset)

    if previousOffset != offset {

            // print out last line content
            buffer = buffer[:numRead]
            fmt.Printf("%s \n", buffer)

            previousOffset = offset
    }

}

Get last-modified time of file

// Get file list from dir
files, err := ioutil.ReadDir("/tmp")
if err != nil {
    return
}

for _, file := range files {
    // Skip dir
    if file.IsDir() {
        continue
    }

    fmt.Printf("%s %s\n", file.Name(), file.ModTime())
}

Line counter

func lineCounter(r io.Reader) (int, error) {
    buf := make([]byte, 32*1024)
    count := 0
    lineSep := []byte{'\n'}

    for {
        c, err := r.Read(buf)
        count += bytes.Count(buf[:c], lineSep)

        switch {
        case err == io.EOF:
            return count, nil

        case err != nil:
            return count, err
        }
    }
}

file, err := os.Open("/tmp/test.log")
if err != nil {
    return
}
defer file.Close()
count, err := lineCounter(file)

有效率的 monitor file 並取得最新內容 (類似 tail 指令)

  1. 先 new File
  2. 用兩個 int 變數記錄上一次及偵測到檔案變更的位置 (也就是檔案大小, 用 os.Stat 取得)
  3. 使用 fsnotify/fsnotify 偵測變更, 有變更就取最新內容 (用 file.ReadAt 在上一次的位置開始讀取, 讀取的長度用目前的size減上一次size

code:

for {
        select {
        case event := <-watcher.Events:
                if event.Op&fsnotify.Write == fsnotify.Write {
                        // log.Println("modified file:", event.Name)
                        info, err := file.Stat()
                        if err != nil {
                                log.Println("failed to get file size, err: ", err)
                                break
                        }
                        curr_size = info.Size()
                        last_line, err := readLastLine(file, prev_size, curr_size)
                        if err != nil {
                                log.Println("failed to get last line, err: ", err)
                                continue
                        }
                        prev_size = curr_size
                        if last_line != "" {
                                log.Println(last_line)
                        }
                }
        case err := <-watcher.Errors:
                log.Println("error:", err)
                return
        }
}

func readLastLine(file *os.File, prev int64, curr int64) (last_line string, err error) {
        // start reading from the end of the file
        buf := make([]byte, curr-prev)
        n, err := file.ReadAt(buf, prev)
        if err != nil && err != io.EOF {
                return "", err
        }
        return string(buf[:n]), nil
}

string

基本處理

String to Array

strings.Split("a/b/c",  "/")

Array to String (join)

s := []string{"a", "b", "c"}
fmt.Println(strings.Join(s, "/"))

os

外部參數

  • command : go run t.go dd
  • t.go : fmt.Println(os.Args[1])

“main.main” 函數並沒有返回值

如果想返回一個出錯信息,可用系統調用強制退出:

os.Exit(1)

# Stop
os.Exit(0)

檔案 / 目錄 是否存在

檔案或目錄是否存在

if _, err := os.Stat(filename); os.IsNotExist(err) {
    fmt.Printf("no such file or directory: %s", filename)
    return
}

檔案資訊

func GetFileInfo(path string) (isExistent bool, fileInfo os.FileInfo) {
    fileInfo, err := os.Stat(path)
    if err != nil {
        // no such file or dir
        return false, nil
    }
    if fileInfo.IsDir() {
        // it's a directory
        return false, nil
    }
    // it's a file
    return true, fileInfo
}

目錄是否存在

func DirExists(path string) (bool) {
    fileInfo, err := os.Stat(path)
    if err != nil {
        // no such file or dir
        return false
    }
    if fileInfo.IsDir() {
        // it's a directory
        return true
    }
    // it's a file
    return false
}

檔案是否存在

func FileExists(path string) (bool) {
    fileInfo, err := os.Stat(path)
    if err != nil {
        // no such file or dir
        return false
    }
    if fileInfo.IsDir() {
        // it's a directory
        return false
    }
    // it's a file
    return true
}

目前位置

dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
        log.Fatal(err)
}
fmt.Println(dir)

列出指定路徑下的檔案或目錄

files, err := ioutil.ReadDir("./")
if err != nil {
    log.Fatal(err)
}
for _, f := range files {
        fmt.Println(f.Name())
}

file - 指定寫入位置

file, _ := os.OpenFile("tt", os.O_RDWR|os.O_APPEND, 0660)
n, err := file.WriteAt([]byte("中Start"), 3)
if err != nil {
    fmt.Println(err.Error())
}
fmt.Println(n)

返回的 n 是寫入的字節大小, 中文字佔 3 btyes, 英文 1 bytes

檔案 tt 寫入後的結果 : ^@^@^@中Start

其中 ^@ 為空字元, 共有 3 組 ^@ 是因為 WriteAt 指定從 3 開始寫入,

但檔案位置是從 0 開始算, 所以會有 3 組 ^@

寫入改成 n, err := file.WriteAt([]byte("678"), 6)

結果為 : ^@^@^@中678rt

註 : ^@ 佔 1byte, 佔 3 bytes

Create folder

 import (
   //"fmt"
   "os"
   "path/filepath"
 )

 func main() {
   // create a TestDir directory on current working directory
   os.Mkdir("." + string(filepath.Separator) + "TestDir",0777)
 }

在 windows 下執行可執行檔(.exe)後視窗停留不關閉

引入 :

import (
    "bufio"
    "os"
)

最後一行執行 :

bufio.NewReader(os.Stdin).ReadBytes('\n')

當執行 .exe 檔後視窗就不會關閉了

或用另一種寫法

b := make([]byte, 1)
os.Stdin.Read(b)

不同作業系統取得不同 path separator (slash, 斜線)

string(filepath.Separator)

os/exec

取得執行中的 binary 路徑
[binary path]
os.Args[0]                      # /home/apps/go/src/test/test

[dir path]
filepath.Dir(os.Args[0])        # /home/apps/go/src/test
執行外部指令
output, err := exec.Command("git", "rev-parse", "HEAD").Output()
if err != nil {
    return "unknown"
}
fmt.Println(string(output))

output 最後會含一個 ascii: 10 (hex: 0A) 的換行字元, 它並不是 \n 字元

signal

接收中止訊號(kill signal)

當程式執行後會停在 <-sigc,如果收到 kill 指令則會繼續往下走

sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, os.Kill)
<-sigc
log.Println("Abort!")

發送 signal

重啟 signal

syscall.Kill(syscall.Getpid(), syscall.SIGHUP)

Kill process signal

syscall.Kill(syscall.Getpid(), syscall.SIGTERM)

time

time

Datetime

time.Now()                  // 2016-12-29 17:33:23.784617257 +0800 CST

Format

 time.Now().Format(time.RFC3339)                            // 2017-02-14T06:59:21Z   可以以 string 型態被解到 time.Time 型態
 time.Now().Local().Format("2006-01-02 15:04:05 +0800")     // 2014-06-30 14:23:38 +0800
 time.Now().Format("2006-01-02 15:04:05")                   // 2015-02-03 04:16:54
 time.Now().String()                                        // 2017-01-10 06:51:14.271079336 +0000 UTC
 time.Now().UTC().String()                                  // 轉成 UTC+0

Timestamp

time.Now().Unix()           // 1483004003

Convert timestamp to time

tm := time.Unix(1484032609, 0)

Convert string to time

today := "2017-01-10 16:57:28"
t, err := time.Parse("2006-01-02 15:04:05", today)

Convert string to time in timezone

// 將指定的時間跟 timezone 轉成 time.Time
l, err := time.LoadLocation("Asia/Taipei")
t, err := time.ParseInLocation("2006-01-02 15:04:05", str, l)       // 2017-01-10 16:57:28 +0800 CST

UTC: time.LoadLocation(“UTC”)

Convert string to time in RFC3339

ts, err := time.Parse(time.RFC3339, src)

Convert time to time in timezone

t := time.Now()
loc, err := time.LoadLocation("Asia/Taipei")
fmt.Println(t.String())             // 2017-01-10 08:41:19.833220416 +0000 UTC
fmt.Println(t.In(loc))              // 2017-01-10 16:41:19.833220416 +0800 CST
fmt.Println(t.In(loc).Format("2006-01-02 15:04:05"))

year, month, day, hour, min, sec

year, month, day := t2.Date()
fmt.Printf("Date : [%d]year : [%d]month : [%d]day \n", year, month, day)
// Date : [2017]year : [1]month : [10]day

hr, min, sec := t2.Clock()
fmt.Printf("Clock : [%d]hour : [%d]minutes : [%d] seconds \n", hr, min, sec)
// Clock : [16]hour : [57]minutes : [28] seconds

first/last day of month

y, m, _ := time.Now().Date()
first := time.Date(y, m, 1, 0, 0, 0, 0, loc)
last := first.AddDate(0, 1, 0).Add(-time.Nanosecond)

Parse time 的非預期狀況

timeTest := "20:36"
timezone := "Asia/Tokyo"
loc, err := time.LoadLocation(timezone)
if err != nil {
    log.Fatal(err)
}
t, err := time.ParseInLocation("15:04", timeTest, loc)
if err != nil {
    log.Fatal(err)
}
fmt.Println(t.Format("15:04"))
fmt.Println(t.UTC().Format("15:04"))

在某些主機會是預期結果

20:36
11:36

某些會是:

20:36
11:17

似乎也不是 go 版本不同的問題, 不知道確切發生的原因, 但如果將指定的時間用完整一點可以解決此問題

timeTest := time.Now().Format("2006-01-02") + " " + "20:36"
timezone := "Asia/Tokyo"

loc, err := time.LoadLocation(timezone)
if err != nil {
    log.Fatal(err)
}
t, err := time.ParseInLocation("2006-01-02 15:04", timeTest, loc)
if err != nil {
    log.Fatal(err)
}
fmt.Println(t.Format("15:04"))
fmt.Println(t.UTC().Format("15:04"))

Comapare

After / Before

time1 := "2016-05-15 12:22:00"
time2 := "2017-05-15 12:22:00"
t1, err := time.Parse("2006-01-02 15:04:05", time1)
t2, err := time.Parse("2006-01-02 15:04:05", time2)
if err == nil && t1.Before(t2) {
    fmt.Println("true")
}

延遲

time.Sleep(3 * time.Second)

time.Sleep(500 * time.Millisecond)

speed := 500
time.Sleep(time.Duration(speed) * time.Millisecond)

時間相加/相減

兩時間相減

startTime := time.Now()                     // 2014-01-26 18:17:22.185125 +0800 CST
time.Sleep(3 * time.Second)
endTime := time.Now()                       // 2014-01-26 18:17:25.186307 +0800 CST

var durationTime time.Duration = endTime.Sub(startTime)
fmt.Println(durationTime.String())

加減(日時秒)

timein := time.Now().Add(time.Hour * time.Duration(1))      // Add 1 hour
timein := time.Now().Add(time.Minute * time.Duration(1))    // Add 1 minute
timein := time.Now().Add(time.Second * time.Duration(1))    // Add 1 second

// 減的話一樣是用 Add 但在 Duration 上加上負號
timein := time.Now().Add(time.Minute * time.Duration(-10))


then := time.Now().AddDate(0, 0, 1)     // +1 day
then := time.Now().AddDate(0, 0, -1)    // -1 day
then := time.Now().AddDate(1, 1, 0)     // +1 year and 1 month

timestamp plus 30 seconds

time.Now().Unix() + 30

Week

time.Now().Weekday()            // Tuesday
int(time.Now().Weekday())       // 2 (Day of the week)

相減算時間差

time_start := time.Now()
// do something
log.Printf("elasped time: %s", time.Since(time_start))

3 * time.Second

如果前面的 int 從 string 轉型,是會噴錯的,要改成 :

time.Duration(params["idle_timeout"].(int)) * time.Second

印出到小數的 timestamp/datetime (e.g. 2017-06-28 02:58:46.452 +0000 UTC)

func main() {
        fmt.Println(unixMilli(time.Unix(0, 123400000)))
        fmt.Println(unixMilli(time.Unix(0, 123500000)))
        m := makeTimestampMilli()
        fmt.Println(m)
        fmt.Println(time.Unix(m/1e3, (m%1e3)*int64(time.Millisecond)/int64(time.Nanosecond)))
}

func unixMilli(t time.Time) int64 {
        return t.Round(time.Millisecond).UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
}

func makeTimestampMilli() int64 {
        return unixMilli(time.Now())
}

Result:

123
124
1498618861845
2017-06-28 03:01:01.845 +0000 UTC

Timestamp with three decimal places

fmt.Sprintf("%f", float64(time.Now().UnixNano())/1000000000) // 1483004003.785

math/rand

產生 0~7 (每一次產生不一樣的變數)

rand.Seed(time.Now().UnixNano())
r := rand.Intn(8)

等於

r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
f := r.Intn(8)

產生一串隨機數值 (ex: 7572000213564998818)

r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
f := r.Int63()

Unique key

32 bytes HEX

key := make([]byte, 16)       // 16 會產生 32 個字元
_, err := rand.Read(key)
if err != nil {
    log.Fatal(err)
}
mk := fmt.Sprintf("%X", key)  //小寫
mk := fmt.Sprintf("%X", key)  // 大寫
fmt.Println(mk)

sync

Pool (First In, First out)

var pool sync.Pool
type Item struct {
    Order string
}

v := pool.Get() // You get nothing if pool is empty.
if v == nil {
    v = &Item{Order: "first"}
}
pool.Put(v)                      // Put the first item
pool.Put(&Item{Order: "second"}) // Put the second item
q := pool.Get()                  // You'll get the first item
q = pool.Get()                   // You'll get the second item
fmt.Println(q.(*Item).Order)     // "second"

Sync WaitGroup

當同一時間同步做很多事,但希望全部 goroutine 做完事才進行下一步

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    for q := 0; q < 10; q++ {
        wg.Add(1)               # flag
        go func(i int) {
            fmt.Println(i)
            defer wg.Done()     # 通報這個 goroutine 做完了
        }(q)
    }
    wg.Wait()                   # 程式會在這裡等全部 goroutine 做完
}

Mutex

a := 0
var wg sync.WaitGroup
for i := 0; i < 10000; i++ {
    wg.Add(1)
    go func() {
        a++
        defer wg.Done()
    }()
}
wg.Wait()
fmt.Printf("a = %d\n", a)     // a = 9222  < 10000 because of race condition

use -race to verify

 ==================
WARNING: DATA RACE
Read at 0x00c420084008 by goroutine 7:
  main.main.func1()
      /tmp/mutex.go:17 +0x3f

Previous write at 0x00c420084008 by goroutine 6:
  main.main.func1()
      /tmp/mutex.go:17 +0x58

Goroutine 7 (running) created at:
  main.main()
      /tmp/mutex.go:15 +0xfb

Goroutine 6 (finished) created at:
  main.main()
      /tmp/mutex.go:15 +0xfb
==================
a = 9978
Found 1 data race(s)
exit status 66

use lock

a := 0
var wg sync.WaitGroup
var l sync.Mutex
for i := 0; i < 10000; i++ {
    wg.Add(1)
    go func() {
        l.Lock()
        a++
        l.Unlock()
        defer wg.Done()
    }()
}
wg.Wait()
fmt.Printf("a = %d\n", a)      // a = 10000

encoding

組出 SOAP

package main

import "fmt"
import "encoding/xml"

type MyRespEnvelope struct {
    XMLName xml.Name
    Body    Body
}

type Body struct {
    XMLName     xml.Name
    GetResponse completeResponse `xml:"activationPack_completeResponse"`
}

type completeResponse struct {
    XMLName xml.Name `xml:"activationPack_completeResponse"`
    Id      string   `xml:"Id,attr"`
    MyVar   string   `xml:"activationPack_completeResult"`
}

func main() {

    Soap := []byte(`<?xml version="1.0" encoding="UTF-8"?>
                    <soap:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
                    <soap:Body>
                    <activationPack_completeResponse Id="http://tempuri.org/">
                    <activationPack_completeResult xsi:type="xsd:string">Active</activationPack_completeResult>
                    </activationPack_completeResponse>
                    </soap:Body>
                    </soap:Envelope>`)

    res := &MyRespEnvelope{}
    if err := xml.Unmarshal(Soap, res); err != nil {
        fmt.Println(err.Error())
    }

    var val completeResponse = res.Body.GetResponse
    fmt.Println(val.MyVar)
}

ref : http://play.golang.org/p/957GWzfdvN

Parse xml into struct

Person.xml :

<Person>
    <FullName>Grace R. Emlin</FullName>
    <Company>Example Inc.</Company>
    <Email where="home">
        <Addr>gre@example.com</Addr>
    </Email>
    <Email where='work'>
        <Addr>gre@work.com</Addr>
    </Email>
    <Group>
        <Value>Friends</Value>
        <Value>Squash</Value>
    </Group>
    <City>Hanga Roa</City>
    <State>Easter Island</State>
</Person>

code :

package main

import (
    "encoding/xml"
    "fmt"
    "io/ioutil"
    "os"
)

type Email struct {
    Where string `xml:"where,attr"`
    Addr  string
}

type Address struct {
    City, State string
}

type Result struct {
    XMLName xml.Name `xml:"Person"`
    Name    string   `xml:"FullName"`
    Phone   string
    Email   []Email
    Groups  []string `xml:"Group>Value"`
    Address
}

func main() {

    var v Result
    xmlFile1, err := ioutil.ReadFile("Person.xml")
    if err != nil {
        fmt.Println("Error opening file: ", err)
        return
    }

    err1 := xml.Unmarshal(xmlFile1, &v)
    if err1 != nil {
        fmt.Printf("error: %v", err)
        return
    }
    fmt.Println(v)

    // 用 streaming 形式解析, 較容易處理大數據的 xml
    xmlFile, err := os.Open("Person.xml")
    if err != nil {
        fmt.Println("Error opening file: ", err)
        return
    }
    defer xmlFile.Close()
    decoder := xml.NewDecoder(xmlFile)
    for {
        t, _ := decoder.Token()
        if t == nil {
            break
        }
        switch se := t.(type) {
        case xml.StartElement:
            if se.Name.Local == "Person" {
                var d Result
                decoder.DecodeElement(&d, &se)
                fmt.Println(d)
            }
        }
    }

    fmt.Println(v.XMLName)
    fmt.Printf("Name=%s \n", v.Name)
    fmt.Printf("Where=%s   Addr=%s  \n", v.Email[0].Where, v.Email[0].Addr)
    fmt.Printf("Where=%s   Addr=%s  \n", v.Email[1].Where, v.Email[1].Addr)
    fmt.Printf("Groups : %s,  %s\n", v.Groups[0], v.Groups[1])
    fmt.Printf("City=%s,  State=%s  \n", v.Address.City, v.Address.State)
}

ref : http://www.cnblogs.com/yuanershi/archive/2013/01/29/2881192.html

Parse xml into map - clbanning/mxj

var xml = `
    <Person>
        <FullName>Grace R. Emlin</FullName>
        <Company>Example Inc.</Company>
        <Email where="home">
            <Addr>gre@example.com</Addr>
        </Email>
        <Email where='work'>
            <Addr>gre@work.com</Addr>
        </Email>
        <Group>
            <Value>Friends</Value>
            <Value>Squash</Value>
        </Group>
        <City>Hanga Roa</City>
        <State>Easter Island</State>
    </Person>`
res, err := mxj.NewMapXml([]byte(xml))
if err != nil {
    fmt.Println(err)
}
fmt.Println(res)
fmt.Println(res["Person"].(interface{}).(map[string]interface{})["FullName"])
fmt.Println(res["Person"].(interface{}).(map[string]interface{})["Email"].([]interface{})[1].(map[string]interface{})["Addr"])

result :

map[Person:map[FullName:Grace R. Emlin Company:Example Inc. Email:[map[-where:home Addr:gre@example.com] map[Addr:gre@work.com -where:work]] Group:map[Value:[Friends Squash]] City:Hanga Roa State:Easter Island]]
Grace R. Emlin
gre@work.com

Parse json (struct, map)

方法1 : Json 放進預先定義好的 Struct 裡

type Test struct {
    JsonTag string `json:"field1"` // 利用 Tag mapping 到欄位名稱
    Field2  []struct {
        SubField1 string
        SubField2 string
    }
}

res := &Test{}
if err := json.Unmarshal([]byte(jsonString), res); err != nil {
    fmt.Println(err)
}
fmt.Println(res.JsonTag)
fmt.Println(res.Field2[0].SubField1)
fmt.Println(res.Field2[1].SubField2)

方法2 : 以 map 方式將 json 讀進來

var res2 map[string]interface{}
if err := json.Unmarshal([]byte(jsonString), &res2); err != nil {
    fmt.Println(err)
}
fmt.Println(res2["field1"])
fmt.Println(res2["field2"].([]interface{})[0].(map[string]interface{})["subField1"])
fmt.Println(res2["field2"].([]interface{})[1].(map[string]interface{})["subField2"])

json unmarshal 預設數字讓它是 int64 而不是 float64

json 字串裡面有 "visible_at":1483079819,但 unmarshal 完 int 變成 1.483079819e+09

解決方法 :

var data = `{
    "id": 12423434,
    "Name": "Fernando"
}`
d := json.NewDecoder(strings.NewReader(data))
d.UseNumber()
var x interface{}
if err := d.Decode(&x); err != nil {
    log.Fatal(err)
}
fmt.Printf("decoded to %#v\n", x)

base64 加解密

data := "abc123!?$*&()'-=@~"

sEnc := b64.StdEncoding.EncodeToString([]byte(data))
fmt.Println(sEnc)           # YWJjMTIzIT8kKiYoKSctPUB+

sDec, _ := b64.StdEncoding.DecodeString(sEnc)
fmt.Println(string(sDec))   # abc123!?$*&()'-=@~

POST base64 with form-data

body := fmt.Sprintf("p=%s&iv=%s", url.QueryEscape(crypt.Base64Encode(p_bytes)), url.QueryEscape(crypt.Base64Encode(iv_bytes)))

json.Marshal 不要 escape 某些字元

當欄位裡面有 url 時,json marshal 會自動地將某些字元(e.g. &) escape 為 \\u0026,為了避免此情形改用 :

type Search struct {
        Query string `json:"query"`
}
data := &Search{Query: "http://google.com/?q=stackoverflow&ie=UTF-8"}
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
err = enc.Encode(data)
fmt.Println(string(buf.Bytes()))            // 注意 Encode 會在字尾加上 `\n`

[output]
    {"query":"http://google.com/?q=stackoverflow&ie=UTF-8"}

或用取代的方式將被脫逸的符號還原

b, err := json.Marshal(v)
b = bytes.Replace(b, []byte("\\u003c"), []byte("<"), -1)
b = bytes.Replace(b, []byte("\\u003e"), []byte(">"), -1)
b = bytes.Replace(b, []byte("\\u0026"), []byte("&"), -1)

crypto

md5

h := md5.New()
io.WriteString(h, "Hello World!")
fmt.Printf("%x", h.Sum(nil))

hasher := md5.New()
hasher.Write([]byte("test"))
fmt.Println(hex.EncodeToString(hasher.Sum(nil)))

h := md5.Sum([]byte("test"))
fmt.Println(hex.EncodeToString(h[:]))

sha456

方法1

hash := sha256.New()
hash.Write([]byte("test"))
b := hash.Sum(nil)  // byte
fmt.Println(hex.EncodeToString(b[:]))

方法2

b := sha256.Sum256([]byte("test"))
fmt.Println(hex.EncodeToString(b[:]))

AES CBC encrypt

plaintext 必須為 aes.Blocksize (16) 的倍數


import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "io"
)

func main() {
    key := []byte("1234567890123456")
    plaintext := pad([]byte("secret data12345"))

    if len(plaintext)%aes.BlockSize != 0 {
        panic("plaintext is not a multiple of the block size")
    }

    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    ciphertext := make([]byte, aes.BlockSize+len(plaintext))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        panic(err)
    }

    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
    fmt.Println(base64.StdEncoding.EncodeToString(iv))              // iv base64: VGnqCX2VQonnNUL/Hlmk1w==
    fmt.Println(base64.StdEncoding.EncodeToString(ciphertext[16:])) // encrypt base64: 5ThQKq6RnAiGsLHU/BWm7A==
}

// plaintext 無法被 aes.Blocksize (16) 要先補位數湊滿
func pad(in []byte) []byte {
    padding := aes.BlockSize - (len(in) % aes.BlockSize)
    if padding == 0 {
        padding = aes.BlockSize
    }
    for i := 0; i < padding; i++ {
        in = append(in, byte(padding))
    }
    return in
}

另種寫法

text := []byte(`secret data12345`)
sharekey := []byte(`1234567890123456`)
block, err := aes.NewCipher(sharekey)
if err != nil {
    panic(errors.New("AESEncrypt Create Cipher error: " + err.Error()))
}

paddingText := pad(text, block.BlockSize())
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
    panic(err)
}

mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(paddingText, paddingText)
fmt.Println(base64.StdEncoding.EncodeToString(iv))
fmt.Println(base64.StdEncoding.EncodeToString(paddingText))

// 補滿 16 位
func pad(src []byte, blocksize int) []byte {
    pdSize := blocksize - (len(src) % blocksize)
    padBytes := bytes.Repeat([]byte{0x00}, pdSize)
    src = append(src, padBytes...)
    return src
}

AES CBC decrypt

import (
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "fmt"
)

func main() {
    key := []byte("1234567890123456")
    iv, _ := base64.StdEncoding.DecodeString("7/JbVL/5cMvqf9sslD6qdQ==")                // iv base64
    ciphertext, _ := base64.StdEncoding.DecodeString("6AFagV3iLEAqbBuSqvL19Q==")        // encrypt base64

    block, _ := aes.NewCipher(key)
    if len(ciphertext) < aes.BlockSize {
        panic("ciphertext too short")
    }
    if len(ciphertext)%aes.BlockSize != 0 {
        panic("ciphertext is not a multiple of the block size")
    }
    aes := cipher.NewCBCDecrypter(block, iv)
    aes.CryptBlocks(ciphertext, ciphertext)
    fmt.Printf("%s\n", ciphertext)
}

AES Decrypt - String 相同, 同長度及 byte 不同 invalid character '\x00' after top-level value

可能是 AES 加解密後發生的情況,可能在過程中被塞入 padding, 導致解出來後的 byte 數量不一樣

加密前 :

2561953d9ec389715498d44b0c150fec
len = 32
[50 53 54 49 57 53 51 100 57 101 99 51 56 57 55 49 53 52 57 56 100 52 52 98 48 99 49 53 48 102 101 99]

加密後 :

len = 48
[50 53 54 49 57 53 51 100 57 101 99 51 56 57 55 49 53 52 57 56 100 52 52 98 48 99 49 53 48 102 101 99 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

ASCII 對應

50 => 2
99 => c
0 => 空字元(Null)

解決方法: 將多餘的 \x00 刪除

b = bytes.Trim(b, "\x00")

ref: 類似問題

UUID (crypto/rand)

// 一般
uuid := make([]byte, 16)
io.ReadFull(rand.Reader, uuid)

// 64 encode
uuid := Gen()
ret := base64.URLEncoding.EncodeToString(uuid)
ret = strings.Replace(ret, "=", "", -1)

import uuid "github.com/satori/go.uuid"
uuid.NewV4()

defer

關於 defer 如何運作

package main

import (
    "fmt"
)

func main() {
    defer Function3()
    Function1()
}

func Function1() {
    fmt.Println("Function1 開始")
    defer Function2()
    fmt.Println("Function1 結束")
}

func Function2() {
    fmt.Println("Function 2")
}

func Function3() {
    fmt.Println("Function 3")
}


執行結果 :

Function1 開始
Function1 結束
Function 2
Function 3

ref : http://ithelp.ithome.com.tw/question/10153473

error

擲出自定義錯誤訊息

err = errors.New("fail")

比對 error

在另一個 package 用全域變數定義

package fruits

var NoMorePumpkins = errors.New("No more pumpkins")

就可以比對了

package shop

if err == fruits.NoMorePumpkins {
     ...
}

runtime

detect OS

switch runtime.GOOS {
    case "windows":
    case "darwin":       //Mac OS
    case "linux":
    default :
}

記憶體使用量

var m0 runtime.MemStats
runtime.ReadMemStats(&m0)
fmt.Printf("Memory: %.2f mb", float64(m0.Sys)/1024/1024)

channel

把 Channel 當成 Queue 使用

它是先進先出,如果要做一個 worker 很好用,直接宣告它的大小是多少,但塞值不需要管它的 index,一直丟就對了

var worker_ch = make(chan string, 10000)

// 用 Goroutine 跑,一個 Worker,它會一直 wait,直到 worker_ch 有值
for {
    file_name := <-worker_ch
    DoSomething(file_name)
}

// 另一個 Goroutine,假設它是 API 負責塞值給 worker_ch
func NonBlcok(ctx *fasthttp.RequestCtx, _ fasthttprouter.Params) {
    file_name := string(ctx.FormValue("filename"))
    worker_ch <- file_name
}

reflect

Call dynamic func by name

func Call(m map[string]interface{}, name string, params ...interface{}) (result []reflect.Value, err error) {
    f := reflect.ValueOf(m[name])
    if len(params) != f.Type().NumIn() {
        err = errors.New("The number of params is not adapted.")
        return
    }
    in := make([]reflect.Value, len(params))
    for k, param := range params {
        in[k] = reflect.ValueOf(param)
    }
    result = f.Call(in)
    return
}

用法 :

// 宣告
funcs := map[string]interface{}{
    "qq": curl.Get,  // index 隨便取, 後面的是 func name  e.g. func QQ() 就填  QQ, 如果是 curl pacakge 的 Get func 就填 curl.Get
}
Call(funcs, "qq", params)  // 如果有參數就傳入 params

Call dynamic struct.function by name (string)

import "fmt"
import "reflect"

type T struct{}

func (t *T) Test() string {
    fmt.Println("test...")
    return "ok"
}

func main() {
    var t T
    res := reflect.ValueOf(&t).MethodByName("Test").Call([]reflect.Value{})
    fmt.Println(res[0]) // ok
}

res 是一個陣列,即使你的 func 只回傳一個參數,它也是放在陣列裡

flag

auto flag

config := struct {
    Name    string        `flag:"queue,queue name"`
}{
    Name:    "",
}
autoflags.Define(&config)
flag.Parse()

image

image/jpg Convert jpg to png

file, err := os.Open("test.jpg")
if err != nil {
    log.Fatal("File error")
}

img, err := jpeg.Decode(file)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}

out, err := os.Create("test.png")
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
err = png.Encode(out, img)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
fmt.Println("Done!")

image/jpg Convert jpg to bmp

// Convert jpg to bmp
img_file, err := os.Open("test.jpg")
if err != nil {
    log.Fatal("File error")
}
defer img_file.Close()

img, err := jpeg.Decode(img_file)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
out_file, err := os.Create("test.bmp")
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
err = bmp.Encode(out_file, img)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
defer out_file.Close()

image/jpg Convert bmp to jpg

// Convert jpg to bmp
img_file, err := os.Open("test.bmp")
if err != nil {
    log.Fatal("File error")
}
defer img_file.Close()

img, err := bmp.Decode(img_file)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
out_file, err := os.Create("test.jpg")
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}

options := &jpeg.Options{Quality: 50}
err = jpeg.Encode(out_file, img, options)
if err != nil {
    fmt.Println(err)
    os.Exit(1)
}
defer out_file.Close()

unsafe

看變數佔多數記憶體 (bytes)

a1 := "xxxdflasjdfl;daskjfdsalfkjasdlfjas"
a2 := int64(66666)
a3 := int32(5555)
a4 := float32(2222.4)
fmt.Println(unsafe.Sizeof(a1))  // 16
fmt.Println(unsafe.Sizeof(a2))  // 8
fmt.Println(unsafe.Sizeof(a3))  // 4
fmt.Println(unsafe.Sizeof(a4))  // 4

DHCP 運作流程

發送流程

[1] client 以 UDP 發出 DHCPDISCOVER 廣播封包尋找 DHCP Server

來源位址 : 0.0.0.0:68 目的位址 : 255.255.255.255:67

當 client 開機或重啟網卡時會發出此的封包

[2] DHCP Server 發出 DHCPOFFER 廣播封包給 client

來源位址 : 192.168.181.30:67 目的位址 : 255.255.255.255:68

收到請求會先依據 client 的 MAC 來判斷以下

  • 到伺服器的登錄檔中尋找該用戶之前是否曾經用過某個 IP ,若有且該 IP 目前無人使用,則提供此 IP 給用戶端;
  • 若設定檔針對該 MAC 提供額外的固定 IP (static IP) 時,則提供該固定 IP 給用戶端;
  • 若不符合上述兩個條件,則隨機取用目前沒有被使用的 IP 參數給用戶端,並記錄下來。

接著 server 提供一組 IP 及租約期限訊息給 client, 如果網路有很多台 DHCP Server, client 會以收到的第一個 DHCPOFFER 訊息為主

[3] client 發出 DHCPREQUEST 給 DHCP Server

來源位址 : 0.0.0.0:68 目的位址 : 255.255.255.255:67

Client 端在收到 DHCPOFFER 封包後,會先送出一個 ARP Request 的廣播封包,來確定對方所提供的 IP 位址沒有其他的主機在使用,

如果發現這個位址已被其它主機使用掉的話,Client 端就會送出一個 DHCPDECLINE 的拒絕訊息給 Server,然後再送出一個 DHCPDISCOVER 的廣播封包來重新索取 IP 位址。

事實上﹐並不是所有 DHCP 客戶端都會無條件接受 DHCP 伺服器的 offer ﹐尤其這些主機安裝有其它 TCP/IP 相關的客戶軟體。客戶端也可以用 Dhcprequest 向伺服器提出 DHCP 選擇

假設 client 確定此位址尚未被使用而且也接受這個 IP 的話, 那麼接著就會送出一個 DHCPREQUEST 的廣播封包來回應給這台 DHCP Server。

之所以還是送出廣播封包的用意,是為了告知其他未被選上的 DHCP Server,這樣這些 DHCP Server 才可將原本打算保留給 Client 的 IP 位址釋放出來給其他 Client 使用。

ARP(Address Resolution Protocol) : 從 cache 尋找 IP 位址所對應的 MAC

[4] DHCP Server 發出 DHCPACK 給 client

來源位址 : 192.168.181.30:67 目的位址 : 255.255.255.255:68

DHCP Server 收到 DHCPREQUEST 的訊息後,會回應一個 DHCPACK 的確認訊息給 Client ( 此時還是透過廣播方式,因 Client 尚未正式取得 IP 位址 ), 以確認 IP 租約的正式生效

此訊息裡包含了指派給 Client 端使用的 IP 位址、網路遮罩、預設閘道器、DNS Server 等等,

而在 client 收到這些訊息後,便完成了整個索取 IP 位址的動作。

IP 租約到期的情況 :

用戶端離線:不論是關閉網路介面 (ifdown)、重新開機 (reboot)、關機 (shutdown) 等行為,皆算是離線狀態,這個時候 Server 端就會將該 IP 回收,並放到 Server 自己的備用區中,等待未來的使用;

用戶端租約到期:前面提到 DHCP server 端發放的 IP 有使用的期限,用戶端使用這個 IP 到達期限規定的時間,而且沒有重新提出 DHCP 的申請時,就需要將 IP 繳回去!這個時候就會造成斷線。但用戶也可以再向 DHCP 伺服器要求再次分配 IP 囉。

其他

更新租約期:

往往 DHCP Client 為了持續延用此 IP 位址,而會向 DHCP Server 要求更新租約期,一旦更新成功,則 Client 的 IP 租約期限就會從頭開始計算起。在 Client 更新租約期的時候,會向 Server 送出一個 DHCPREQUEST 的訊息。接著以下就舉兩種 Client 送出 DHCPREQUEST 給 Server 的時機:

  1. DHCP Client 重新開機時:

當 DHCP 的用戶端電腦重新啟動時,會送出 DHCPREQUEST 給 DHCP Server,來要求延用原來的 IP 位址,倘若此位址目前尚未租用給其它的 Client,則用戶端就可以取得與上次所使用的 IP 相同。

  1. 達 ½ 租約期時:

DHCP Client 會在 IP 租約期達到一半時,主動送出一個 DHCPREQUEST 給 DHCP Server,而 Client 如果也順利收到對方所回應的 DHCPACK 訊息,就表示已成功更新租約期。萬一更新失敗,Client 還是可以繼續使用此位址,因為租約期還沒到。而在更新失敗的情況下,Client 會在租約期到了八分之七時,利用廣播方式送出 DHCPREQUEST 更新訊息來尋找任何一台可用的 DHCP Server,看看能否更新租約,如果還是無法更新成功,則 Client 會放棄目前使用的 IP,並重新送出 DHCPDISCOVER 來向 DHCP 索取一個新的位址。

檢查租約檔案

cat /var/lib/dhcpd/dhcpd.leases

ref : http://blog.roodo.com/dabinn/archives/7009437.html http://www.study-area.org/network/network_ip_dhcp.htm http://paching.myweb.hinet.net/lesson13.htm http://linux.vbird.org/linux_server/0340dhcp.php

Mkfifo 指令

介紹

用在兩個不同執行的程式溝通, 他就像是一條水管(pipeline file)將兩個程式連接起來

其中一邊不斷的將水倒進去, 另一邊則取水, 這樣就可以達到資料

Example

session 1 :

建立 pipe

cd /tmp
mkfifo test

傳送資料進去

echo "Hello Jex" > test
echo "line 1" > test&

在命令的最後面加上 & 可以讓它背景作業

session 2 :

將 session 1 存的資料拿出來

cd /tmp
cat test

fifo (命名通道)

這些參數似乎要用 C 寫才能使用

讀 :

  • O_NONBLOCK enable : 立刻返回成功
  • O_NONBLOCK dieable : block 直到有資料寫入而開啟 FIFO

寫 :

  • O_NONBLOCK enable : 立刻返回失敗, error code 為 ENXIO
  • O_NONBLOCK dieable : block 直到有資料寫入而開啟 FIFO

打開檔案預設是 block

mkfifo 與 fifo 的差異

mkfifo 由 pipe 函數建立並打開, fifo 由 mkfifo 函數建立, 打開用 open

差別只在建立與開啟的方式不同, 一旦完成這些, 在通道 I/O 及 FIFO 幾乎一樣

  • mkfifo - make FIFOs (named pipes)
  • mknod - make block or character special files

ref : * http://blog.csdn.net/jnu_simba/article/details/8953960 * http://goo.gl/vxsxoX * http://catchtest.pixnet.net/blog/post/22220521-linux%E7%A8%8B%E8%A8%AD%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98(%E5%8D%81%E5%9B%9B)

Nc (Netcat) 指令

介紹

用來連接特定 port, 或啟動一個 port 來監聽, 可以執行簡單的訊息或檔案傳送

有些系統將 nc 改為 netcat

快速看如何使用

啟動 20000 port

nc -l 192.168.1.171 20000
  • 如果不加上 IP, 就會監聽所有從 20000 port 進來的主機
  • -l :作為監聽之用,亦即開啟一個 port 來監聽用戶的連線;
  • -u :不使用 TCP 而是使用 UDP 作為連線的封包狀態

連線到 20000 port

nc 192.168.1.171 20000

然候就可以隨便打一些字, 送出後主機那就會看到了, 也可搭配 stdout, stdin 做互動輸入/輸出

傳送/接收 檔案

傳送檔案

$ cat backup.iso | nc 54.250.122.78 3333

接收檔案並存入 backup.iso

$ nc -l 3333 >> backup.iso

傳送顯示進度條

$ cat backup.iso | pv -b | nc 54.250.122.78 3333

接收顯示進度條

$ nc -l 8000 | pv -b >> backup.iso
  • pv can be used to show a progress indicator.
  • pv : Pipe Viewer, 顯示通過 pipe 的資料處理進度, 例如應用在 copy, compress, nc 等等..

傳送 partition

建立一個 partition 的 image 並傳送到遠端主機

$ dd if=/dev/hdb5 | gzip -9 | nc -l 3333

-9, –best : These options change the compression level used, with the -1 option being the fastest, with less compression, and the -9 option being the slowest, with optimal compression. The default compression level is 6.

連到傳送端的 server 並且接收 partition image

$ nc 192.168.0.1 3333 | pv -b > myhdb5partition.img.gz

傳送資料夾

壓縮檔案並傳到遠端主機

$ tar -czf - /etc/ | nc -l 3333

接收檔案

$ nc 192.168.0.1 3333 | pv -b > mybackup.tar.gz

使用 ssh 傳送檔案

有兩個好處 :

  1. 資料透過加密的通道傳送更為安全
  2. 你不需要在額外開 port, 因為使用的是 ssh port

You pipe the file to a listening socket on the server machine in the same way as before. It is assumed that an SSH server runs on this machine too. listen 一個 port 並以 pipe 傳送檔案 (假設 SSH server 已經在這台啟動了)

$ cat backup.iso | nc -l 3333

On the client machine connect to the listening socket through an SSH tunnel: client server 以 SSH 連到傳送端的主機

$ ssh -f -L 23333:127.0.0.1:3333 me@192.168.0.1 sleep 10; \
                nc 127.0.0.1 23333 | pv -b > backup.iso
  • 這個方式可以設定當檔案傳送完後自動關閉連線
  • ssh -f : Requests ssh to go to background just before command execution. This is useful if ssh is going to ask for passwords or passphrases, but the user wants it in the background.
  • ssh -L [bind_address:]port:host:hostport : Specifies that the given port on the local (client) host is to be forwarded to the given host and port on the remote side. This works by allocating a socket to listen to port on the local side, optionally bound to the specified bind_address.

監聽一個範圍 port 的 connection

可以當作 Port Scanner

使用 -z, netcat 不會 initiate 一個連線到 server, 只會通知這個 port 有找到, 它可以允許 port-range 去掃

例如掃 80~90 的 port, 發送 80 port 有打開 :

$ nc -z 192.168.0.1 80-90
Connection to 192.168.0.1 80 port [tcp/http] succeeded!

ref : http://linux.vbird.org/linux_server/0140networkcommand.php#nc http://www.g-loaded.eu/2006/11/06/netcat-a-couple-of-useful-examples/

解析 TCP 三向交握(Three-way Handshake)封包

瀏覽器向 server 發送請求 :

瀏覽器發送 : 指定 seq

22:39:13.832224 IP 42.79.253.189.29729 > 172.31.23.23.8080: Flags [S], seq 3605791234, win 65535, options [mss 1394,nop,wscale 4,nop,nop,TS val 401990828 ecr 0,sackOK,eol], length 0

server 回應 : 將 seq+1 放到 ack, 並且指定 seq

22:39:13.832271 IP 172.31.23.23.8080 > 42.79.253.189.29729: Flags [S.], seq 4067821211, ack 3605791235, win 28960, options [mss 1460,sackOK,TS val 333436167 ecr 401990828,nop,wscale 7], length 0

瀏覽器發送 : ack = 1 用來回應上一個封包並且確認封包正確

22:39:13.943021 IP 42.79.253.189.29729 > 172.31.23.23.8080: Flags [.], ack 1, win 8205, options [nop,nop,TS val 401990926 ecr 333436167], length 0

瀏覽器發送 : Push 表示此封包應立即傳送不需等緩衝區滿了才送, seq : 1:321 代表從 1 開始, 並且加上長度 320 的 header

22:39:13.943692 IP 42.79.253.189.29729 > 172.31.23.23.8080: Flags [P.], seq 1:321, ack 1, win 8205, options [nop,nop,TS val 401990927 ecr 333436167], length 320

Browser request headers (320 bytes of data) :


GET / HTTP/1.1
Host: 54.250.138.78:8080
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-tw
Connection: keep-alive
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9) AppleWebKit/537.71 (KHTML, like Gecko) Version/7.0 Safari/537.71

server 回應 : ack : 321 表示收到剛剛瀏覽器傳來的資料

22:39:13.943711 IP 172.31.23.23.8080 > 42.79.253.189.29729: Flags [.], ack 321, win 235, options [nop,nop,TS val 333436194 ecr 401990927], length 0

server 回應網頁內容 :

server 回應 : seq 從 1 開始並加上傳送 64 字元的長度, ack 為剛剛收到瀏覽器 header 的 seq:321

22:39:35.943037 IP 172.31.23.23.8080 > 42.79.253.189.29729: Flags [P.], seq 1:65, ack 321, win 235, options [nop,nop,TS val 333441694 ecr 401990927], length 64

server 像 browser 發送的 header 資料 :


server response header (64 bytes of data) :

HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/html

fghd

server 回應 : 資料傳送結束, 並等待結束回應

22:39:35.943086 IP 172.31.23.23.8080 > 42.79.253.189.29729: Flags [F.], seq 65, ack 321, win 235, options [nop,nop,TS val 333441694 ecr 401990927], length 0

瀏覽器發送 : 跟 server 說剛收到的 seq 為 65

22:39:36.067513 IP 42.79.253.189.29729 > 172.31.23.23.8080: Flags [.], ack 65, win 8201, options [nop,nop,TS val 402012885 ecr 333441694], length 0

瀏覽器發送 : 與上個封包一樣, 重複送了一次

22:39:36.067565 IP 42.79.253.189.29729 > 172.31.23.23.8080: Flags [.], ack 66, win 8201, options [nop,nop,TS val 402012885 ecr 333441694], length 0

瀏覽器向 server 發送結束連線

瀏覽器發送 : 回應剛剛 server 請求的結束, ack 66 為 seq 65 + 1, seq 321 為 ack 321

22:39:44.832134 IP 42.79.253.189.29729 > 172.31.23.23.8080: Flags [F.], seq 321, ack 66, win 8201, options [nop,nop,TS val 402021471 ecr 333441694], length 0

server 回應 : ack = seq + 1

22:39:44.832200 IP 172.31.23.23.8080 > 42.79.253.189.29729: Flags [.], ack 322, win 235, options [nop,nop,TS val 333443917 ecr 402021471], length 0

狀態代碼

  • CWR : CWR (Congestion Window Reduced) flag 為 1 代表接到 ECE 旗標 為 1 的 TCP 封包。
  • ECE : ECE (ECN-Echo) flag 為 1 代表 TCP peer (對等體) 具備 ECN (Explicit Congestion Notification, 明確擁塞通知) 功能,同時 IP 封包中的 ECN 欄位被設定為 11。
  • URG : URG (Urgent) flag 為 1 代表緊急封包,接收端應優先處理。
  • ACK : ACK (Acknowledgment) flag 為 1 代表此封包的 Acknowledgment Number 是有效的﹐是用來回應上一個封包。
  • PSH : PSH (Push function) flag 為 1 代表此封包連同緩衝區的其它相關封包應立即進行傳送,而無需等待緩衝區滿了才送。
  • RST : RST (Reset) flag 為 1 代表馬上結束連線,無需等待終止確認手續。
  • SYN : SYN (Synchronize) flag 為 1 代表要求雙方進行同步處理,也就是要求建立連線。
  • FIN : FIN (Finish) flag 為 1 代表資料傳送結束,等待結束回應,以便正式結束 TCP 傳送流程。

ref : http://120.105.184.250/peiyuli/network-3/%E7%B6%B2%E8%B7%AF%E7%B0%A1%E4%BB%8B%E8%88%87%E6%8C%87%E4%BB%A4.htm

Netstat 指令

常用

  • -t: tcp
  • -u: udp
  • -n: don’t resolve names
  • -l: listening
  • -p: show pid or program name
  • -i: Display a table of all network interfaces.
  • -a: Show both listening and non-listening sockets.
  • 如果 netstat / lsof 無法顯示出 pid or program name 要改用 sudo 執行

列出在監聽的網路服務:

netstat -tunl

列出所有的連線

netstat -antp

列出已連線的網路連線狀態:

netstat -tun

刪除已建立或在監聽當中的連線:

netstat -tunp   #看他的pid ex: 10270
kill -9 10270

netstat -nltp

查看 port 被那隻程式佔用

lsof -i :3306

查看有些建立連線 (4 stands for ipv4)

lsof -i 4

查看某個 process 建立的連線

lsof -p 12084

找出哪個 pid 開啟某個特定的檔案

lsof -t /tmp/qq.log

Show process name, pid and port

$ lsof -i -n -P | grep TCP
Spotify   60924  jex   47u  IPv4 0x5f4ad82c61e46fc7      0t0  TCP 127.0.0.1:4381 (LISTEN)
Spotify   60924  jex   48u  IPv4 0x5f4ad82c620734df      0t0  TCP 127.0.0.1:4371 (LISTEN)

顯示 timer

$ netstat -antop

tcp        0      0 0.0.0.0:55206           0.0.0.0:*               LISTEN      -                off (0.00/0/0)
tcp        0      0 10.173.27.211:11211     0.0.0.0:*               LISTEN      -                off (0.00/0/0)
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      -                off (0.00/0/0)
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                off (0.00/0/0)
tcp        0      0 182.92.229.142:40879    110.75.102.62:80        ESTABLISHED -                off (0.00/0/0)
tcp        0      0 10.173.27.211:58364     10.171.39.175:22        TIME_WAIT   -                timewait (15.42/0/0)
tcp        0      0 182.92.229.142:22       125.227.251.150:51042   ESTABLISHED -                keepalive (5318.20/0/0)
tcp        0      0 182.92.229.142:22       125.227.251.150:56130   ESTABLISHED -                keepalive (5187.13/0/0)
tcp        0      0 10.173.27.211:2049      10.171.39.175:825       ESTABLISHED -                off (0.00/0/0)
tcp        0      0 10.173.27.211:52061     10.242.174.13:80        TIME_WAIT   -                timewait (48.05/0/0)
tcp        0      0 10.173.27.211:2049      10.170.189.142:696      ESTABLISHED -                off (0.00/0/0)
tcp6       0      0 :::56705                :::*                    LISTEN      -                off (0.00/0/0)
tcp6       0      0 :::2049                 :::*                    LISTEN      -                off (0.00/0/0)

顯示連線狀態數量

$ netstat -an|awk '/tcp/ {print $6}'|sort|uniq -c

     16 CLOSING
    130 ESTABLISHED
    298 FIN_WAIT1
     13 FIN_WAIT2
      9 LAST_ACK
      7 LISTEN
    103 SYN_RECV
   5204 TIME_WAIT

狀態: 描述

  • ESTABLISHED: The socket has an established connection.
  • SYN_SENT: The socket is actively attempting to establish a connection.
  • SYN_RECV: A connection request has been received from the network.
  • FIN_WAIT1: The socket is closed, and the connection is shutting down.
  • FIN_WAIT2: Connection is closed, and the socket is waiting for a shutdown from the remote end.
  • TIME_WAIT: The socket is waiting after close to handle packets still in the network.
  • CLOSE: The socket is not being used.
  • CLOSE_WAIT: The remote end has shut down, waiting for the socket to close.
  • LAST_ACK: The remote end has shut down, and the socket is closed. Waiting for acknowledgement.
  • LISTEN: The socket is listening for incoming connections.
  • CLOSING: Both sockets are shut down but we still don’t have all our data sent.
  • UNKNOWN: The state of the socket is unknown.

https://i.stack.imgur.com/5H0Cr.png

大量 TIME_WAIT 問題

(..略..)
tcp        0      0 127.0.0.1:9000              127.0.0.1:37260             TIME_WAIT   -
tcp        0      0 10.0.11.229:58492           10.0.23.32:3306             TIME_WAIT   -
(..略..)

問題 :

php-fpm 使用 9000 port, mysql 使用 3306 如果一個 request 進來最少就會有兩個 TIME_WAIT,要等系統 1 分鐘才會回收,在這期間就佔用了兩個 TCP 連線, 如果 request 量大的時候就會沒 port 可用造成錯誤

產生原因

ref : 打開linux-tcp端口快速回收

  1. nginx現有的負載均衡模塊實現php fastcgi負載均衡,nginx使用了短連接方式,所以會造成大量處於TIME_WAIT狀態的連接。
  2. TCP/IP設計者本來是這麼設計的, 主要有兩個原因
    • 防止上一次連接中的包,迷路後重新出現,影響新連接(經過2MSL,上一次連接中所有的重複包都會消失)
    • 可靠的關閉TCP連接, 在主動關閉方發送的最後一個ack(fin) ,有可能丟失,這時被動方會重新發fin, 如果這時主動方處於CLOSED 狀態,就會響應rst 而不是ack。所以主動方要處於TIME_WAIT 狀態,而不能是CLOSED 。

解決方法 : 開啟 Linux TCP port 快速回收

修改 /etc/sysctl.conf, 但重啟後會還原(?)

net.ipv4.tcp_tw_reuse = 1                   # Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0.  It should not be changed without advice/request of technical experts.
net.ipv4.tcp_tw_recycle = 1                 # Enable fast recycling TIME-WAIT sockets. Default value is 0.  It should not be changed without advice/request of technical experts.

再執行 sudo sysctl -p 生效

注意!! 修改 tcp_tw_recycle 在使用 load balancer 可能會造成問題 ref : 記一次TIME_WAIT網絡故障 下面別人的回覆訂正原文錯誤也值得一看

結果

當 request 一執行完畢 TIME_WAIT 只會存在一下子就消失了

TIME_WAIT 補充

TCP is layer 4, HTTP layer 7.

In HTTP 1.0, HTTP Keep-Alive is used at layer 7 to simulate persistent connections using Connection header.

In HTTP 1.1, connections are assumed persistent by default and then rely on TCP only to do that job. Requests can be pipelined in the same TCP connection, then one side will set Connection: close in the last request or response headers, so both side knows that no more HTTP request can be exchanged and the connection will then be closed.

Usually in the case of a web server, the TIME_WAIT state will be the state after which, once decided to actively close the connection, it received client’s FIN packet and is sending the last ACK back in the four-way tear-down. After this, it waits for 2 * MSL : it’s a way to be sure that the connection is closed. That’s where the 60s compiled in the kernel comes from. In this way we are sure that we won’t receive in a new connection, using the same 4 tuple, packets out of sequence arising from the previous connection.

You don’t want to change it.

In the other side server.max-keep-alive-idle is the timeout after which an ESTABLISHED connection will be considered idle if no HTTP request comes in and will be actively closed by the web server. When this decision is made, as you understand now, the TCP tear-down will take place.

Be very careful with tcp_tw_recycle, if your visitors come from behind a wide NATed network then it could lead to multiple TCP connections with the same 4 tuple taking place with out of order timestamps resulting in silently dropping client connections attempts on the server side.

So the best option is to adjust the parameter you saw in lighttpd. System-wide, you can safely lower FIN_WAIT2 state and raise buckets for sockets in TIME_WAIT state with net.ipv4.tcp_fin_timeout and net.ipv4.tcp_max_tw_buckets.

其他指令

netstat command example to find out gateway/router IP

netstat -r -n

or

route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         192.168.79.xxx  0.0.0.0         UG    0      0        0 em1
192.168.79.0    *               255.255.255.128 U     0      0        0 em1

顯示主機上所有已建立的連線 (己開放的port)

netstat -na

顯示所有 port 80 的連線,並把結果排序。

netstat -an | grep :80 | sort

列出主機上有多少個 SYNC_REC,一般上這個數字應該相當低。

netstat -n -p|grep SYN_REC | wc -l

同樣是列出 SYNC_REC,但不只列出數字,而是將每個 SYNC_REC 的連線列出。

netstat -n -p | grep SYN_REC | sort -u

列出發送 SYNC_REC 的所有 ip 地址。

netstat -n -p | grep SYN_REC | awk ‘{print $5}’ | awk -F: ‘{print $1}’

計算每一個 ip 在主機上建立的連線數量。

netstat -ntu | awk ‘{print $5}’ | cut -d: -f1 | sort | uniq -c | sort -n

列出從 TCP 或 UDP 連線到主機的 ip 的數量。

netstat -anp |grep ‘tcp\|udp’ | awk ‘{print $5}’ | cut -d: -f1 | sort | uniq -c | sort -n

列出每個 ip 建立的 ESTABLISHED 連線數量。

netstat -ntu | grep ESTAB | awk ‘{print $5}’ | cut -d: -f1 | sort | uniq -c | sort -nr

列出每個 ip 建立的 port 80 連線數量。

netstat -plan|grep :80|awk {‘print $5′}|cut -d: -f 1|sort|uniq -c|sort -nk 1

ref: http://www.hkcode.com/linux-bsd-notes/559

Windows 安裝 Gcc 編譯器 - MinGW

MinGW 介紹

MinGW 全稱 Minimalist GNU For Windows,是個精簡的Windows平台C/C++、ADA及Fortran編譯器,相比Cygwin而言,體積要小很多,使用較為方便。MinGW提供了一套完整的開源編譯工具集,以適合Windows平台應用開發,且不依賴任何第三方C運行時庫。

MinGW包括:

  • 一套集成編譯器,包括C、C++、ADA語言和Fortran語言編譯器
  • 用於生成Windows二進製文件的GNU工具的(編譯器、鏈接器和檔案管理器)
  • 用於Windows平台安裝和部署MinGW和MSYS的命令行安裝器(mingw-get)
  • 用於命令行安裝器的GUI打包器(mingw-get-inst)

下載 MinGW

mingw 下載頁面 上面應該有可以直接下載最新版的連結


Looking for the latest version? Download mingw-get-setup.exe (86.5 kB)


安裝套件 - C/C++ 編譯器

安裝完後會跳出一個視窗讓你選擇你要安裝的套件,

mingw32-gcc-g++ 上按右鍵選擇 Mark for Installation 把它勾選起來

gc  .JPG

然候左上角 Installation 選擇 Apply Change 就會開始安裝 C/C++ 編譯器了,

設定系統環變數

安裝完後要將 GCC 的路徑 C:\MinGW\bin 加到系統環境變數 PATH, 才能使用,

我的電腦右鍵 -> 內容 -> 進階系統設定 -> 進階TAB, 點選在下面的環境變數 -> 在下面的系統變數那新增


變數名稱 : PATH 變數值 : %SystemRoot%\system32;%SystemRoot%;C:\MinGW\bin


重開機, 讓變數生效

測試是否安裝完成

可以在 cmd 輸入 gcc -v, 如果出現 gcc 的相關資訊表示安裝成功!

gccVersion.JPG

windows 8

如果 windows 8 安裝完 MinGW 是沒有 C:\MinGW\bin\gcc.exe 而是 C:\MinGW\bin\g++.exe

所以 cmd 指令要改下 g++ -v

Compile C++ 檔案

C:\MinGW 下新增 hello.cpp :

#include <iostream>  // Basic input and output library
#include <cstdlib>   // system("pause")

using namespace std;

int main()
{
    cout << "Hello World" << endl;
    system("pause");
    return 0;
}

cmd :

cd C:\MinGW
g++ hello.cpp -o hello.exe

cc1plus.exe - 系統錯誤

無法啟動程式,因為您的電腦遺失 libgmp-10.dll。請嘗試重新安裝以修正這個問題。

error.PNG

再打開安裝檔 MinGW Installation Manager 或 C:\MinGW\bin\mingw-get.exe, 他們是一樣的東西

然候再安裝 mingw32-base, 即可解決此問題

install mingw32-base.PNG

ref: MinGW windows go mingw c

Raspberry Pi 作業系統, XBMC 影音, Samba 網芳

介紹

raspberry-pi.jpg

Raspberry Pi 是一台小型的電腦, 幾乎一般 linux 電腦能做到的事它也都能做到, 而它最大的優勢就是體型非常的小, 小到你可以方便攜帶, 而功能一點也不陽春, 非常適合拿它來作媒體中心或小型的運算。

如果還沒有 Raspberry Pi(以下簡稱 rpi) 不知道哪些要買的可以參考本文末的推薦清單

安裝 OS(作業系統)

[下載 OS 映像檔]

下載 Raspberry Pi 的 OS 選擇 Raspbian 版本是 wheezy

下載完後解壓縮出來是一個 2013-09-25-wheezy-raspbian.img 檔

[將 Raspbian OS 燒錄到 SD 卡]

不同的作業系統要用不同的方式燒到 SD 卡, 先將 SD 卡插入到電腦

如果是 Windows 下載 Win32 Disk Imager, 解壓縮完是一個資料夾, 執行 Win32DiskImager, Image File 選擇 2013-09-25-wheezy-raspbian.img

Device 選擇 SD 卡, 按 write, 就會進行燒錄了

[安裝 Raspbian]

將燒錄好的 SD 卡插入 Raspberry pi, 並且將電源線插上插座, 鍵盤接上, HDMI線接上電視或電腦螢幕,

就可以開始使用了,基本上 SD 卡燒進去的檔案已安裝的差不多了, 剩下的只是一些設定的調整,

例如第一項是檢查你的 SD 卡有沒有讓整個 OS 使用, 還有修改密碼的設定, 其他的可以不用設定直接跳過


如果像我一樣接上螢幕沒有畫面, 試試看 rpi 把 SD 卡拔出來再插到你的電腦, 打開 SD 卡的 config.txt 並加上 :

sdtv_mode = 0
sdtv_aspect = 3
hdmi_group = 1
hdmi_mode = 1
sdtv_aspect=3  16:9

再插回 rpi 開機後螢幕應該就會有畫面了


  • Raspberry Config GUI : sudo raspi-config
  • 預設帳密 : pi/raspberry

安裝 Raspbmc (也就是 Raspberry 版的 XBMC)

跟作業系統一樣下載完要燒到 SD 卡裡, 點我下載

所以要安裝前先關機, 將 SD 卡從 rpi 拔下來, 然候再插到你的電腦,

接著打開剛剛下載的軟體, 在上面選擇你的 SD 卡位置, 及下面打勾 Install Raspbmc to a USB drive 將 raspbmc 燒進 SD 卡

然候再把 SD 卡插到 rpi, 如果有接上螢幕開機後就會自動安裝, 並且進入到 XBMC 畫面了


如果發生錯誤: Raspbmc cannot be installed to a USB drive, because it cannot detect it!

將 SD 卡插到你電腦然候檢查 SD 卡有沒有檔名叫做 usb 的檔案, 有的話刪除它, 再重新插回 rpi 啟動就能自動安裝了


[搖控 XBMC 的方式]

Remote default port : 9090, 如果有設定防火牆要記得打開 9090 port

[瀏覽器] :

Chrome extension 安裝 XBMC

先用鍵盤在 XBMC 操控, 因為要開啟遠端遙控, 才可以用 chrome 的 extension 控制

系統設定 -> 服務 -> 遠端遙控 -> 允許其他電腦連接

在開啟 chrome 的 extenstion -> IP 輸入 rpi 的 ip (如果不知道可以到 系統設定 -> 系統資訊 看 ip 是什麼,

[手機] :

手機用 wifi 連到家裡的無線網路 (為了要跟 rpi 同一個 lan 下)

下載 Official XBMC Remote 這個 app, IP 輸入後就可以操控了

[操作界面]

滑鼠操作

system -> settings -> appearance -> skin 改為 `Touched`

鍵盤操作

system -> settings -> appearance -> skin 改為 `Raspbmc Confluence Mod`

[XBMC 中文語系]

如果亂碼, 有些操作的項目根本無法顯示, 跟著以下操作至少能切回英文

system -> settings(第一項) -> appearance(第一項) -> International(第二項) -> Language 改為 `English(US)`

改成中文語系前先將 Fonts 由 Skin Default 改為 Arial based 才不會改了中文後就變成亂碼了

system -> settings -> appearance -> skin -> Fonts 改為 `Arial Based`

改成中文語系

system -> settings -> appearance -> international -> Language 改為 Chinese(Tranditional)

[XBMC 指令]

  • sudo initctl start xbmc
  • sudo initctl stop xbmc
  • sudo initctl restart xbmc

[用 XBMC 看線上影片(ex: PPS)]

[下載觀看線上影片的套件]

xbmc-addons-chinese 選擇 repository.googlecode.xbmc-addons-chinese-eden.zip 複製連結網址

下載到 rpi 裡

cd /tmp
wget https://xbmc-addons-chinese.googlecode.com/files/repository.googlecode.xbmc-addons-chinese-eden.zip

在下載時會發現有分 eden 與 Dharma 版, 它們是指版本代號, Dharma 是 10, eden 是 11

像我的是 12.2 版, 安裝 eden 沒問題

如果要查看 XBMC 版本 : XBMC 啟動後 -> 系統設定 -> 糸統資訊 就會顯示你的 XBMC 版本


[安裝套件]

系統設定 -> 附加元件 -> 從 zip 檔案安裝 -> 檔案系統 -> 剛是下載到 /tmp 所以選擇 /tmp -> repository.googlecode.xbmc-addons-chinese-eden.zip

[啟用你想開啟的服務 (ex: pps)]

系統設定 -> 附加元件 -> 取得附加元件 -> 全部附加元件 -> 視訊附加元件 -> 往下找 PPStream (最下面是中文的服務, 騰訊視頻等等)

  • 撥放含有字幕檔的影片也沒有問題, 將 XXX.mkv XXX.srt 放在同一個目錄, 撥放時 XBMC 自動會載入字幕
  • 不支援 .rmvb 的檔, 播放時只會有聲音不會有畫面

[從 XBMC 回到 terminal]

在 XBMC 的介面左下角有電源的 icon, 點它並選擇第一個 “關閉”, 然候再按 esc, 就

可以回到 terminal 介面了

ctrl+alt+F1 似乎也行(沒試過)

安裝及設定 samba

如果 windows 要丢檔案到 rpi 裡就可以透過 samba, 例如把影片丢進去再使用 XBMC 播放在電視上看

參考 samba 安裝

\\192.168.74.65\share), 安裝完後在 /home/pi 下建立資料夾 samba

/etc/samba/smb.conf 最下面加入

[samba]
    path = /home/pi/samba
    public = yes
    writable = yes
    printable = no

重啟

sudo service samba restart
  • 如果有設定防火牆記得開 445 port
  • 網址 : \\192.168.1.10\samba

設定無線網路

ifconfig -a 看有沒有 wlan0, 這個就是你的無線網卡, 如果沒有執行 lsusb 看 usb 的無線網卡有讀到嗎?

pi@raspbmc:~$ lsusb
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 004: ID 0bda:8176 Realtek Semiconductor Corp. RTL8188CUS 802.11n WLAN Adapter

這是我的 usb 無線網卡 : RTL8188CUS 802.11n WLAN Adapter

如果都有確定有讀到就可以開始設定無線網路了, 可參考設定無線網路

推薦購買的清單 :

Raspberry pi 主機板 Model B (512mb) : $1390

另一個版本 Model A(256mb), 硬體相對較小, usb 插槽只有一個

壓克力外殼(6片裝密合式) + 3個散熱片 : $100

另一種是開放式的, 上下各一片壓克力, 4 個角落以柱子固定

電源(5V2A, micro usb 線) : $100

建議買, 否則就得用電腦透過 usb 線供電給主機板

8GB SD卡 : $200

必買! 對 Raspberry Pi 來說, 它就相當於一般電腦的硬碟

Mini wifi 無線網卡(802.11N) : $200

  • 注意! 網卡需與主機版相容
  • 非必要買, 可直接用網路線接主機版

HDMI v1.4 傳輸線 : $100

如果要輸出到電視上看, 而且電視也支授 HDMI 的話就要買

USB PC 搖控器 : $200

建議買, 不然就得接鍵盤到主機板或者透過 chrome extension - xbmc 才能操作 XBMC

Q & A

鍵盤輸入某些符號(ex: #, @)keycode錯亂

sudo dpkg-reconfigure keyboard-configuration

選擇 Generic 105-key (Intl) PC -> 裡面的選項都是 UK , 所以選擇 Other -> English (US) -> layout, 選擇 English (US) -> The default for the keyboard layout (default) -> No compose key (default) -> 組合鍵 X server, 選擇 Yes

重開機

sudo shutdown -r now