Golang學習-第二篇 搭建一個簡單的Go Web服務器

序言

因爲本人一直從事Web服務器端的程序開發,因此在學習Golang也想從Web這裏開始學起,若是對Golang還不太清楚怎麼搭建環境的朋友們能夠參考個人上一篇文章 Golang的簡單介紹及Windows環境下安裝、部署,這一篇咱們來了解一下Golang的Web開發入門:搭建一個簡單的Go Web服務器。html

注:此文借鑑了Astaxie《Go Web編程》一書中的內容程序員


正文

Go語言標準庫 - net/http

在學習Go語言有一個很好的起點,Go語言官方文檔很詳細,今天咱們學習的Go Web服務器的搭建就須要用到Go語言官方提供的標準庫 net/http,經過http包提供了HTTP客戶端和服務端的實現。同時使用這個包能很簡單地對web的路由,靜態文件,模版,cookie等數據進行設置和操做。若是對http概念不是太清楚的朋友能夠自行googlegolang

http包創建Web服務器

package main
import (
    "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 Wrold!") //這個寫入到w的是輸出到客戶端的
}
func main() {
    http.HandleFunc("/", sayhelloName) //設置訪問的路由
    err := http.ListenAndServe(":9090", nil) //設置監聽的端口
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

  

上面的代碼咱們在IDE中編譯後並運行成功後,這個時侯咱們就能夠在9090端口監聽http連接請求了。web


上圖中,咱們在瀏覽器中輸入了 http://localhost:9090 ,能夠看到瀏覽器頁面中輸入出 Hello World!
這個時侯若是咱們在瀏覽器地址後面加一些參數試試:http://localhost:9090?url_long=111&url_long=222,看看瀏覽器中輸出什麼?服務器端輸出的又是什麼?編程


瀏覽器中輸出圖片

服務器端輸出圖片

咱們看到了上面的代碼,要編寫一個Web服務器是否是很簡單,只要調用http包的兩個函數就能夠了。
若是之前你是.NET程序員,那你也許就會問,咱們的IIS服務器不須要嗎?Go就是不須要這些,由於他直接就監聽了TCP端口了。
咱們看到Go經過簡單的幾行代碼就已經運行起來一個Web服務了,並且這個Web服務內部有支持高併發的特性。如今Web服務已經搭建完成了,那咱們如今來了解一個這個服務是怎麼運行起來的呢?瀏覽器

Web工做方式的幾個概念

如下幾個爲服務器段的概念服務器

  • Request:用戶請求的信息,用來解析用戶的請求信息,包括post、get、cookie、url等信息
  • Response:服務器須要反饋給客戶端的信息
  • Conn:用戶的每次請求連接
  • Handler:處理請求和生成返回信息的處理邏輯

分析http包運行機制


Go實現Web服務的工做模式流程圖


這個過程咱們須要清楚如下三個問題,則就清楚Go是如何讓Web運行起來了cookie

  • 如何監聽端口?
    經過上面的代碼咱們看到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
        if srv.ReadTimeout != 0 {
            rw.SetReadDeadline(time.Now().Add(srv.ReadTimeout))
        }
        if srv.WriteTimeout != 0 {
            rw.SetWriteDeadline(time.Now().Add(srv.WriteTimeout))
        }
        c, err := srv.newConn(rw)
        if err != nil {
            continue
        }
        go c.serve()
    }
    panic("not reached")
}

  

  • 如何接收客戶端請求?
    上面代碼執行監控端口以後,調用了srv.Serve(net.Listener)函數,這個函數就是處理接收客戶端的請求信 息。這個函數裏面起了一個for{},首先經過Listener接收請求,其次建立一個 Conn,最後單獨開了一個 goroutine,把這個請求的數據當作參數扔給這個conn去服務:go c.serve()。這 個就是高併發體現了, 用戶的每一次請求都是在一個新的goroutine去服務,相互不影響。
  • 如何分配handler?
    conn首先會解析request:c.readRequest(),而後獲取相應的handler:handler := c.server.Handler,也就是咱們剛纔在調用函數ListenAndServe時候的第二個參數,咱們前面例子傳遞的是nil,也就是爲空,那麼默認獲取handler = DefaultServeMux,那麼這個變量用來作什麼的呢?對,這個變量就是一個路由器,它用來匹配url跳轉到其相應的handle函數,那麼這個咱們有設置過嗎?有,咱們調用的代碼裏面第一句不是調用了http.HandleFunc("/", sayhelloName)嘛。這個做用就是註冊了請求/的路由規則,當請求uri爲"/",路由就會轉到函數sayhelloName,DefaultServeMux會調用ServeHTTP方法,這個方法內部其實就是調用sayhelloName自己,最後經過寫入response的信息反饋到客戶端。

一個http鏈接處理流程

至此咱們的三個問題已經所有獲得瞭解答,你如今對於Go如何讓Web跑起來的是否已經基本瞭解。tcp

相關文章
相關標籤/搜索