1. 程式人生 > >【go語言 socket程式設計系列】一個簡單的HTTP伺服器及func (srv *Server) Serve(l net.Listener) 方法

【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