【go語言 socket程式設計系列】一個簡單的HTTP伺服器及func (srv *Server) Serve(l net.Listener) 方法
【簡單的HTTP伺服器】
原始檔server.go中 ListenAndServe()函式的註釋中有個簡單的HTTP服務實現程式碼,如下
package main import ( "io" "log" "net/http" ) func HelloServer(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "hello ,this is from HelloServer func ") } func main() { http.HandleFunc("/hello", HelloServer) log.Fatal(http.ListenAndServe(":12345", nil)) }
【func (srv *Server) Serve(l net.Listener) 方法】
之前有梳理過http.ListenAndServe() 最終會呼叫 Server型別的Serve()方法。再看下其原始碼如下 net/http/server.go 中
2678 func (srv *Server) Serve(l net.Listener) error { 2679 defer l.Close() 2680 if fn := testHookServerServe; fn != nil { 2681 fn(srv, l) 2682 } 2683 var tempDelay time.Duration // how long to sleep on accept failure 2684 2685 if err := srv.setupHTTP2_Serve(); err != nil { 2686 return err 2687 } 2688 2689 srv.trackListener(l, true) 2690 defer srv.trackListener(l, false) 2691 2692 baseCtx := context.Background() // base is always background, per Issue 16220 2693 ctx := context.WithValue(baseCtx, ServerContextKey, srv) 2694 for { 2695 rw, e := l.Accept() 2696 if e != nil { 2697 select { 2698 case <-srv.getDoneChan(): 2699 return ErrServerClosed 2700 default: 2701 } 2702 if ne, ok := e.(net.Error); ok && ne.Temporary() { 2703 if tempDelay == 0 { 2704 tempDelay = 5 * time.Millisecond 2705 } else { 2706 tempDelay *= 2 2707 } 2708 if max := 1 * time.Second; tempDelay > max { 2709 tempDelay = max 2710 } 2711 srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) 2712 time.Sleep(tempDelay) 2713 continue 2714 } 2715 return e 2716 } 2717 tempDelay = 0 2718 c := srv.newConn(rw) 2719 c.setState(c.rwc, StateNew) // before Serve can return 2720 go c.serve(ctx) 2721 } 2722 }
第2683行定義了一個 time.Duration 型別的區域性變數,用於控制 accept失敗後的等待時間。後續的select語句會用到。
var tempDelay time.Duration // how long to sleep on accept failure
2689 srv.trackListener(l, true)
2690 defer srv.trackListener(l, false)
原始碼如下,呼叫了Server型別的trackListener()方法,私有函式,用於對 *Server 資料的併發保護,s.listeners為Server型別的成員變數,型別是一個map,key為 net.Listener ,value為 Struce{}。 Server 型別可以是零值的,所以在使用listeners 成員的時候 要做個nil的判斷,通過make()內建函式初始化。
2767 func (s *Server) trackListener(ln net.Listener, add bool) {
2768 s.mu.Lock()
2769 defer s.mu.Unlock()
2770 if s.listeners == nil {
2771 s.listeners = make(map[net.Listener]struct{})
2772 }
2773 if add {
2774 // If the *Server is being reused after a previous
2775 // Close or Shutdown, reset its doneChan:
2776 if len(s.listeners) == 0 && len(s.activeConn) == 0 {
2777 s.doneChan = nil
2778 }
2779 s.listeners[ln] = struct{}{}
2780 } else {
2781 delete(s.listeners, ln)
2782 }
2783 }
接下來的for迴圈,原始碼如下
2694 for {
2695 rw, e := l.Accept()
2696 if e != nil {
2697 select {
2698 case <-srv.getDoneChan():
2699 return ErrServerClosed
2700 default:
2701 }
2702 if ne, ok := e.(net.Error); ok && ne.Temporary() {
2703 if tempDelay == 0 {
2704 tempDelay = 5 * time.Millisecond
2705 } else {
2706 tempDelay *= 2
2707 }
2708 if max := 1 * time.Second; tempDelay > max {
2709 tempDelay = max
2710 }
2711 srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
2712 time.Sleep(tempDelay)
2713 continue
2714 }
2715 return e
2716 }
2717 tempDelay = 0
2718 c := srv.newConn(rw)
2719 c.setState(c.rwc, StateNew) // before Serve can return
2720 go c.serve(ctx)
主要做了5件事情
- 通過Accept() 監聽請求
- Accpet錯誤處理
- 通過Server型別的newConn方法構建 conn型別資料(小寫c)
- 設定conn狀態
- 併發執行conn
1、l.Accept() 預設是執行net.Listener 介面的 Accept() 方法,即: Accept() (Conn, error)
之前梳理 ListenAndServe的時候,有個tcpKeepAliveListener 型別的資料,重新實現了Accept方法,增加了超時時間的設定。
2、通過select語句實現錯誤等待。case阻塞後,會執行default語句。通過Temporary()簡單判斷
3、 c := srv.newConn(rw) 中 newConn()方法程式碼如下,構造conn型別。
func (srv *Server) newConn(rwc net.Conn) *conn {
c := &conn{
server: srv,
rwc: rwc,
}
if debugServerConnections {
c.rwc = newLoggingConn("server", c.rwc)
}
return c
}
conn型別為server.go 中的私有型別,較複雜,需要了解的直接看原始碼
4、c.setState(c.rwc, StateNew) 設定conn的狀態,setState()原始碼如下,根據引數state的值來設定狀態
1634 func (c *conn) setState(nc net.Conn, state ConnState) {
1635 srv := c.server
1636 switch state {
1637 case StateNew:
1638 srv.trackConn(c, true)
1639 case StateHijacked, StateClosed:
1640 srv.trackConn(c, false)
1641 }
1642 c.curState.Store(connStateInterface[state])
1643 if hook := srv.ConnState; hook != nil {
1644 hook(nc, state)
1645 }
1646 }
引數state的數值如下:
var stateName = map[ConnState]string{
StateNew: "new",
StateActive: "active",
StateIdle: "idle",
StateHijacked: "hijacked",
StateClosed: "closed",
5、go c.serve(ctx)併發執行 請求。其原型如下 func (c *conn) serve(ctx context.Context)。原始碼邏輯較複雜,我們值關注下面這行,即 ServeHTTP(w, w.req)。
1794 // HTTP cannot have multiple simultaneous active requests.[*]
1795 // Until the server replies to this request, it can't read another,
1796 // so we might as well run the handler in this goroutine.
1797 // [*] Not strictly true: HTTP pipelining. We could let them all process
1798 // in parallel even if their responses need to be serialized.
1799 // But we're not going to implement HTTP pipelining because it
1800 // was never deployed in the wild and the answer is HTTP/2.
1801 serverHandler{c.server}.ServeHTTP(w, w.req)
回到開始的例子,HelloServer(w http.ResponseWriter, r *http.Request) 的簽名與 ServeHTTP(w, w.req)是一致的.即 ListerAndServe → Server.Serve() → conn.serve 最後執行 ServeHTTP(w, w.req) 簽名格式的邏輯,在本例中就是HelloServer自定義函式。最終HTTP的通訊通過Request 和Response 來實現。
相關推薦
【go語言 socket程式設計系列】一個簡單的HTTP伺服器及func (srv *Server) Serve(l net.Listener) 方法
【簡單的HTTP伺服器】 原始檔server.go中 ListenAndServe()函式的註釋中有個簡單的HTTP服務實現程式碼,如下 package main import ( "io" "log" "net/http" ) func HelloServ
【go語言 socket程式設計系列】Request型別 http.Get方法及http.NewRequest方法
【Request型別】 Request型別定義在request.go檔案中,用於設定一個http請求來發送給服務端。 // A Request represents an HTTP request received by a server // or to be sen
【go語言 socket程式設計系列】IPAddr型別及ResolveIPAddr方法
【型別定義】 IPAddr型別本質上是一個IP型別,原始碼定義檔案:golang/src/pkg/net/iprawsock.go 常用方法會返回一個*IPAddr的資料。 package net // IPAddr represents the address of
【go語言 socket程式設計系列】TCPConn型別與ne.tDialTCP方法
【TCPConn】 netTCPConn是允許服務端與客戶端之間的全雙工通訊的Go型別。其定義在tcpsock_posix.go檔案。 其定義如下 type TCPConn struct { conn } 注意到 conn 是小寫的c,其定義在net.
【go語言 socket程式設計系列】從單執行緒到簡單多執行緒的服務端搭建
簡單單執行緒serverdemo 通過下面程式碼簡單搭建一個服務端,並通過telnet模擬客戶端,演示多客戶端同時請求訪問單執行緒伺服器時的阻塞現象。 package main import ( "fmt" "net" "os" ) func main() {
[C++] Windows下的socket程式設計(這是一個簡單的TCP/IP例子)
Socket的概念: Socket,即套接字,用於描述地址和埠,是一個通訊鏈的控制代碼。 應用程式通過Socket像網路發出請求或者回應。 什麼事客戶/伺服器模式: 在TCP/IP網路應用中,通訊的兩個程序相互作用的主要模式是客戶/伺服器模
Windows 上靜態編譯 Libevent 2.0.10 並實現一個簡單 HTTP 伺服器
假設 Visual Studio 2005 的安裝路徑為“D:\Program Files\Microsoft Visual Studio 8\”,Libevent 2.0.10 解壓後的路徑為“D:\libevent-2.0.10-stable”。 編譯生成L
【go語言 基礎系列】陣列及slice
【陣列】 Go語言處理陣列特別的地方是:go把陣列看成是值傳遞 如果需要傳引用,需要額外處理 *[5]int 如下demo package main import ( "fmt" ) func main() { var arr1 = [5]int{1,
【go語言 基礎系列】內建函式
原始檔builtin.go檔案中一共定義了15個內建函式,go1.9.2 版本。通過函式名可以直接呼叫函式。 func append(slice []Type, elems ...Type) []Type func copy(dst, src []Type) int
【鏈塊技術35期】區塊鏈技術語言——Go語言併發程式設計(上)
併發程式設計分為上、下兩節。這一節包括了併發程式設計的概述、goroutine和channel的部分內容。 一、概述 1.1 並行和併發 並行(parallel):在多個處理器上同時執行多條指令,如圖1所示。 併發(concurrency):同一時刻只有一條指令在
【鏈塊技術50期】區塊鏈技術基礎語言(三十二):Go語言網路程式設計(下)
原文連結:區塊鏈技術基礎語言(三十二):Go語言網路程式設計(下) 本文緊接上文所述,講解socket程式設計和HTTP程式設計。 一、socket程式設計 在上一節我們介紹了網路體系的五層模型,介紹了每層模型所遵守的協議。TCP/IP是一個協議族,它由網路層的IP協議
【視窗程式設計】一個簡單的C語言視窗程式
#include <windows.h> //回撥函式 LRESULT CALLBACK WinProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) { switch(message) { cas
【Go語言入門系列】(七)如何使用Go的方法?
[【Go語言入門系列】](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1441283546689404928)前面的文章: - [【Go語言入門系列】(四)之map的使用](https://mp.weixin.qq
【Go語言入門系列】(八)Go語言是不是面嚮物件語言?
[【Go語言入門系列】](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1441283546689404928)前面的文章: - [【Go語言入門系列】(五)指標和結構體的使用](https://mp.weixin.
【Go語言入門系列】(九)寫這些就是為了搞懂怎麼用介面
[【Go語言入門系列】](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1441283546689404928)前面的文章: - [【Go語言入門系列】(六)再探函式](https://mp.weixin.qq.co
【Go語言】map在goroutine通信中的使用
cannot .net html goroutin field tail 問題 tar ocs 簡介 本篇文章的主要內容是解決go語言map在使用中遇到的兩個問題 一、cannot assign to struct field https://haobook.readthe
【go 語言環境安裝】goland語言環境安裝配置詳解
1、下載go 下載地址: https://golang.google.cn/dl/ 開啟網址,由於我的電腦是win64,所以下載第一個。 2、安裝go 2.1、UNIX/Linux/Mac OS X, 和 FreeBSD 安裝 以下介紹了在UNIX/Linux/Mac OS
【Python面試必看系列】之怎麼移除一個字串中的前導空格?
Q 20. 怎麼移除一個字串中的前導空格? 字串中的前導空格就是出現在字串中第一個非空格字元前的空格。我們使用方法 Istrip() 可以將它從字串中移除。 >>> ' Ayushi '.lstrip() 結果: ‘Ayushi ‘ 可以看到
【Python面試必看系列】之如何以就地操作方式打亂一個列表的元素
Q 16. 如何以就地操作方式打亂一個列表的元素? 為了達到這個目的,我們從 random 模組中匯入 shuffle() 函式。 >>> from random import shuffle >>> mylist=[x for x in ra
【Python面試必看系列】之計算一個檔案中的大寫字母數量
Q 14. 請寫一個 Python 邏輯,計算一個檔案中的大寫字母數量 首先在程式所在資料夾下面新建一個test.txt檔案,裡面寫入一些大小寫字母,然後執行下面的程式即可: with open('test.txt') as test: count = 0 fo