build-web-application-with-golang學習筆記

來源:互聯網
上載者:User

標籤:遍曆   source   簡單   through   drive   執行個體化   重點   入口   初始化   

build-web-application-with-golang 學習教程

這幾周學習以上教程,僅記錄一些重點痛點部分。

Go語言Go語言基礎

Go是一門類似C的編譯型語言,但是它的編譯速度非常快。這門語言的關鍵字總共也就二十五個:

break    default      func    interface    selectcase     defer        go      map          structchan     else         goto    package      switchconst    fallthrough  if      range        typecontinue for          import  return       var
  • var和const參考2.2Go語言基礎裡面的變數和常量申明
  • package和import已經有過短暫的接觸
  • func 用於定義函數和方法
  • return 用於從函數返回
  • defer 用於類似解構函式
  • go 用於並發
  • select 用於選擇不同類型的通訊
  • interface 用於定義介面,參考2.6小節
  • struct 用於定義抽象資料類型,參考2.5小節
  • break、case、continue、for、fallthrough、else、if、switch、goto、default這些參考2.3流程介紹裡面
  • chan用於channel通訊
  • type用於聲明自訂類型
  • map用於聲明map類型資料
  • range用於讀取slice、map、channel資料

Go程式是通過package來組織的,package <pkgName>這一行告訴我們當前檔案屬於哪個包,而包名main則告訴我們它是一個可獨立啟動並執行包,它在編譯後會產生可執行檔。除了main包之外,其它的包最後都會產生*.a檔案(也就是包檔案)並放置在$GOPATH/pkg/$GOOS_$GOARCH中(以Mac為例就是$GOPATH/pkg/darwin_amd64)。

每一個可獨立啟動並執行Go程式,必定包含一個package main,在這個main包中必定包含一個入口函數main,而這個函數既沒有參數
,也沒有傳回值。

包的概念和Python中的package類似,它們都有一些特別的好處:模組化(能夠把你的程式分成多個模組)和可重用性(每個模組都能被其它應用程式反覆使用)。

Go語言重點痛點interface

簡單的說,interface是一組method簽名的組合,我們通過interface來定義對象的一組行為。

package mainimport "fmt"type Human struct {    name string    age int    phone string}type Student struct {    Human //匿名欄位    school string    loan float32}type Employee struct {    Human //匿名欄位    company string    money float32}//Human實現SayHi方法func (h Human) SayHi() {    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)}//Human實現Sing方法func (h Human) Sing(lyrics string) {    fmt.Println("La la la la...", lyrics)}//Employee重載Human的SayHi方法func (e Employee) SayHi() {    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,        e.company, e.phone)    }// Interface Men被Human,Student和Employee實現// 因為這三個類型都實現了這兩個方法type Men interface {    SayHi()    Sing(lyrics string)}func main() {    mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}    paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}    sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}    tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}    //定義Men類型的變數i    var i Men    //i能儲存Student    i = mike    fmt.Println("This is Mike, a Student:")    i.SayHi()    i.Sing("November rain")    //i也能儲存Employee    i = tom    fmt.Println("This is tom, an Employee:")    i.SayHi()    i.Sing("Born to be wild")    //定義了slice Men    fmt.Println("Let‘s use a slice of Men and see what happens")    x := make([]Men, 3)    //這三個都是不同類型的元素,但是他們實現了interface同一個介面    x[0], x[1], x[2] = paul, sam, mike    for _, value := range x{        value.SayHi()    }}

interface就是一組抽象方法的集合,它必須由其他非interface類型實現,而不能自我實現。

空interface

空interface(interface{})不包含任何的method,正因為如此,所有的類型都實現了空interface。空interface對於描述起不到任何的作用(因為它不包含任何的method),但是空interface在我們需要儲存任意類型的數值的時候相當有用,因為它可以儲存任意類型的數值。它有點類似於C語言的void*類型。

// 定義a為空白介面var a interface{}var i int = 5s := "Hello world"// a可以儲存任意類型的數值a = ia = s
goroutine

goroutine是Go並行設計的核心。goroutine說到底其實就是協程,但是它比線程更小,十幾個goroutine可能體現在底層就是五六個線程,Go語言內部幫你實現了這些goroutine之間的記憶體共用。執行goroutine只需極少的棧記憶體(大概是4~5KB),當然會根據相應的資料伸縮。也正因為如此,可同時運行成千上萬個並發任務。goroutine比thread更易用、更高效、更輕便。

goroutine是通過Go的runtime管理的一個線程管理器。goroutine通過go關鍵字實現了,其實就是一個普通的函數。

package mainimport (    "fmt"    "runtime")func say(s string) {    for i := 0; i < 5; i++ {        runtime.Gosched()        fmt.Println(s)    }}func main() {    go say("world") //開一個新的Goroutines執行    say("hello") //當前Goroutines執行}// 以上程式執行後將輸出:// hello// world// hello// world// hello// world// hello// world// hello
channels

goroutine運行在相同的地址空間,因此訪問共用記憶體必須做好同步。那麼goroutine之間如何進行資料的通訊呢,Go提供了一個很好的通訊機制channel。channel可以與Unix shell 中的雙向管道做類比:可以通過它發送或者接收值。這些值只能是特定的類型:channel類型。定義一個channel時,也需要定義發送到channel的值的類型。注意,必須使用make 建立channel:

package mainimport "fmt"func sum(a []int, c chan int) {    total := 0    for _, v := range a {        total += v    }    c <- total  // send total to c}func main() {    a := []int{7, 2, 8, -9, 4, 0}    c := make(chan int)    go sum(a[:len(a)/2], c)    go sum(a[len(a)/2:], c)    x, y := <-c, <-c  // receive from c    fmt.Println(x, y, x + y)}
Web Service With GolangGo搭建一個Web伺服器

Web是基於http協議的一個服務,Go語言裡面提供了一個完善的net/http包,通過http包可以很方便的就搭建起來一個可以啟動並執行Web服務。同時使用這個包能很簡單地對Web的路由,靜態檔案,模版,cookie等資料進行設定和操作。

package mainimport (    "fmt"    "net/http"    "strings"    "log")func sayhelloName(w http.ResponseWriter, r *http.Request) {    r.ParseForm()  //解析參數,預設是不會解析的    fmt.Println(r.Form)  //這些資訊是輸出到伺服器端的列印資訊    fmt.Println("path", r.URL.Path)    fmt.Println("scheme", r.URL.Scheme)    fmt.Println(r.Form["url_long"])    for k, v := range r.Form {        fmt.Println("key:", k)        fmt.Println("val:", strings.Join(v, ""))    }    fmt.Fprintf(w, "Hello astaxie!") //這個寫入到w的是輸出到用戶端的}func main() {    http.HandleFunc("/", sayhelloName) //設定訪問的路由    err := http.ListenAndServe(":9090", nil) //設定監聽的連接埠    if err != nil {        log.Fatal("ListenAndServe: ", err)    }}

看到上面這個代碼,要編寫一個Web伺服器很簡單,只要調用http包的兩個函數就可以了(類似於Python的tornado)。我們build之後,然後執行web.exe,這個時候其實已經在9090連接埠監聽http連結請求了。

在瀏覽器輸入http://localhost:9090

可以看到瀏覽器頁面輸出了Hello astaxie!

可以換一個地址試試:http://localhost:9090/?url_long=111&url_long=222

看看瀏覽器輸出的是什麼,伺服器輸出的是什嗎?

Go的Web服務底層

http包執行流程

  1. 建立Listen Socket, 監聽指定的連接埠, 等待用戶端請求到來。

  2. Listen Socket接受用戶端的請求, 得到Client Socket, 接下來通過Client Socket與用戶端通訊。

  3. 處理用戶端的請求, 首先從Client Socket讀取HTTP請求的協議頭, 如果是POST方法, 還可能要讀取用戶端提交的資料, 然後交給相應的handler處理請求, handler處理完畢準備好用戶端需要的資料, 通過Client Socket寫給用戶端。

這整個的過程裡面我們只要瞭解清楚下面三個問題,也就知道Go是如何讓Web運行起來了

  • 如何監聽連接埠?
  • 如何接收用戶端請求?
  • 如何分配handler?

前面小節的代碼裡面我們可以看到,Go是通過一個函數ListenAndServe來處理這些事情的,這個底層其實這樣處理的:初始化一個server對象,然後調用了net.Listen("tcp", addr),也就是底層用TCP協議搭建了一個服務,然後監控我們設定的連接埠。

下面代碼來自Go的http包的源碼,通過下面的代碼我們可以看到整個的http處理過程:

func (srv *Server) Serve(l net.Listener) error {    defer l.Close()    var tempDelay time.Duration // how long to sleep on accept failure    for {        rw, e := l.Accept()        if e != nil {            if ne, ok := e.(net.Error); ok && ne.Temporary() {                if tempDelay == 0 {                    tempDelay = 5 * time.Millisecond                } else {                    tempDelay *= 2                }                if max := 1 * time.Second; tempDelay > max {                    tempDelay = max                }                log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)                time.Sleep(tempDelay)                continue            }            return e        }        tempDelay = 0        c, err := srv.newConn(rw)        if err != nil {            continue        }        go c.serve()    }}

監控之後如何接收用戶端的請求呢?上面代碼執行監控連接埠之後,調用了srv.Serve(net.Listener)函數,這個函數就是處理接收用戶端的請求資訊。這個函數裡面起了一個for{},首先通過Listener接收請求,其次建立一個Conn,最後單獨開了一個goroutine,把這個請求的資料當做參數扔給這個conn去服務:go c.serve()。這個就是高並發體現了,使用者的每一次請求都是在一個新的goroutine去服務,相互不影響。

那麼如何具體分配到相應的函數來處理請求呢?conn首先會解析request:c.readRequest(),然後擷取相應的handler:handler := c.server.Handler,也就是我們剛才在調用函數ListenAndServe時候的第二個參數,我們前面例子傳遞的是nil,也就是為空白,那麼預設擷取handler = DefaultServeMux,那麼這個變數用來做什麼的呢?對,這個變數就是一個路由器,它用來匹配url跳轉到其相應的handle函數,那麼這個我們有設定過嗎?有,我們調用的代碼裡面第一句不是調用了http.HandleFunc("/", sayhelloName)嘛。這個作用就是註冊了請求/的路由規則,當請求uri為"/",路由就會轉到函數sayhelloName,DefaultServeMux會調用ServeHTTP方法,這個方法內部其實就是調用sayhelloName本身,最後通過寫入response的資訊反饋到用戶端。

詳細的整個流程如所示:

Go代碼的執行流程

通過對http包的分析之後,現在讓我們來梳理一下整個的代碼執行過程。

  • 首先調用Http.HandleFunc

    按順序做了幾件事:

    1 調用了DefaultServeMux的HandleFunc

    2 調用了DefaultServeMux的Handle

    3 往DefaultServeMux的map[string]muxEntry中增加對應的handler和路由規則

  • 其次調用http.ListenAndServe(":9090", nil)

    按順序做了幾件事情:

    1 執行個體化Server

    2 調用Server的ListenAndServe()

    3 調用net.Listen("tcp", addr)監聽連接埠

    4 啟動一個for迴圈,在迴圈體中Accept請求

    5 對每個請求執行個體化一個Conn,並且開啟一個goroutine為這個請求進行服務go c.serve()

    6 讀取每個請求的內容w, err := c.readRequest()

    7 判斷handler是否為空白,如果沒有設定handler(這個例子就沒有設定handler),handler就設定為DefaultServeMux

    8 調用handler的ServeHttp

    9 在這個例子中,下面就進入到DefaultServeMux.ServeHttp

    10 根據request選擇handler,並且進入到這個handler的ServeHTTP

    mux.handler(r).ServeHTTP(w, r)

    11 選擇handler:

    A 判斷是否有路由能滿足這個request(迴圈遍曆ServeMux的muxEntry)

    B 如果有路由滿足,調用這個路由handler的ServeHTTP

    C 如果沒有路由滿足,調用NotFoundHandler的ServeHTTP

表單

表單是我們平常編寫Web應用常用的工具,通過表單我們可以方便的讓用戶端和伺服器進行資料的互動。
表單是一個包含表單元素的地區。表單元素是允許使用者在表單中(比如:文本域、下拉式清單、單選框、複選框等等)輸入資訊的元素。表單使用表單標籤(

build-web-application-with-golang學習筆記

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.