Go語言Web基礎--Go如何使得Web工作
發表時(shí)間:2018-7-22
發布人(rén):融晨科技
浏覽次數:92
Go搭建一個(gè)Web服務器
前面小節已經介紹了(le/liǎo)Web是(shì)基于(yú)http協議的(de)一個(gè)服務,Go語言裏面提供了(le/liǎo)一個(gè)完善的(de)net/http包,通過http包可以(yǐ)很方便的(de)就(jiù)搭建起來(lái)一個(gè)可以(yǐ)運行的(de)Web服務。同時(shí)使用這(zhè)個(gè)包能很簡單地(dì / de)對Web的(de)路由,靜态文件,模版,cookie等數據進行設置和(hé / huò)操作。
http包建立Web服務器
package main
import (
"fmt"
"net/http"
"strings"
"log"
)
func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() //解析參數,默認是(shì)不(bù)會解析的(de)
fmt.Println(r.Form) //這(zhè)些信息是(shì)輸出(chū)到(dào)服務器端的(de)打印信息
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!") //這(zhè)個(gè)寫入到(dào)w的(de)是(shì)輸出(chū)到(dào)客戶端的(de)
}
func main() {
http.HandleFunc("/", sayhelloName) //設置訪問的(de)路由
err := http.ListenAndServe(":9090", nil) //設置監聽的(de)端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
上(shàng)面這(zhè)個(gè)代碼,我們build之(zhī)後,然後執行web.exe,這(zhè)個(gè)時(shí)候其實已經在(zài)9090端口監聽http鏈接請求了(le/liǎo)。
在(zài)浏覽器輸入http://localhost:9090
可以(yǐ)看到(dào)浏覽器頁面輸出(chū)了(le/liǎo)Hello astaxie!
可以(yǐ)換一個(gè)地(dì / de)址試試:http://localhost:9090/?url_long=111&url_long=222
看看浏覽器輸出(chū)的(de)是(shì)什麽,服務器輸出(chū)的(de)是(shì)什麽?
在(zài)服務器端輸出(chū)的(de)信息如下:
我們看到(dào)上(shàng)面的(de)代碼,要(yào / yāo)編寫一個(gè)Web服務器很簡單,隻要(yào / yāo)調用http包的(de)兩個(gè)函數就(jiù)可以(yǐ)了(le/liǎo)。
如果你以(yǐ)前是(shì)PHP程序員,那你也(yě)許就(jiù)會問,我們的(de)nginx、apache服務器不(bù)需要(yào / yāo)嗎?Go就(jiù)是(shì)不(bù)需要(yào / yāo)這(zhè)些,因爲(wéi / wèi)他(tā)直接就(jiù)監聽tcp端口了(le/liǎo),做了(le/liǎo)nginx做的(de)事情,然後sayhelloName這(zhè)個(gè)其實就(jiù)是(shì)我們寫的(de)邏輯函數了(le/liǎo),跟php裏面的(de)控制層(controller)函數類似。
如果你以(yǐ)前是(shì)Python程序員,那麽你一定聽說(shuō)過tornado,這(zhè)個(gè)代碼和(hé / huò)他(tā)是(shì)不(bù)是(shì)很像,對,沒錯,Go就(jiù)是(shì)擁有類似Python這(zhè)樣動态語言的(de)特性,寫Web應用很方便。
如果你以(yǐ)前是(shì)Ruby程序員,會發現和(hé / huò)ROR的(de)/script/server啓動有點類似。
我們看到(dào)Go通過簡單的(de)幾行代碼就(jiù)已經運行起來(lái)一個(gè)Web服務了(le/liǎo),而(ér)且這(zhè)個(gè)Web服務内部有支持高并發的(de)特性,我将會在(zài)接下來(lái)的(de)兩個(gè)小節裏面詳細的(de)講解一下Go是(shì)如何實現Web高并發的(de)。
Go如何使得Web工作
前面介紹了(le/liǎo)如何通過Go搭建一個(gè)Web服務,我們可以(yǐ)看到(dào)簡單應用一個(gè)net/http包就(jiù)方便的(de)搭建起來(lái)了(le/liǎo)。那麽Go在(zài)底層到(dào)底是(shì)怎麽做的(de)呢?萬變不(bù)離其宗,Go的(de)Web服務工作也(yě)離不(bù)開我們前面介紹的(de)Web工作方式。
web工作方式的(de)幾個(gè)概念
以(yǐ)下均是(shì)服務器端的(de)幾個(gè)概念
- Request:用戶請求的(de)信息,用來(lái)解析用戶的(de)請求信息,包括post、get、cookie、url等信息
- Response:服務器需要(yào / yāo)反饋給客戶端的(de)信息
- Conn:用戶的(de)每次請求鏈接
- Handler:處理請求和(hé / huò)生成返回信息的(de)處理邏輯
分析http包運行機制
如下圖所示,是(shì)Go實現Web服務的(de)工作模式的(de)流程圖
http包執行流程:
-
創建Listen Socket, 監聽指定的(de)端口, 等待客戶端請求到(dào)來(lái)。
-
Listen Socket接受客戶端的(de)請求, 得到(dào)Client Socket, 接下來(lái)通過Client Socket與客戶端通信。
-
處理客戶端的(de)請求, 首先從Client Socket讀取HTTP請求的(de)協議頭, 如果是(shì)POST方法, 還可能要(yào / yāo)讀取客戶端提交的(de)數據, 然後交給相應的(de)handler處理請求, handler處理完畢準備好客戶端需要(yào / yāo)的(de)數據, 通過Client Socket寫給客戶端。
這(zhè)整個(gè)的(de)過程裏面我們隻要(yào / yāo)了(le/liǎo)解清楚下面三個(gè)問題,也(yě)就(jiù)知道(dào)Go是(shì)如何讓Web運行起來(lái)了(le/liǎo)
- 如何監聽端口?
- 如何接收客戶端請求?
- 如何分配handler?
Go是(shì)通過一個(gè)函數ListenAndServe
來(lái)處理這(zhè)些事情的(de),這(zhè)個(gè)底層其實這(zhè)樣處理的(de):初始化一個(gè)server對象,然後調用了(le/liǎo)net.Listen("tcp", addr)
,也(yě)就(jiù)是(shì)底層用TCP協議搭建了(le/liǎo)一個(gè)服務,然後監控我們設置的(de)端口。
下面代碼來(lái)自Go的(de)http包的(de)源碼,通過下面的(de)代碼我們可以(yǐ)看到(dào)整個(gè)的(de)http處理過程:
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
var tempDelay time.Duration // how long to sleep>監控之(zhī)後如何接收客戶端的(de)請求呢?上(shàng)面代碼執行監控端口之(zhī)後,調用了(le/liǎo)srv.Serve(net.Listener)
函數,這(zhè)個(gè)函數就(jiù)是(shì)處理接收客戶端的(de)請求信息。這(zhè)個(gè)函數裏面起了(le/liǎo)一個(gè)for{}
,首先通過Listener接收請求,其次創建一個(gè)Conn,最後單獨開了(le/liǎo)一個(gè)goroutine,把這(zhè)個(gè)請求的(de)數據當做參數扔給這(zhè)個(gè)conn去服務:go c.serve()
。這(zhè)個(gè)就(jiù)是(shì)高并發體現了(le/liǎo),用戶的(de)每一次請求都是(shì)在(zài)一個(gè)新的(de)goroutine去服務,相互不(bù)影響。
那麽如何具體分配到(dào)相應的(de)函數來(lái)處理請求呢?conn首先會解析request:c.readRequest()
,然後獲取相應的(de)handler:handler := c.server.Handler
,也(yě)就(jiù)是(shì)我們剛才在(zài)調用函數ListenAndServe
時(shí)候的(de)第二個(gè)參數,我們前面例子(zǐ)傳遞的(de)是(shì)nil,也(yě)就(jiù)是(shì)爲(wéi / wèi)空,那麽默認獲取handler = DefaultServeMux
,那麽這(zhè)個(gè)變量用來(lái)做什麽的(de)呢?對,這(zhè)個(gè)變量就(jiù)是(shì)一個(gè)路由器,它用來(lái)匹配url跳轉到(dào)其相應的(de)handle函數,那麽這(zhè)個(gè)我們有設置過嗎?有,我們調用的(de)代碼裏面第一句不(bù)是(shì)調用了(le/liǎo)http.HandleFunc("/", sayhelloName)
嘛。這(zhè)個(gè)作用就(jiù)是(shì)注冊了(le/liǎo)請求/
的(de)路由規則,當請求uri爲(wéi / wèi)"/",路由就(jiù)會轉到(dào)函數sayhelloName,DefaultServeMux會調用ServeHTTP方法,這(zhè)個(gè)方法内部其實就(jiù)是(shì)調用sayhelloName本身,最後通過寫入response的(de)信息反饋到(dào)客戶端。
詳細的(de)整個(gè)流程如下圖所示:

至此我們的(de)三個(gè)問題已經全部得到(dào)了(le/liǎo)解答,你現在(zài)對于(yú)Go如何讓Web跑起來(lái)的(de)是(shì)否已經基本了(le/liǎo)解呢?
End