Golang學(xué)習(xí)-第二篇 搭建一個(gè)簡(jiǎn)單的Go Web服務(wù)器

序言

由于本人一直從事Web服務(wù)器端的程序開(kāi)發(fā),所以在學(xué)習(xí)Golang也想從Web這里開(kāi)始學(xué)起,如果對(duì)Golang還不太清楚怎么搭建環(huán)境的朋友們可以參考我的上一篇文章 Golang的簡(jiǎn)單介紹及Windows環(huán)境下安裝、部署,這一篇我們來(lái)了解一下Golang的Web開(kāi)發(fā)入門:搭建一個(gè)簡(jiǎn)單的Go Web服務(wù)器。

注:此文借鑒了Astaxie《Go Web編程》一書中的內(nèi)容


正文

Go語(yǔ)言標(biāo)準(zhǔn)庫(kù) - net/http

在學(xué)習(xí)Go語(yǔ)言有一個(gè)很好的起點(diǎn),Go語(yǔ)言官方文檔很詳細(xì),今天我們學(xué)習(xí)的Go Web服務(wù)器的搭建就需要用到Go語(yǔ)言官方提供的標(biāo)準(zhǔn)庫(kù) net/http,通過(guò)http包提供了HTTP客戶端和服務(wù)端的實(shí)現(xiàn)。同時(shí)使用這個(gè)包能很簡(jiǎn)單地對(duì)web的路由,靜態(tài)文件,模版,cookie等數(shù)據(jù)進(jìn)行設(shè)置和操作。如果對(duì)http概念不是太清楚的朋友可以自行google。

http包建立Web服務(wù)器

package main
import (
    "fmt"
    "net/http"
    "strings"
    "log"
)
func sayhelloName(w http.ResponseWriter, r *http.Request) {
    r.ParseForm() //解析參數(shù),默認(rèn)是不會(huì)解析的
    fmt.Println(r.Form) //這些信息是輸出到服務(wù)器端的打印信息
    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!") //這個(gè)寫入到w的是輸出到客戶端的
}
func main() {
    http.HandleFunc("/", sayhelloName) //設(shè)置訪問(wèn)的路由
    err := http.ListenAndServe(":9090", nil) //設(shè)置監(jiān)聽(tīng)的端口
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

上面的代碼我們?cè)贗DE中編譯后并運(yùn)行成功后,這個(gè)時(shí)侯我們就可以在9090端口監(jiān)聽(tīng)http鏈接請(qǐng)求了。


上圖中,我們?cè)跒g覽器中輸入了 http://localhost:9090 ,可以看到瀏覽器頁(yè)面中輸入出 Hello World!
這個(gè)時(shí)侯如果我們?cè)跒g覽器地址后面加一些參數(shù)試試:http://localhost:9090?url_long=111&url_long=222,看看瀏覽器中輸出什么?服務(wù)器端輸出的又是什么?

瀏覽器中輸出圖片
服務(wù)器端輸出圖片

我們看到了上面的代碼,要編寫一個(gè)Web服務(wù)器是不是很簡(jiǎn)單,只要調(diào)用http包的兩個(gè)函數(shù)就可以了。
如果以前你是.NET程序員,那你也許就會(huì)問(wèn),我們的IIS服務(wù)器不需要嗎?Go就是不需要這些,因?yàn)樗苯泳捅O(jiān)聽(tīng)了TCP端口了。
我們看到Go通過(guò)簡(jiǎn)單的幾行代碼就已經(jīng)運(yùn)行起來(lái)一個(gè)Web服務(wù)了,而且這個(gè)Web服務(wù)內(nèi)部有支持高并發(fā)的特性。現(xiàn)在Web服務(wù)已經(jīng)搭建完成了,那我們現(xiàn)在來(lái)了解一個(gè)這個(gè)服務(wù)是怎么運(yùn)行起來(lái)的呢?

Web工作方式的幾個(gè)概念

以下幾個(gè)為服務(wù)器段的概念

  • Request:用戶請(qǐng)求的信息,用來(lái)解析用戶的請(qǐng)求信息,包括post、get、cookie、url等信息
  • Response:服務(wù)器需要反饋給客戶端的信息
  • Conn:用戶的每次請(qǐng)求鏈接
  • Handler:處理請(qǐng)求和生成返回信息的處理邏輯

分析http包運(yùn)行機(jī)制

Go實(shí)現(xiàn)Web服務(wù)的工作模式流程圖

這個(gè)過(guò)程我們需要清楚以下三個(gè)問(wèn)題,則就清楚Go是如何讓W(xué)eb運(yùn)行起來(lái)了

  • 如何監(jiān)聽(tīng)端口?
    通過(guò)上面的代碼我們看到Go是通過(guò)一個(gè)函數(shù)ListenAndServe來(lái)處理這些事情的,這個(gè)底層其實(shí)這樣處
    理的:初始化一個(gè)server對(duì)象,然后調(diào)用了net.Listen("tcp", addr),也就是底層用TCP協(xié)議搭建了一個(gè)服
    務(wù),然后監(jiān)控我們?cè)O(shè)置的端口。

Go http包的源碼,這里我們可以看到整個(gè)http處理過(guò)程

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")
}
  • 如何接收客戶端請(qǐng)求?
    上面代碼執(zhí)行監(jiān)控端口之后,調(diào)用了srv.Serve(net.Listener)函數(shù),這個(gè)函數(shù)就是處理接收客戶端的請(qǐng)求信 息。這個(gè)函數(shù)里面起了一個(gè)for{},首先通過(guò)Listener接收請(qǐng)求,其次創(chuàng)建一個(gè) Conn,最后單獨(dú)開(kāi)了一個(gè) goroutine,把這個(gè)請(qǐng)求的數(shù)據(jù)當(dāng)做參數(shù)扔給這個(gè)conn去服務(wù):go c.serve()。這 個(gè)就是高并發(fā)體現(xiàn)了, 用戶的每一次請(qǐng)求都是在一個(gè)新的goroutine去服務(wù),相互不影響。
  • 如何分配handler?
    conn首先會(huì)解析request:c.readRequest(),然后獲取相應(yīng)的handler:handler := c.server.Handler,也就是我們剛才在調(diào)用函數(shù)ListenAndServe時(shí)候的第二個(gè)參數(shù),我們前面例子傳遞的是nil,也就是為空,那么默認(rèn)獲取handler = DefaultServeMux,那么這個(gè)變量用來(lái)做什么的呢?對(duì),這個(gè)變量就是一個(gè)路由器,它用來(lái)匹配url跳轉(zhuǎn)到其相應(yīng)的handle函數(shù),那么這個(gè)我們有設(shè)置過(guò)嗎?有,我們調(diào)用的代碼里面第一句不是調(diào)用了http.HandleFunc("/", sayhelloName)嘛。這個(gè)作用就是注冊(cè)了請(qǐng)求/的路由規(guī)則,當(dāng)請(qǐng)求uri為"/",路由就會(huì)轉(zhuǎn)到函數(shù)sayhelloName,DefaultServeMux會(huì)調(diào)用ServeHTTP方法,這個(gè)方法內(nèi)部其實(shí)就是調(diào)用sayhelloName本身,最后通過(guò)寫入response的信息反饋到客戶端。
一個(gè)http連接處理流程

至此我們的三個(gè)問(wèn)題已經(jīng)全部得到了解答,你現(xiàn)在對(duì)于Go如何讓W(xué)eb跑起來(lái)的是否已經(jīng)基本了解。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,578評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,094評(píng)論 25 709
  • 原文鏈接 http://ironxu.com/779 Go Web 基礎(chǔ)概念與代碼閱讀 1. Go 搭建簡(jiǎn)單的we...
    好剛編程閱讀 4,266評(píng)論 1 18
  • 簡(jiǎn)介和要求 教程將涵蓋以下知識(shí): 創(chuàng)建一個(gè)帶有l(wèi)oad和save方法的數(shù)據(jù)結(jié)構(gòu)體 使用net/http包來(lái)創(chuàng)建we...
    煎魚不可能有BUG閱讀 9,419評(píng)論 2 24
  • 有時(shí)候總會(huì)問(wèn)自己?jiǎn)栕约簽槭裁疵刻飚嫯?,難道不累嗎,不煩嗎。 然后我會(huì)想了想,我只不過(guò)在堅(jiān)持做自己喜歡的事,既然是喜...
    RongCZ閱讀 567評(píng)論 21 31

友情鏈接更多精彩內(nèi)容