前面小節介紹了如何通過Go搭建一個Web服務,我們可以看到簡單應用一個net/http包就方便的搭建起來了。那么Go在底層到底是怎么做的呢?萬變不離其宗,Go的Web服務工作也離不開我們第一小節介紹的Web工作方式。
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.3.md#web工作方式的幾個概念)web工作方式的幾個概念
以下均是服務器端的幾個概念
Request:用戶請求的信息,用來解析用戶的請求信息,包括post、get、cookie、url等信息
Response:服務器需要反饋給客戶端的信息
Conn:用戶的每次請求鏈接
Handler:處理請求和生成返回信息的處理邏輯
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.3.md#分析http包運行機制)分析http包運行機制
如下圖所示,是Go實現Web服務的工作模式的流程圖
[](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/3.3.http.png?raw=true)
圖3.9 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的信息反饋到客戶端。
詳細的整個流程如下圖所示:
[](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/3.3.illustrator.png?raw=true)
圖3.10 一個http連接處理流程
至此我們的三個問題已經全部得到了解答,你現在對于Go如何讓Web跑起來的是否已經基本了解呢?
- 第一章 Go環境配置
- 1.1 Go安裝
- 1.2 GOPATH 與工作空間
- 1.3 Go 命令
- 1.4 Go開發工具
- 1.5 小結
- 第二章 Go語言基礎
- 2.1 你好,Go
- 2.2 Go基礎
- 2.3 流程和函數
- 2.4 struct類型
- 2.5 面向對象
- 2.6 interface
- 2.7 并發
- 2.8 總結
- 第三章 Web基礎
- 3.1 Web工作方式
- 3.2 Go搭建一個Web服務器
- 3.3 Go如何使得Web工作
- 3.4 Go的http包詳解
- 3.5 小結
- 第四章 表單
- 4.1 處理表單的輸入
- 4.2 驗證表單的輸入
- 4.3 預防跨站腳本
- 4.4 防止多次遞交表單
- 4.5 處理文件上傳
- 4.6 小結
- 第五章 訪問數據庫
- 5.1 database/sql接口
- 5.2 使用MySQL數據庫
- 5.3 使用SQLite數據庫
- 5.4 使用PostgreSQL數據庫
- 5.5 使用beedb庫進行ORM開發
- 5.6 NOSQL數據庫操作
- 5.7 小結
- 第六章 session和數據存儲
- 6.1 session和cookie
- 6.2 Go如何使用session
- 6.3 session存儲
- 6.4 預防session劫持
- 6.5 小結
- 第七章 文本處理
- 7.1 XML處理
- 7.2 JSON處理
- 7.3 正則處理
- 7.4 模板處理
- 7.5 文件操作
- 7.6 字符串處理
- 7.7 小結
- 第八章 Web服務
- 8.1 Socket編程
- 8.2 WebSocket
- 8.3 REST
- 8.4 RPC
- 8.5 小結
- 第九章 安全與加密
- 9.1 預防CSRF攻擊
- 9.2 確保輸入過濾
- 9.3 避免XSS攻擊
- 9.4 避免SQL注入
- 9.5 存儲密碼
- 9.6 加密和解密數據
- 9.7 小結
- 第十章 國際化和本地化
- 10.1 設置默認地區
- 10.2 本地化資源
- 10.3 國際化站點
- 10.4 小結
- 第十一章 錯誤處理,調試和測試
- 11.1 錯誤處理
- 11.2 使用GDB調試
- 11.3 Go怎么寫測試用例
- 11.4 小結
- 第十二章 部署與維護
- 12.1 應用日志
- 12.2 網站錯誤處理
- 12.3 應用部署
- 12.4 備份和恢復
- 12.5 小結
- 第十三章 如何設計一個Web框架
- 13.1 項目規劃
- 13.2 自定義路由器設計
- 13.3 controller設計
- 13.4 日志和配置設計
- 13.5 實現博客的增刪改
- 13.6 小結
- 第十四章 擴展Web框架
- 14.1 靜態文件支持
- 14.2 Session支持
- 14.3 表單及驗證支持
- 14.4 用戶認證
- 14.5 多語言支持
- 14.6 pprof支持
- 14.7 小結
- 附錄A 參考資料