按:
源碼地址:https://github.com/mholt/caddy
對Golang不是很熟悉,算是初學,並沒有什麼好的方式進行深入的學習,故選此項目研究。
假定我們都是初學Golang,或者是熟悉其他語言,但Golang不瞭解,那麼我們下面就從0開始來看看,寫Golang到底是啥樣!
分析不當,望指正!
0. 項目介紹
Caddy是一款輕量級通用性很強的web服務器,支持在Windows,Mac,Linux,BSD,以及Android多種平臺。她完全能夠替代其他主流的網絡服務器。
1. 項目結構
- 雲端構建
- appveyor
- 依賴
- golang.org/x/tools/cmd/vet
main_test.go爲測試文件
go test
完成測試操作。
簡單看下main_test.go
文件,
package main
import (
"runtime" // runtime包,運行時的系統交互操作
"testing" // testing包,自動化測試
)
func TestSetCPU(t *testing.T) {
// 測試設置併發數
currentCPU := runtime.GOMAXPROCS(-1) // 設置最大併發數,參數小於1時保持當前設置
maxCPU := runtime.NumCPU() // 獲取機器上的邏輯cpu數
halfCPU := int(0.5 * float32(maxCPU))
if halfCPU < 1 {
halfCPU = 1
}
/*
* 下面這段簡化爲
*/
for i, test := range []struct {
input string
output int
shouldErr bool
}{
{"1", 1, false},
{"-1", currentCPU, true},
{"0", currentCPU, true},
{"100%", maxCPU, false},
{"50%", halfCPU, false},
{"110%", currentCPU, true},
{"-10%", currentCPU, true},
{"invalid input", currentCPU, true},
{"invalid input%", currentCPU, true},
{"9999", maxCPU, false}, // over available CPU
} {
err := setCPU(test.input)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected error, but there wasn't any", i)
}
if !test.shouldErr && err != nil {
t.Errorf("Test %d: Expected no error, but there was one: %v", i, err)
}
if actual, expected := runtime.GOMAXPROCS(-1), test.output; actual != expected {
t.Errorf("Test %d: GOMAXPROCS was %d but expected %d", i, actual, expected)
}
// teardown
runtime.GOMAXPROCS(currentCPU)
}
}
2. 讀源碼
/main.go
1. 包名及導入相關包
package main
import (
"errors" // 實現操作錯誤的函數,類似於Python中的Expection,但用法存在很大差異,後面再介紹
"flag" // 解析命令行輸入參數
"fmt" // 格式化工具
"io/ioutil" // IO操作
"log" // 日誌
"os" // 操作系統
"runtime" // 運行時環境
"strconv" // 字符串於一些基本類型的轉換
"strings" // 處理UTF-8編碼的字符串
"time" // 時間
"github.com/mholt/caddy/caddy" // 主包
"github.com/mholt/caddy/caddy/letsencrypt" // Let's Encrypt TLS
)
簡單下介紹下幾個包,
-
error
:
實現操作錯誤的函數,用法是先定義一個結構體,然後給這個結構體實現一個Error()
『方法』。 -
flag
:
flag.String(N1, N2, N3)
,有反回值返回值返回給字面量,N1是變量參數名,N2是默認參數,N3是提示字符串;
flag.VarString(N1, N2, N3)
,N1爲存儲的指針
/*
* 定義變量
*/
var (
conf string
cpu string
logfile string
revoke string
version bool
)
/*
* 定義常量
*/
const (
appName = "Caddy"
appVersion = "0.8"
)
初始化函數,這個函數再main()
函數之前執行,
func init() {
caddy.TrapSignals()
/*
* 命令行參數的初始化,再main函數中flag.Parse()就能獲取到輸入的值
*/
flag.BoolVar(&letsencrypt.Agreed, "agree", false, "Agree to Let's Encrypt Subscriber Agreement")
flag.StringVar(&letsencrypt.CAUrl, "ca", "https://acme-v01.api.letsencrypt.org/directory", "Certificate authority ACME server")
.
.
.
}
下面看下caddy
包內TrapSignals()
函數,這個函數再/caddy/sigtrap.go
,
// TrapSignals create signal handlers for all applicable signals for this
// system. If your Go program uses signals, this is a rather invasive
// function; best to implement them yourself in that case. Signals are not
// required for the caddy package to function properly, but this is a
// convenient way to allow the user to control this package of your program.
func TrapSignals() {
trapSignalsCrossPlatform()
/*
* 這個函數不在這個文件中,但同樣是在`caddy`包內,
* 在/caddy/sigtrap_posix.go文件中,
* 所以可以直接調用,這點類似於Java,而不同於Python以文件來區分包
*/
trapSignalsPosix()
}
// trapSignalsCrossPlatform captures SIGINT, which triggers forceful
// shutdown that executes shutdown callbacks first. A second interrupt
// signal will exit the process immediately.
func trapSignalsCrossPlatform() {
go func() {
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt)
for i := 0; true; i++ {
<-shutdown
if i > 0 {
log.Println("[INFO] SIGINT: Force quit")
os.Exit(1)
}
log.Println("[INFO] SIGINT: Shutting down")
go os.Exit(executeShutdownCallbacks("SIGINT"))
}
}()
}
// executeShutdownCallbacks executes the shutdown callbacks as initiated
// by signame. It logs any errors and returns the recommended exit status.
// This function is idempotent; subsequent invocations always return 0.
func executeShutdownCallbacks(signame string) (exitCode int) {
shutdownCallbacksOnce.Do(func() {
serversMu.Lock()
errs := server.ShutdownCallbacks(servers)
serversMu.Unlock()
if len(errs) > 0 {
for _, err := range errs {
log.Printf("[ERROR] %s shutdown: %v", signame, err)
}
exitCode = 1
}
})
return
}
var shutdownCallbacksOnce sync.Once
下面嘗試分析下,這個函數或者說這些函數是來幹什麼的?
初看下註釋,大概的意思是,這是一個信號槽,不知這樣理解是否妥當。它主要是用來做程序功能的切換,或者說響應,簡單點就是事件的處理或者分發,當然在這裏是信號。
先看trapSignalsCrossPlatform()
這個函數,
跨平臺信號捕捉,觸發強制關閉回調的停止信號,第二次中斷立即退出。
go func(){}()
,Golang的殺手鐧,併發,關鍵字go
,來執行這個匿名函數。make()
變量的申明,只適用於通道
、切片
、字典
這三種引用類型(底層就是數組),返回的是值本身,已經初始化(非零值);相反地,new()
用於爲值分配內存,返回的是已經初始化(非零值)的內存指針。