Jex’s Note

Golang Websocket

Introduction

目前主要 golang 的 websocket 套件有兩個, 分别是官方維護的 code.google.com/p/go.net/websocket,

及非官方版本的 github.com/gorilla/websocket, 我也不知道哪個比較好,

這邊有個比較表可以參考看看

官方版的在使用 go get 下載前還需要先下載 Mercurial 這個版控, 否則無法下載

以下分别使用這兩種套件實現 websocket 的 example

HTML + JS :

<!DOCTYPE html>
<head>
    <title>Test~</title>
</head>
<body>
<script type="text/javascript">
    ws = new WebSocket("ws://54.250.138.78:9090/connws/");
    ws.onopen = function() {
        console.log("[onopen] connect ws uri.");
        var data = {
            "Enabled" : "true"
        };
        ws.send(JSON.stringify(data));
    }
    ws.onmessage = function(e) {
        var res = JSON.parse(e.data);
        console.log(res);
    }
    ws.onclose = function(e) {
        console.log("[onclose] connection closed (" + e.code + ")");
        delete ws;
    }
    ws.onerror = function (e) {
        console.log("[onerror] error!");
    }
</script>
</body>
</html>

[1] 以 go.net/websocket 實作

main :

http.Handle("/connws/", websocket.Handler(ConnWs))
err := http.ListenAndServe(":9090", nil)
if err != nil {
    log.Fatal("ListenAndServe: ", err)
}

func ConnWs(ws *websocket.Conn) {
    var err error
    rec := map[string]interface{}{}

    for {
        err = websocket.JSON.Receive(ws, &rec)
        if err != nil {
            fmt.Println(err.Error())
            ws.Close()
            break
        }
        fmt.Printf("Server received : %v\n", rec)

        if err = websocket.JSON.Send(ws, rec); err != nil {
            fmt.Println("Fail to send message.")
            ws.Close()
            break
        }
    }
}

result :

$ go run main.go
Server received : map[Enabled:true]

[2] 以 gorilla/websocket 實作

main :

http.HandleFunc("/connws/", ConnWs)
err := http.ListenAndServe(":9090", nil)
if err != nil {
    log.Fatal("ListenAndServe: ", err)
}

func ConnWs(w http.ResponseWriter, r *http.Request) :

ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
if _, ok := err.(websocket.HandshakeError); ok {
    http.Error(w, "Not a websocket handshake", 400)
    return
} else if err != nil {
    log.Println(err)
    return
}

rec := map[string] interface{}{}
for {
    if err = ws.ReadJSON(&rec); err != nil {
        if err.Error() == "EOF" {
            return
        }
        // ErrShortWrite means that a write accepted fewer bytes than requested but failed to return an explicit error.
        if err.Error() == "unexpected EOF" {
            return
        }
        fmt.Println("Read : " + err.Error())
        return
    }
    rec["Test"] = "tt"
    fmt.Println(rec)
    if err = ws.WriteJSON(&rec); err != nil {
        fmt.Println("Write : " + err.Error())
        return
    }
}

result :

$ go run main.go
map[Enabled:true Test:tt]

有特別處理 io error 的 EOF, 否則頁面 refresh 會陷入無窮迴圈

Cross domain

原本是使用官方的版本, 直到有 cross domain 的需求,

HTML5 websocket 本身是支持 cross domain 的,

但是官方版本不知道怎麼去支持它, 一直得到 403,

最後發現 gorilla/websocket 是支持的, 就改用它來達成

Golang Template

template 來源可以是字串也可以是一個檔案(html or text) 透過 html/template 或 text/template 將內容取代

取代的 HTML template 為字串

package main

import (
    "html/template"
    "log"
    "net/http"
)

const tmpl = `
<html>
    <head>
        <title></title>
    </head>
    <body>

    </body>
</html>`

func tHandler(w http.ResponseWriter, r *http.Request) {
    t := template.Must(template.New("ex").Parse(tmpl))
    v := map[string]interface{}{
        "Title": "Test <b>World</b>",
        "Body":  template.HTML("Hello <b>World</b>"),
    }
    t.Execute(w, v)
}

func main() {
    http.HandleFunc("/", tHandler)
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

瀏覽器得到 server 輸出的內容 :

<html>
    <head>
        <title>Test &lt;b&gt;World&lt;/b&gt;</title>
    </head>
    <body>
        Hello <b>World</b>
    </body>
</html>

可以看到 title 跟 body 的結果截然不同, title 的 html 標籤會被轉義, 而 body 沒有是因為我加上了 template.HTML() 不要轉義 HTML 標籤, 我覺得預設轉義設計是很好的, 防止沒注意而產生了 XSS 漏洞, rails 也是預設轉義的, 但 php 什麼時候才要改!!!??? (怒

當改成 t.Execute(os.Stdout, v), 表示是系統的標準輸出, 所以只會在 terminal 上看到輸出, 瀏覽器則不會有任何內容

取代的 HTML template 為檔案

將 header 與 footer 做為固定的 template, 並傳入動態內容 :

func tHandler(w http.ResponseWriter, r *http.Request) {
    t, _ := template.ParseFiles("header.tmpl", "body.html", "footer.tmpl")
    var data = map[string] interface{}{
        "content" : "Do you copy?",
    }
    t.ExecuteTemplate(w, "body", data)
    t.Execute(w, nil)
}

header.tmpl :

<html>
<head>
    <title>Video downloader</title>
</head>
<body>

body.html :

<h1 id="go">Golang Web Works!</h1>

footer.tmpl :

</body>
</html>

最後在瀏覽器顯示的結果是 :

<html>
<head>
    <title>Video downloader</title>
</head>
<body>

<h1 id="go">Golang Web Works!</h1>
Do you copy?

</body>
</html>

副檔名不需要一定是 .html .tmpl, 你可以自訂任何你喜歡的 template.ParseFiles 只負責引入檔案而已, 而真正負責要呈獻出來的內容及 parse 是 t.ExecuteTemplate

For loop template

有時候我們會重覆使用同一個 template 連續印出10次, 作法如下

作法 1, 用 range 直接在 template 跑 10 次

view/nameList.tmpl :

Name list :

 -> 

func main :

var tmp bytes.Buffer
var data = map[string]interface{}{}
t, _ := template.ParseFiles(
    "view/nameList.tmpl",
)
data["nameList"] = []string{"Jex", "Bob"}
t.ExecuteTemplate(&tmp, "nameList", data)
fmt.Println(tmp.String())
作法2, 先跑 10 次的 template, 再將生成後的 html 傳到 body 再輸出

建立要重覆的 urlItem.tmpl :

<li id="url-"></li>

body.html :

<ul class="list-group">

</ul>

完整程式碼 :

// Show view
var tmplPath string = "view/template/"
var indexPath string = "view/index/"
t, _ := template.ParseFiles(
    tmplPath + "header.tmpl",
    indexPath + "body.html",
    tmplPath + "index/urlItem.tmpl",
    tmplPath + "footer.tmpl",
)

// For loop url item
var tmplBuf bytes.Buffer                            // 建立 buffer, 等等 for loop 的 template 都存>進來
var nums = map[string] interface{}{}                // 建立 data interface{}
for num := 1; num <= 10; num++ {
    nums["num"] = num
    t.ExecuteTemplate(&tmplBuf, "urlItem", nums)    // 將數字帶進 template
}
data["urlItem"] = template.HTML(tmplBuf.String())   // 將 for loop template 的結果存進 urlItem, 並>且不要 escape HTML Tag
t.ExecuteTemplate(w, "body", data)
t.Execute(w, nil)

ref : http://play.golang.org/p/Uw8l3M7Qvg https://groups.google.com/forum/#!topic/golang-nuts/8L4eDkr5Q84 http://blog.xcai.net/golang/templates

取代的 TEXT template 為檔案

// 讀取資料夾底下的 template (不需要指定檔名)
var emailBodyTemplates = template.Must(template.ParseGlob("app/template/email/en/*"))     // 資料夾底下有很多 .txt 的 template 檔案
var tmp bytes.Buffer
err = emailBodyTemplates.ExecuteTemplate(&tmp, templateName, replacement)
if err != nil {
    return
}

// 讀取檔案的 template (需要指定檔名)
var emailLayoutTemplates = template.Must(template.ParseFiles("app/template/layout/email.html"))
err = emailLayoutTemplates.Execute(&tmp, replacement)

不要脫逸 ' " 等 HTML 符號

template.HTML 包起來

improt "html/template"

var t = template.Must(template.New("ex").Parse(` pen.  pen.`))
data := map[string]interface{}{
    "name":  template.HTML("Tom's"),
    "name2": "Tom's",
}
t.ExecuteTemplate(os.Stdout, "ex", data)

result

Tom's pen. Tom&#39;s pen.

' -> &#39;

解法2: improt 改成 text/template, 就不需要用 template.HTML 防止脫逸了

Golang 建立 GUI 應用程式

therecipe/qt

Install on mac

按照它建議 mac 安裝的順序, 其中 qt 我是下載 without Android or iOS support 這個版本

如果都安裝成功後跑 basic example 就可以看到 GUI 介面了

如果跑 $GOPATH/bin/qtsetup 遇到這個錯誤 Project ERROR: Could not resolve SDK Path for 'macosx'

執行 sudo xcode-select -s /Applications/Xcode.app/Contents/Developer, 再跑一次

Cross compile (未完成)

linux 似乎可以 cross compile 到 windows, 使用官方提供的 docker 並按步驟安裝 cross compile 需要的套件, 但一直遇到環境問題, 很多套件無法順利安裝, 便放棄了

Windows

安裝 walk 及 rsrc

1) Walk 是一款 golang 的 GUI Framework

2) rsrc 是可以將 manifest 嵌入執行檔的工具

cd c:\Go\bin                    # windows
go get github.com/lxn/walk
go get github.com/akavel/rsrc

c:\Go\bin 下建立官方範例

建立 gui.go :

package main

import (
    "github.com/lxn/walk"
    . "github.com/lxn/walk/declarative"
    "strings"
)

func main() {
    var inTE, outTE *walk.TextEdit

    MainWindow{
        Title:   "SCREAMO",
        MinSize: Size{600, 400},
        Layout:  VBox{},
        Children: []Widget{
            HSplitter{
                Children: []Widget{
                    TextEdit{AssignTo: &inTE},
                    TextEdit{AssignTo: &outTE, ReadOnly: true},
                },
            },
            PushButton{
                Text: "SCREAM",
                OnClicked: func() {
                    outTE.SetText(strings.ToUpper(inTE.Text()))
                },
            },
        },
    }.Run()
}

建立 gui.manifest

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
        <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/>
        <dependency>
            <dependentAssembly>
                <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
            </dependentAssembly>
        </dependency>
    </assembly>

為你的執行檔增加圖案

找一個喜歡的 .ico 圖檔

如果這步驟省略, 以下的步驟要記得把 -ico gui.ico 拿掉

建立一個產生執行檔的腳本

因為一直下指令很麻煩, 所以建立一個 gen_exe.bat, 改完 .go 檔, 執行它就可以幫你產生執行檔了

c:\Go\bin\gen_exe.bat :

set GOARCH=386
set GOOS=windows
c:\go\mygo\bin\rsrc.exe -manifest gui.manifest -ico gui.ico -o gui.syso
go build -ldflags="-H windowsgui"

加上 -ldflags="-H windowsgui" 產生的執行檔就不會有醜醜的 cmd 背景

產生執行檔

雙擊 gen_exe.bat, 會產生 bin.exe, GUI 就已經包在裡面了

icon.JPG

gui.JPG


Linux (Cross-Compile)

這個範例不包含 GUI

Install

go get github.com/akavel/rsrc

事前準備

1) 找一個喜歡的圖並轉成 .ico (選項, 可省略)

2) 建立 gui.manifest

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
        <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/>
        <dependency>
            <dependentAssembly>
                <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
            </dependentAssembly>
        </dependency>
    </assembly>

產生執行檔

1) 將gui.ico, gui.manifest 放同層目錄

2) 建立 cross-compile 的 shell 檔, 方便未來發佈

deploy.sh :

#!/bin/bash
if [ -f gui.syso ]; then
    rm gui.syso
fi

GOOS=linux     GOARCH=amd64 go build
GOOS=darwin    GOARCH=amd64 go build -o example.command
rsrc -manifest gui.manifest -ico gui.ico -o gui.syso
GOOS=windows   GOARCH=amd64 go build -o example.exe

如果沒有 .ico 圖, 要記得把 -ico gui.ico 拿掉

3) 執行

./deploy.sh

[註] Detection at compile time

golang 提供在 compile 時為不同環境準備不同的變數, 只要額外新增 _windows.go, _unix.go, 如下 :

Windows : /project/path_windows.go :

package project
const PATH_SEPERATOR = '\\'

Unix : /proejct/path_unix.go :

package project
const PATH_SEPARATOR = '/'

ref : https://inconshreveable.com/04-30-2014/cross-compiling-golang-programs-with-native-libraries/

Raspberry Pi Camera RTSP 影像串流

什麼是 RTP/RTCP/RTSP/RTMP

  • RTP (Real-time Transport Protocol) 是一種傳遞音訊和視訊的協定, 主要應用在 UDP 上, 大多用在一對一傳播
  • RTCP (Real-time Transport Control Protocol) 為 RTP 提供 out-of-band 控制, 本身不傳輸數據, 但和RTP一起協作將 media data 打包和發送
  • RTSP (Real Time Streaming Protocol) 控制聲音及影像的多媒體串流協定, 運作跟 HTTP/1.1 類似, 支援 Multicast, 不強調時間同步等特性, 通常處理 RTP 及 RTCP 協定使用, 可選擇 TCP 或 UDP 傳送
  • RTMP (Real Time Messaging Protocol) 是 Adobe Flash 的多媒體串流協定

RTMP vs RTSP

  • 做的事情基本是一樣的, rtsp 是公開的 protocal, rtmp 是 adobe 的 protocal
  • rtsp on web: Realplayer / Quicktime player / VLC player
  • rtmp on web: flash (目前直播 live 幾乎都走 rtmp)
  • youtube 是 vp8 (一種 codec 格式)
  • camera 幾乎都是走 h.264
  • rtmp 可以轉輸出 vp8 格式, 都會很吃 cpu

WebRTC

WebRTC Google 推的協定, 主要應用在 Browser 上的影印串流 i.e. 視訊聊天

  • 並不適合用在 device, 因為要 porting 在 device 端太複雜及困難, 不過成功後可以提供 p2p 的路線可以省頻寬
  • 需要另外架設 signal server, stun server, turn server
  • A STUN server is used to get an external network address. (A -> stun server <- B (A 跟 B 報 public ip))
  • TURN servers are used to relay traffic if direct (peer to peer) connection fails.
  • webrtc port 到 device 裡, 推流就都是走 webrtc protocal, web 也是 webrtc 的 protocal, device 不能走 rtmp -> WebRTC
  • Just to reiterate: TURN is used to relay audio/video/data streaming between peers, not signaling data!
  • signal server 是讓 WebRTC client 媒合配對用的, 確定雙方要開始連接後再各自報位置給 stun server 打洞, 如果打不通就改用 relay (turn server, turn 有自已的 protocal)

什麼是 live555

是一套 C++ 的函式庫, 可以實作出 RTSP/RTP server, VLC 是基於此函式庫開發的

支援傳輸方式

  • RTP over UDP
  • RTP over RTSP
  • RTP/RTSP over HTTP
  • 等等..

支援的影音格式

  • H.264
  • MPEG4
  • MP3
  • 等等..

Install live555

Easiest way is first ensure you have the appropriate compiler:

sudo apt-get install build-essential

then make sure you don’t have the repository live555 libraries on your system:

sudo apt-get remove liblivemedia-dev

and finally download, build and install the libraries:

cd /tmp
wget http://www.live555.com/liveMedia/public/live555-latest.tar.gz
tar xvf live555-latest.tar.gz
cd live
./genMakefiles linux
make
sudo cp -r /tmp/live /usr/lib
make clean

啟動 RTSP server

將影音檔 (h.264, mp4 etc..) copy 到 /usr/lib/live/mediaServer

h.264 副檔名為 .264, mp4 副檔名為 .m4e

執行

cd /usr/lib/live/mediaServer
./live555MediaServer

安裝並打開 VLC 播放器, 選擇播放網路串流,

輸入 : rtsp://54.250.138.78:8554/akb.m4e

建議在 /usr/lib/live/mediaServer 建立 my_video 資料夾, 將影音與程式分開 (rtsp://54.250.138.78:8554/my_video/akb.m4e)

用 Raspberry Pi camera 播放即時影像

此方法太沒效率了, 不建議使用

啟動 RTSP server 及接收 Raspberry Pi 傳來的資料儲存成檔案:

cd /usr/lib/live/mediaServer/my_video
touch rpi.264
nc -l 8080 | pv -b > rpi.264

將 camera 的影像傳到遠端 server :

raspivid -t 999999 -h 180 -w 270 -o - | pv -b | nc 54.250.138.76 8080

VLC -> open network : rtsp://54.250.138.78:8554/my_video/rpi.264

原本想要做一個能讓很多人同時看即時影像的 server, 所以將 Raspberry Pi 的串流丟到外面的 server, 但效果不太好, 傳輸速率很慢, 即使不用 wifi 傳輸也一樣很慢, 大概過 10 幾秒才能看到影像 (這已經是將影像的畫質調到很低的狀態下)

Q & A

發生錯誤 : StreamParser internal error (149997 + 4 > 150000) Aborted (core dumped)

原因 : 傳送一幀非常大的 H.264 影像 (default 150000) 造成的

查到的解決方法 :

修改 /usr/lib/live/liveMedia/StreamParser.cpp,

找到 BANK_SIZE 建議將值設為 150000~300000 之間

改完要 compile :

cd /usr/lib/live
sudo make
  • 但我仍然還是一樣發生此 buffer 問題, 但播放 mp3 及 aac 沒問題
  • 此情況似乎是發生在 H.264 parsing 才有的問題, 或是 mp4 都有這問題, 試了很多 mp4 影片, 都還是一樣發生
  • 這邊下載.ts 影片是能播放的

其他

現在多數瀏覽器支援播放 H.264 影片但可惜沒有瀏覽器的 HTML5 播放器支援播放 RTSP 串流影片, 所以只能透過像 VLC 這種播放器來播放,

目前 RTMP 應該算是 live streaming 的通用解, 看來要擺脫 flash 還有很長一段路要走

比較可惜的還有 webm (vp8) 無法普及, 不然它的編解碼效率也很不錯, 重要的是它是開源的

市面上常見的 player 比較 :

  • Realplayer : 原生不支援 H.264 影片, 必須安裝 quicktime plugin 才能對 H.264 解碼
  • Quicktime player : 不支援 RTP/AVP/TCP 傳輸, RTP/AVP (UDP) 傳輸不包括 NAT 打洞, 因此唯一適合的傳輸是 HTTP tunneling
  • VLC player : RTP/AVP 傳輸也不支援 NAT 打洞, 但可以用 RTP/AVP/TCP 傳輸

ref: http://ubuntuforums.org/showthread.php?t=1324290 http://www.raspberrypi.org/phpBB3/viewtopic.php?&t=52071

Raspberry Pi 使用 3.5G 網卡上網 (Sim卡)

介紹

RPI 本身如果要上網就只能連附近的 wifi 熱點, 這樣就會有地域上的限制,

想要脫離此限制, 可以使用 3.5G 無線網卡來上網,

這樣一來不但可以解決網路問題, 也能做出更好玩的應用,

想想如果可以將 RPI + camera + arduino + 無線網卡結合,

最基本就可以做到超遠端控制了, e.g. 具備即時影像的遠端搖控車


首先需要一個 RPI 支援的無線網卡, 需要注意的是並不是每家無線網卡 RPI 都支援,

看到蠻多的成功案例是華為 E173, 所以我也買此款,

如果要買其他廠牌型號的網卡建議先去 google 看看是否有人成功

檢查一下硬體有沒有讀到

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 12d1:1446 Huawei Technologies Co., Ltd. E1552/E1800/E173 (HSPA modem)
Bus 001 Device 005: ID 045e:0750 Microsoft Corp. Wired Keyboard 600

如果找不到, 安裝 3G 網卡驅動

sudo apt-get install usb-modeswitch usb-modeswitch-data

sudo apt-get install ppp

我沒有安裝過, 因為我的 pi 可以直接讀到我的 E173, 所以沒此問題, 這兩者都看到有人裝過, 如果 usb 讀不到的同學可以參考一下

安裝 Sakis

cd /tmp
wget "http://raspberry-at-home.com/files/sakis3g.tar.gz"
tar -zxv -f sakis3g.tar.gz
sudo chmod +x sakis3g
sudo mv sakis3g /usr/bin/

設定從 sim 卡讀取網路

sudo sakis3g --interactive

接著會跳出 GUI 的安裝選單, 選擇如下

-> Connect with 3G

-> 選擇 modem 的 USB 介面, 選擇 1. Interface #0

-> 輸入 pin 碼(你每次手機打開 sim 卡要你輸入的那個密碼)

-> Select APN, 選擇 Custom APN...

-> 輸入 APN : internet

-> 輸入 APN 密碼 : *99#

-> 再輸入一次 APN 密碼 : *99#

-> 成功 E173 connected to Chunghwa Telecom (46692).

檢查是否連線成功

ifconfig
(..略..)
ppp0      Link encap:Point-to-Point Protocol
          inet addr:100.102.97.230  P-t-P:10.64.64.64  Mask:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  Metric:1
          RX packets:10 errors:1 dropped:0 overruns:0 frame:0
          TX packets:12 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:3
          RX bytes:406 (406.0 B)  TX bytes:521 (521.0 B)

指令

連線 : sudo sakis3g connect

有可能會要你指定 usb 介面 : sudo sakis3g USBINTERFACE="0" connect

斷線 : sudo sakis3g disconnect

Info :

$ sudo sakis3g info
Connection Information

Interface: P-t-P (ppp0)

Connected since: 2014-01-19 13:16
Kilobytes received: 2
Kilobytes sent: 2

Network ID: 46692
Operator name: Chunghwa Telecom
APN: internet

Modem: E173
Modem type: USB
Kernel driver: option
Device: /dev/ttyUSB2

IP Address: 100.102.34.28
Subnet Mask: 255.255.255.255
Peer IP Address: 10.64.64.64
Default route(s): 10.64.64.64

[註] 用電腦測試 華為 E173 上網

插入後會要你安裝他們的程式 Mobile Partner, 安裝完後打開

會有讓你選擇你的通信商, 然後按連線

如果跟我一樣買的不是跟台灣的通信商合作的無線網卡, 像我的 E173 是跟國外合作的, 所以不會有台灣的通信商

所以要自行新增

Options -> Profile Management -> New 並在相對應的表格輸入以下 :

Profile Name    : CHT       (我的是中華電信, 取名為縮寫名稱)
APN             : 單選按鈕選擇 `Static`, 下面輸入 internet
Access Number   : *99#      (預設已填上, 如果沒有需自行輸入)
Authentication Protocol Settings : CHAP(預設)    PAP

Save, 並回 Connection 頁選擇剛新增的並連線

ref : http://shkspr.mobi/blog/2012/07/3g-internet-on-raspberry-pi-success/ http://blog.csdn.net/rk2900/article/details/8667833

Raspberry Pi Camera MJPG-streamer Server

MJPG-streamer 是一個能截取 camera 影像的套件, 將每幅影像壓成 jpg, 並且起一個 server 將影像輸出, 好處是跨瀏覽器、編解碼容易、運算能力需求低, 缺點是即時性差(在高解析的影像更是明顯)

安裝 MJPG-streamer

安裝必要的相依套件

sudo apt-get install subversion libjpeg8-dev imagemagick libv4l-dev

下載並安裝 MJPG-streamer

cd ~
svn co https://svn.code.sf.net/p/mjpg-streamer/code/
cd mjpg-streamer/mjpg-streamer
make
sudo make install
  • svn 點斷了可到官方下載
  • 最後一行安裝指令會將一些檔案搬到以下路徑
  • mjpg_streamer : /usr/local/bin
  • input_uvc.so output_file.so output_udp.so input_testpicture.so input_file.so : /usr/local/lib
  • www/* : /usr/local/www

Starting the Webcam Server

cd ~/mjpg-streamer/mjpg-streamer
./mjpg_streamer -i "./input_uvc.so -r 320x240 -f 1" -o "./output_http.so -w ./www"

320x240, fps:1

Watch the stream in your browser

你可以在同一個 lan 的電腦打開瀏覽器輸入你的 ip:8080 (i.e. http://192.168.1.10:8080/?action=snapshot)

頁面就會是你的 camera 的影像, 你必須 refresh 才會更新影像

或者用以下每 1.5 秒自動 reload 模擬動態影像的網頁來看

<html>
<head></head>
<body>
<script>
setTimeout("location.reload()", 1500)
</script>
<img src="http://192.168.1.10:8080/?action=snapshot"/>
</body>
</html>

讓 Wan 可以連進來看你的 camera 影像

大部份人的網路都在 NAT 下, 所以外面是無法連到你的 RPI 的, 但可以藉由 ngrok 這個工具做到讓 Wan 連進來, ngrok 的原理是提供一條 tunnel, 有點像接水管的方式, 讓你的 RPI 跟 ngrok 的主機建立一條連線, ngrok 提供一個 TOKEN URL 讓你從外部連進來再幫你導到你的 RPI

下載並安裝 ngrok, 由於 RPI 是 ARM 架構, 記得選取 Linux/ARM

使用

1) 先執行你的 start-mjpg 指令, 此時 mjpg-streamer 的 URL 只有 Lan 的 8080 才可以看到影像

2) 再執行 ngrok http 8080, 畫面上會提供一組 URL ex: http://cf77c5af.ngrok.io -> localhost:8080, 意思是連到 http://cf77c5af.ngrok.io 會幫你導到 localhost 的 8080 port

3) http://cf77c5af.ngrok.io?action=snapshot 就可以從 Wan 連進來了

4) 寫個簡單的 HTML + script 讓影像每一秒自動更新一次

html :

<img id="stream" onload="start();">

javascript :

tmpimage = new Image();
var url = "http:// ........ ";
function start() {
    setTimeout(function () {
        $('#stream').attr('src', url + '&t=' + new Date().getTime());
    }, 1000);
}
tmpimage.src = url + '&t=' + new Date().getTime();
$('#stream').attr('src', tmpimage.src);

mjpg+ngrok.JPG

安裝 mjpg-streamer 的 Q&A

如果發生 ERROR opening V4L interface: No such file or directory

上面應該會有 Using V4L2 device.: /dev/video0, 表示你的 /dev/video0 不存在

原因是你的 V4L driver 不支援你的 camera, 所以沒有 /dev/video0

V4L : Video4Linux framework

解法方式是安裝非官方的 V4L driver, 這裡是它的安裝說明頁, 或直接照著以下的步驟做 :

[1] wget http://www.linux-projects.org/listing/uv4l_repo/lrkey.asc && sudo apt-key add ./lrkey.asc

[2] Append into /etc/apt/source.list :

deb http://www.linux-projects.org/listing/uv4l_repo/raspbian/ wheezy main

[3] sudo apt-get updatesudo apt-get install uv4l uv4l-raspicam

If you want the driver to be loaded at boot, execute sudo apt-get install uv4l-raspicam-extras

Now the UV4L core component and the Video4Linux2 driver for the CSI Camera Board are installed.

[4] update raspberry pi firmware : sudo rpi-update (更新超久)

[5] 重開機後就抓的到 /dev/video0

[6] 關閉 uv4l 的程序, 並更新

sudo pkill uv4l
sudo apt-get update (非必要, 建議執行)
sudo apt-get install uv4l-uvc
sudo apt-get install uv4l-xscreen
sudo apt-get install uv4l-mjpegstream

[7] 重開機, 再啟動 mjpg_streamer -i "./input...(略), 沒意外的話你會遇到下面的問題

發生 /dev/video0 does not support streaming i/o

因為之後都要靠 uv4l 的 library 驅動了, 只要後面加上 mjpg_streamer -i "./input...(略) 就行了, 如下

cd ~/mjpg-streamer/mjpg-streamer
LD_PRELOAD=/usr/lib/uv4l/uv4lext/armv6l/libuv4lext.so mjpg_streamer -i "./input_uvc.so" -o "./output_http.so -w ./www"

如果不行試試先執行這行看看 uv4l --driver raspicam --auto-video_nr --extension-presence=1

mjpg-streamer 應該就可以正常跑起來了,

你跟我想的一樣..這指令實在太醜了, 簡化它一下

/home/pi/.bashrc 最下面加上 :

export MJPG_STREAMER_PATH=/home/pi/mjpg-streamer/mjpg-streamer
alias start-mjpg='LD_PRELOAD=/usr/lib/uv4l/uv4lext/armv6l/libuv4lext.so ${MJPG_STREAMER_PATH}/mjpg_streamer -i "${MJPG_STREAMER_PATH}/input_uvc.so" -o "${MJPG_STREAMER_PATH}/output_http.so -w ${MJPG_STREAMER_PATH}/www"'

存檔後重新讀取 .bashrc : source .bashrc

之後就以這個簡短指令 start-mjpg 執行它就行了

Raspberry Pi Camera 試玩

硬體規格

  • 感測器像素 5M Pixel sensor (500 萬)
  • 靜止拍照解析度 2592 X 1944
  • 最大錄影解析度 1080p (720p 或 1080p 的 H.264 錄影格式
  • 最大錄影速度 30fps

安裝

硬體參考官網影片教學將 camera 安裝到 Rpi 上

安裝 camera

sudo apt-get update
sudo apt-get upgrade
sudo raspi-config

選擇 Enable Camera -> enable 結束 config 時會問你要不要重開機, 選擇要

指令介紹

raspistill 拍照儲存成jpeg

raspistill -o image.jpg

-o : Output filename

輸入完後原本 terminal 的黑畫面會有一層 camera 的影像佔滿螢幕高度

過了大約 5 秒就會拍下來了, 檔案大小大約 2.4 M

raspivid 錄影 5 秒編碼為 h.264 :

raspivid -o video.h264

會有影像填滿整個螢幕畫面, 檔案大小約 9.9 M

10 秒 :

raspivid -o video.h264 -t 10000

查看還有哪些選項可以玩

raspivid | less
raspistill | less

從我的 Mac 看 Raspberry Pi 的 camera 畫面

[1] Mac 這邊要先 listen 5001 port 接收影像並播放

如果先執行 Raspberry pi 把影像丟出來, 沒人聽會直接結束

  1. 先安裝 mplayer (using brew)

    sudo brew update // 還是先更新吧! 發現版本太舊無法安裝 sudo brew install mplayer

  2. 執行 nc -l 5001 | mplayer -fps 31 -cache 1024 -

[2] 首先讓 Raspberry Pi 丟影像到我的 Mac 那台的 IP 的 5001 port

  1. Raspberry Pi 安裝 : sudo apt-get install mplayer netcat

  2. 執行 raspivid -t 999999 -o - | nc 192.168.1.26 5001

- 表示 STDOUT

[3] 接著 Mac 就會開始接收檔案, 並且跳出 MPlayer 的影片播放程式了

$ nc -l 5001 | mplayer -fps 31 -cache 1024 -
MPlayer 1.1-4.2.1 (C) 2000-2012 MPlayer Team
Can't init Apple Remote.

Playing -.
Reading from stdin...
Cache fill: 17.26% (180944 bytes)

libavformat version 54.6.100 (internal)

我是用無線網路傳送, 有夠 lag, 稍微調整一下讓它傳比較快

  • Mac : nc -l 5001 | mplayer -fps 8 -cache 1024 -
  • Raspberry Pi : raspivid -t 999999 -h 180 -w 270 -fps 5 -o - | nc 192.168.1.26 5001

ref : http://www.raspberrypi.org/camera

Ruby Basic

變數

以此例為例子

class Var
  def print
    puts $hh
  end
  $hh = "hh"
end

t = Var.new
t.print

替換以下變數

  • $name : 全域變數. 結果: 正常
  • @name : 實例變數, 作用僅限於 self 指示的物件. 結果: 相當於輸出 nil, 什麼東沒有
  • @@name : 類別變數, 在 class 內使用, 如果另個物件繼承它的物件, 也可以使用 @@name. 結果: 正常
  • name : 區域變數 (小寫字母或底線開頭, 初始化前並沒有 nil 值). 結果: undefined local variable or method hh'
  • Name : 常數 (大寫開頭, 不可重覆定義). 結果: 正常

結論 : 定義在 class 內的變數必須是 全域, 類別或常數

宣告

陣列

%w(i1 b2 c3 j4)                 # ["i1", "b2", "c3", "j4"]

arr = {                         # {:A => 1, :B => 2 }
    'A' : 1,
    'B' : 2,
}

基本 class 觀念

class Foo

  def instance_method                           # instance method   (可以用 self 取 instance 的值)
    'instance_method 要先 new 才能使用'
  end

  def self.class_method                         # class method (無法用 self 取內部的值)
    'slef.class_method 不能 new, 可直接使用'
  end

  def call_method
    inside_method
  end

  protected

  def inside_method
    'Call 內部的 inside_method 不需加 `self.` prefix'
  end

end

a = Foo.new
puts a.instance_method      # instance_method 要先 new 才能使用
puts Foo.class_method       # slef.class_method 不能 new, 可直接使用
puts a.call_method          # Call 內部的 inside_method 不需加 `self.` prefix

判斷

0 及 empty 都是 TRUE, 只有 false 與 nil 才是 FALSE

是否為數值

9.9.integer?

數值是否在範圍裡面

(11..15).include? 15
15.between?(11, 15)

檔案相關

Check directory in existence, Create folder

require 'fileutils'
if File.directory?('/tmp/layer1')
  puts "Folder exist !"
else
  puts "Create folder ..."
  FileUtils.mkpath('/tmp/layer1/layer2')
end

Check if a file exists

File.exist?('/tmp/ruby_test/send/sorry.mp3')

Getting path from full file path

2.0.0-p247 :001 > File.dirname("/tmp/ruby_test/send/qq.php")
 => "/tmp/ruby_test/send"

Getting filename from path

2.0.0-p247 :003 > File.basename("/tmp/ruby_test/send/qq.php")
 => "qq.php"

Integer & Float

小數第一位四捨五入

a = 633.633
a.round(1)     # 633.6

次方

2 ** 3

絕對值

-5.abs      # 5

取最近的整數/四捨五入

5.6.round   # 6

取整數/無條件捨去

9.9.floor   # 9

取整數/無條件進位

2312.22.ceil    # 2313

下一個數

2.next      # 3

二元運算

n & num
n | num
n ^ num (XOR)
n << num (向左位移)
n >> num (向右位移)

Rand

rand(1..10)
[true, false].sample

亂數

SecureRandom.random_number(99999999999)             # 9997979524

建立 UUID

require 'securerandom'                  # 原生
SecureRandom.hex(20)                    # ac5d23f916dd83dcc495dc5f0b8b602942b635fa    長度會是你傳值的 2 倍
SecureRandom.base64(20)                 # WcLJKJzgibphbiXHKIeCsP8jtN8=

SecureRandom : This library is an interface for secure random number generator which is suitable for generating session key in HTTP cookies

字串處理

gsub

"hello".gsub(/[aeiou]/, '*')                    # "h*ll*"
"hello".gsub(/([aeiou])/, '<\1>')               # "h<e>ll<o>"
"hello".gsub(/./) {|s| s.ord.to_s + ' '}        # "104 101 108 108 111 "
"hello".gsub(/(?<foo>[aeiou])/, '{\k<foo>}')    # "h{e}ll{o}"
'hello'.gsub(/[eo]/, 'e' => 3, 'o' => '*')      # "h3ll*"

split

'C3-0803986E423F0C66DA56'.split('-')            # ["C3", "0803986E423F0C66DA56"]

last word

'example'.last                                  # e

取固定位置及長度的字串

a = "1234567890"
a[1..3]  # 234

Hash

key 是否存在

genders.has_key?(:male)

value 是否存在

genders.value?(2)

刪除某個 key

a.delete('es')

刪除某個 value

hash.delete_if{|_,v| v == "2"}

Reverse key <-> value

Hash[h.to_a.collect(&:reverse)]

Find value and return key

h.key(value)    # return key

Get all keys from Hash

h.keys

Sort

hash = {f: 4, a: 2, r: 1 }
hash.sort # => [[:a, 2], [:f, 4], [:r, 1]]          # 這不是我們要的結果
hash.sort.to_h                                      # => {:a=>2, :f=>4, :r=>1} , 這才是

兩個 hash 合併另種寫法

a = {"zh"=>1, "es"=>5}
b = {"zh"=>1, "qq"=>5}
a.merge(b)                  # {"zh"=>1, "es"=>5, "qq"=>5}

# 這個範例可以看的出它只比對 key,如果 key 一樣值不一樣,則會被後者的值覆蓋
a = {"zh"=>1, "es"=>5}
b = {"zh"=>1, "es"=>7}
a.merge(b)                  # {"zh"=>1, "es"=>7}

只取得 hash 裡面某些 key -> value

{a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b)
{:a=>1, :b=>2}

Array

是否為 array

array.is_a?(Array)

去除相同的值 :

[2,4,2,5,1].uniq            # => [2, 4, 5, 1]

Array to Hash

a = ['apple', 'banana', 'mongo']
c = Hash[a.map { |e| [e.to_sym, e + ' !'] }]
 => {:apple=>"apple !", :banana=>"banana !", :mongo=>"mongo !"}

a = [["Bob", 1], ["Jex", 2], ["Jxx", 3]]
Hash[a.map { |e| [e[0].to_sym, e[1]]}
 => {:Bob=>1, :Jex=>2, :Jxx=>3}

刪除 each 判斷

a.delete_if { |x| x >= 3 }

a.delete_if do |v|
  if v >= 3
    true                # Make sure the if statement returns true, so it gets marked for deletion
  end
end

array’s value to symbol

array.map { |x| x.to_sym }
或
array.map &:to_sym

值是否存在

a = [1,2,3]
a.include?(2)

多項是否存在, 只要其中一項存在就是 true
([2, 6, 13, 99, 27] & [5,6]).any?

找出值旳 index

array.find_index(90)

兩個 array 合併

a.zip(s).flatten.compact
或
s.inject(a, :<<)

add

會改變原值
a.push("d", "e", "f")
a << 'x'

不會改變原值
a + [3]

delete

會改變原值
a = [2,4,6,3,8]
a.delete(3)

不會改變原值
a - [3]

remove duplicate elements

array = array.uniq

join array (無值就 delete value)

my_array.delete_if(&:empty?).join(',')

ordre ASC

my_array.sort

order DESC

my_array.sort.reverse

order 某個欄位

my_array.sort_by &:lastname

associations 關聯,如果想刪除其中某一筆

items.each do |i|
  if i.product.nil?
    needed_remove << i  # 先放進待刪除區
  end
end
items - needed_remove

# 以下看起來會 work ,但實際上最後的 items 還是跟原始的一樣,也就是 delete 沒有在原本的 items 移除該項目
items.each do |i|
  if i.product.nil?
    items.delete(i)
  end
end
items

insert 一筆到最前面

[1,3,6].unshift(5)              # [5, 1, 3, 6]

json

string to json

JSON.parse(params[:JSONData])

each

一行

cats.each(&:name)
cat_names = cats.map(&:name)            # 相當於 each cat.name
cats.each do |cat| cat.name end
cats.each {|cat| cat.name }

Call Dynamic method name

Sometimes you might need a dynamic method, you can use send

send

def test
  puts 'test~'
end

send('test')

前面也可以接 instance 以及可以多個 method 一起執行

User.send('smart').send('honest')           # 等於 User.smart.honest

傳遞參數

User.send('smart', iq: 130)

只 call public method

User.public_send(:name, "Jex")

method call

程式碼比較多而且不能串多個 method

> a = "my_string"
> meth = a.method("size")
> meth.call() # call the size method
=> 9

型態

Print variable type

3.class
 => Fixnum
3.class.superclass
 => Integer

加密

Digest::SHA256.digest 'message'         # "\xABS\n\x13\xE4Y\x14\x98+y\xF9\xB7\xE3\xFB\xA9\x94\xCF\xD1\xF3\xFB\"\xF7\x1C\xEA\x1A\xFB\xF0+F\fm\x1D"

Digest::SHA256.hexdigest('message')     # ab530a13e45914982b79f9b7e3fba994cfd1f3fb22f71cea1afbf02b460c6d1d

Progress bar

簡易

10.times{|i| STDOUT.write "\r#{i}"; sleep 1}

1~100%

progress = 'Progress [ '
1000.times do |i|
  j = i + 1
  if j % 10 == 0
    # 將 = 加到 progress string
    progress << "="

    # 將游標移到這行的最前面
    print "\r"

    # 取代目前這一行, 並輸出進度
    print progress + " #{j / 10} % ]"

    # flush buffer 立即顯示
    $stdout.flush
    sleep 0.05
  end
end
puts "\nDone!"

定義 hash parameters

def instance_method(hash = {})
Call : a.instance_method aa: 'aa', bb: 'bb'

hash key 使用 integer 時注意 :

hash = {1: 'one'} # will not work
hash = {1 => 'one'} # will work

Date format

Symbol

  • %Y: 2017
    • %C : 20 (year / 100)
    • %y : 17 (year % 100)
  • %B : January
    • %^b : JANUARY
  • %b : Jan 也等於 %h
    • %^b : JAN
  • %m : 01..12 with zero-padding
    • %_m : 1..12 with blank-padding
    • %-m : 1..12 without balnk-padding
  • %d : 01..31 with zero-padding
    • %e : 1..31 with blank-padding
    • %-d : 1..31 without balnk-padding
  • %j : 001..336 day of the year
  • %H : 00..23 with zero-padding
  • %k : 0..23 with blank-padding
  • %I : 01..12 with zero-padding
  • %l : 1..12 with blank-padding
  • %P : am / pm
  • %p : AM / PM
  • %M : 00..59 minute
  • %S : 00..59 second
  • %L : 000..999 millisecond
  • %N = 9 digits nanosecond
    • %3N = 3 digits millisecond
    • %6N = 6 digits microsecond
    • %9N = 9 digits nanosecond
    • %12N = 12 digits picosecond
  • %z : +0900
    • %:z : +09:00
    • %::z : +09:00:00
    • %:::z : +09:30:30
    • %Z : CST time zone abbreviation name
  • %A : Sunday
    • %^A : SUNDAY
    • %a : Sun
    • %^a : SUN
    • %u : 1..7 day of the week (monday is 1)
    • %w : 0..6 day of the week (sunday is 0)
  • %G : 2016 the week-based year
    • %g : 16 the last 2 digits of the week-based year
    • %V : 52 Week number of the week-based year
  • %U : 00..53 week number of the year. starts with sunday
    • %W : 00..53 starts with mondy
  • %s : 1483249934 timestamp, number of seconds since 1970-01-01 00:00:00 UTC
  • %n : \n
    • %t : \t
    • %% : %
  • %c : Sun Jan 1 13:54:29 2017 date and time (%a %b %e %T %Y)
    • %D = %x : 01/01/17 Date (%m/%d/%y)
    • %F : 2017-01-01 The ISO 8601 date format (%Y-%m-%d)
    • %v : 1-JAN-2017 VMS date (%e-%b-%Y)
    • %r : 01:56:03 PM 12-hour time (%I:%M:%S %p)
    • %R : 13:56 24-hour time (%H:%M)
    • %T = %X : 13:55:44 24-hour time (%H:%M:%S)

語法

Time.now                                        # 2016-04-07 11:04:09 +0800
Time.now.getutc                                 # 2016-04-07 03:05:00 UTC
Time.now.strftime('%F %T')                      # 2015-08-10 21:02:04
Time.now.strftime('%F %R')                      # 2015-09-21 01:04
Time.now.strftime("%Y-%m-%d %H:%M:%S")          # 2015-08-10 21:01:06
Time.now.in_time_zone                           # Tue, 22 Sep 2015 09:27:56 CST +08:00    如果 config 有設定 timezone
Time.now.to_i                                   # 1483249426 (timestamp)

String 轉回 Time

Time.parse("2015-09-21 12:00")
或
"2015-09-21 12:00".to_time

2 天前 (rails)

2.days.ago

2 天後 (rails)

2.days.since

現在 DateTime 減 5 天

Post.find(12).update(created_at: Time.now.strftime('%F %R').to_time - 5.days)

5 天後

Date.today + 5
Date.today + 5.days

10.days.from_now

date 相減

require 'date'
> now = Date.today
> before = Date.today + 2.days
> difference_in_days = (before - now).to_i

-----
start_date = Date.parse "2012-03-02 14:46:21 +0100"
end_date =  Date.parse "2012-04-02 14:46:21 +0200"
(end_date - start_date).to_i

Add time to date

d = Date.new(2012, 8, 29)
t = Time.now
dt = DateTime.new(d.year, d.month, d.day, t.hour, t.min, t.sec, t.zone)
或
Date.new(2015, 2, 10).to_datetime + Time.parse("16:30").seconds_since_midnight.seconds

Today

Date.today

Year

Time.now.year

顯示時間語意

# console 要引入 include ActionView::Helpers::DateHelper
time_ago_in_words(3.minutes.from_now)                       # => 3 minutes
time_ago_in_words(Time.now - 15.hours)                      # => 15 hours
time_ago_in_words(Time.now)                                 # => less than a minute
from_time = Time.now - 3.days - 14.minutes - 25.seconds     # => 3 days
distance_of_time_in_words('2015-09-16'.to_date, Date.today) # => 不到 1 分鐘

Gem error

Gem::FilePermissionError

$ gem install sitemap_generator
ERROR:  While executing gem ... (Gem::FilePermissionError)
    You don't have write permissions for the /usr/local/rvm/gems/ruby-2.2.1 directory.

使用 sudo 安裝也不行

sudo: gem: command not found

解決方法 : 切換到 sudo 加上 gem 相關的 PATH

$ $PATH
-bash: /usr/local/rvm/gems/ruby-2.2.1/bin:/usr/local/rvm/gems/ruby-2.2.1@global/bin:/usr/local/rvm/rubies/ruby-2.2.1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin

切換到 sudo, 在 .bashrc 最後一行加上

export PATH=$PATH:/usr/local/rvm/gems/ruby-2.2.1/bin:/usr/local/rvm/gems/ruby-2.2.1@global/bin:/usr/local/rvm/rubies/ruby-2.2.1/bin

再重新登入, 完成!

Gem::LoadError: You have already activated rake ...

Gem::LoadError: You have already activated rake 12.0.0, but your Gemfile requires rake 11.3.0.

1) Check your Gemfile to see if rake exists. If yes, upgrade it.

2) If no, execute bundle update rake

防火牆 Iptable

基本設定

/etc/iptables.firewall.rules:

*filter

#  Allow all loopback (lo0) traffic and drop all traffic to 127/8 that doesn't use lo0
-A INPUT -i lo -j ACCEPT
-A INPUT -d 127.0.0.0/8 -j REJECT

#  Accept all established inbound connections
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

#  Allow all outbound traffic - you can modify this to only allow certain traffic
-A OUTPUT -j ACCEPT

#  Allow HTTP and HTTPS connections from anywhere (the normal ports for websites and SSL).
-A INPUT -p tcp --dport 80 -j ACCEPT
-A INPUT -p tcp --dport 443 -j ACCEPT

#  Allow SSH connections
#
#  The -dport number should be the same port number you set in sshd_config
#
-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT

#  Allow ping
-A INPUT -p icmp -j ACCEPT

#  Log iptables denied calls
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

#  Drop all other inbound - default deny unless explicitly allowed policy
-A INPUT -j DROP
-A FORWARD -j DROP

COMMIT

附加設定

-A INPUT -p tcp --dport 445 -j ACCEPT    // for samba
-A INPUT -p tcp -s 10.160.55.77     --dport 11211 -j ACCEPT // for memcache
-A INPUT -p tcp -m state --state NEW --dport 4123 -j ACCEPT   // ssh port 改成 4123
-A INPUT -p icmp -j ACCEPT // allow ping

啟動防火牆

sudo iptables-restore < /etc/iptables.firewall.rules

檢查是否設定成功

sudo iptables -L

重開後防火牆自動啟動

編輯 sudo vim /etc/network/if-pre-up.d/firewall:

#!/bin/sh
/sbin/iptables-restore < /etc/iptables.firewall.rules

permission :

sudo chmod +x /etc/network/if-pre-up.d/firewall

Q & A

發生 iptables-restore: line 36 failed

雖然 36 行是指向 COMMIT, 但並不是它的問題, 是前面一些設定的問題,

把下面這些刪除再跑一次看看, 在 Raspberry Pi 發現不刪會一直過不了

#  Log iptables denied calls
-A INPUT -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7

Golang 建立自己的套件

先確定 GOPATH 路徑存在, 這是放你的 package 檔案位置

.bash_profile :

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

$GOPATH 底下要有這三個目錄, 你應該建立它們

  • src : 存放源始碼 (ex: .go .c .h .s)
  • pkg : 編譯後生成的文件 (ex: .a)
  • bin : 編譯後生成的可執行檔

在 src 下建立一個練習用的套件 exlib

appletekiMacBook-Air-2:mygo apple$ tree src
src
└── exlib
    ├── exlib.go
    └── sub_exlib
        └── sub_exlib.go

2 directories, 2 files

exlib/exlib.go :

package exlib

type TestInterface interface {
    run() string
}

type TestStruct struct {
    Name string
    Age  int
}

// return initial struct
func NewStruct(name string, age int) (testStruct *TestStruct) {
    testStruct = &TestStruct {
        Name: name,
        Age: age,
    }

    return testStruct
}

// Implement TestStruct func
func (testStruct TestStruct) run() string {
    return "run run run!!"
}

// Execute interface's func
func InterfaceFunc (testStruct TestStruct) string {
    testInterface := TestInterface(testStruct)
    return testInterface.run()
}

建立 exlib 的相關子 package

在 exlib 建立子目錄 sub_exlib

exlib/sub_exlib/sub_exlib.go:

package sub_exlib

// Public func
func SubFunc() string {
    return "sub_exlib"
}

你也可以將 sub_exlib 這個 package 分成許多檔案 :

exlib/sub_exlib/sub_exlib2.go:

package sub_exlib

// Public func
func QQ() string {
    return "sub_exlib2"
}

注意必須都要在 exlib/sub_exlib 下以及 package 名稱也都要一樣

完成! 引入並執行剛剛建立的套件

主程式 :

package main

import (
    "fmt"
    "exlib"
    "exlib/sub_exlib"
)

func main() {
    // New struct
    new_struct := exlib.NewStruct("Jex", 26)
    fmt.Println(*new_struct)

    // Interface - implement func, and run its func
    interfaceRun := exlib.InterfaceFunc(*new_struct)
    fmt.Println(interfaceRun)

    // Sub package
    sub_exlib := sub_exlib.SubFunc()
    fmt.Println(sub_exlib)
}

結果 :

{Jex 26}
run run run!!
sub_exlib

ref: https://github.com/jex-lin/build-web-application-with-golang/blob/master/ebook/01.2.md