• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Fork me on GitHub
            隨筆 - 215  文章 - 13  trackbacks - 0
            <2025年7月>
            293012345
            6789101112
            13141516171819
            20212223242526
            272829303112
            3456789


            專注即時通訊及網游服務端編程
            ------------------------------------
            Openresty 官方模塊
            Openresty 標準模塊(Opm)
            Openresty 三方模塊
            ------------------------------------
            本博收藏大部分文章為轉載,并在文章開頭給出了原文出處,如有再轉,敬請保留相關信息,這是大家對原創作者勞動成果的自覺尊重!!如為您帶來不便,請于本博下留言,謝謝配合。

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            相冊

            Awesome

            Blog

            Book

            GitHub

            Link

            搜索

            •  

            積分與排名

            • 積分 - 216915
            • 排名 - 118

            最新評論

            閱讀排行榜

            Golang標準庫探秘(一):sync 標準庫
            http://www.infoq.com/cn/articles/golang-standard-library-part01

            在高并發或者海量數據的生產環境中,我們會遇到很多問題,GC(garbage collection,中文譯成垃圾回收)就是其中之一。說起優化GC我們首先想到的肯定是讓對象可重用,這就需要一個對象池來存儲待回收對象,等待下次重用,從而減少對象產生數量。

            標準庫原生的對象池

            在Golang1.3版本便已新增了sync.Pool功能,它就是用來保存和復用臨時對象,以減少內存分配,降低CG壓力,下面就來講講sync.Pool的基本用法。

            type Pool struct {
                 local unsafe.Pointer
                 localSize uintptr
                 New func() interface{}
            }
            

            很簡潔,最常用的兩個函數Get/Put

               var pool = &sync.Pool{New:func()interface{}{return NewObject()}}
                pool.Put()
                Pool.Get()
            

            對象池底層數據結構

            我們選擇用Golang的container標準包中的鏈表來做對象池的底層數據結構,它被封裝在container/list標準包里:

               type Element struct {
                      next, prev *Element
                      list *List     
                      Value interface{}
                 }
            

            這里是定義了鏈表中的元素,這個標準庫實現的是一個雙向鏈表,并且已經為我們封裝好了各種Front/Back方法。不過Front方法的實現和我們需要的還是有點差異,它只是返回鏈表中的第一個元素,但這個元素依然會鏈接在鏈表里,所以我們需要自行將它從鏈表中刪除,remove方法如下:

            func (l *List) remove(e *Element) *Element {
                  e.prev.next = e.next
                    e.next.prev = e.prev
                    e.next = nil
                    e.prev = nil
                    e.list = nil
                    l.len--
                    return e
             }
            

            這樣對象池的核心部分就完成了,但注意一下,從remove函數可以看出,container/list并不是線程安全的,所以在對象池的對象個數統計等一些功能會有問題。

            原子操作并發安全

            下面我們來自行解決并發安全的問題。Golang的sync標準包封裝了常用的原子操作和鎖操作。
            sync/atomic封裝了常用的原子操作。所謂原子操作就是在針對某個值進行操作的整個過程中,為了實現嚴謹性必須由一個獨立的CPU指令完成,該過程不能被其他操作中斷,以保證該操作的并發安全性。

              `type ConnPool struct {
                conns []*conn
                mu sync.Mutex // lock protected
                len int32
            }`
            

            在Golang中,我們常用的數據類型除了channel之外都不是線程安全的,所以在這里我們需要對數量(len)和切片(conns []*conn)做并發保護。至于需要幾把鎖做保護,取決于實際場景,合理控制鎖的粒度。
            接著介紹一下鎖操作,我們在Golang中常用的鎖——互斥鎖(Lock)和讀寫鎖(RWLock),互斥鎖和讀寫鎖的區別是:互斥鎖無論是讀操作還是寫操作都會對目標加鎖也就是說所有的操作都需要排隊進行,讀寫鎖是加鎖后寫操作只能排隊進行但是可以并發進行讀操作,要注意一點就是讀的時候寫操作是阻塞的,寫操作進行的時候讀操作是阻塞的。類型sync.Mutex/sync.RWMutex的零值表示了未被鎖定的互斥量。也就是說,它是一個開箱即用的工具。只需對它進行簡單聲明就可以正常使用了,例如(在這里以Mutex為例,相對于RWMutex也是同理):

             var mu sync.Mutex
                mu.Lock()
                mu.Unlock()
            

            鎖操作一定要成對出現,也就是說在加鎖之后操作的某一個地方一定要記得釋放鎖,否則再次加鎖會造成死鎖問題

            fatal error: all goroutines are asleep - deadlock
            

            不過在Golang里這種錯誤發生的幾率會很少,因為有defer延時函數的存在
            上面的代碼可以改寫為

            var mu sync.Mutex
            mu.Lock()
            defer mu.Unlock()
            

            在加鎖之后馬上用defer函數進行解鎖操作,這樣即使下面我們只關心函數邏輯而在函數退出的時候忘記Unlock操作也不會造成死鎖,因為在函數退出的時候會自動執行defer延時函數釋放鎖。

            標準庫中的并發控制-WaitGroup

            sync標準包還封裝了其他很有用的功能,比如WaitGroup,它能夠一直等到所有的goroutine執行完成,并且阻塞主線程的執行,直到所有的goroutine(Golang中并發執行的協程)執行完成。文章開始我們說過,Golang是支持并發的語言,在其他goroutine異步運行的時候主協程并不知道其他協程是否運行結束,一旦主協程退出那所有的協程就會退出,這時我們需要控制主協程退出的時間,常用的方法:

            1、time.Sleep()

            讓主協程睡一會,好方法,但是睡多久呢?不確定(最簡單暴力)

            2、channel

            在主協程一直阻塞等待一個退出信號,在其他協程完成任務后給主協程發送一個信號,主協程收到這個信號后退出

            e := make(chan bool)
             go func() {
                  fmt.Println("hello")
                  e <- true
             }()
             <-e
            

            3、waitgroup

            給一個類似隊列似得東西初始化一個任務數量,完成一個減少一個

              var wg sync.WaitGroup
                 func main() {
                      wg.Add(1)
                      go func() {
                           fmt.Println("hello")
                           wg.Done() //完成
                      }()
                      wg.Wait()
                 }
            

            這里要特別主要一下,如果waitGroup的add數量最終無法變成0,會造成死鎖,比如上面例子我add(2)但是我自始至終只有一個Done,那剩下的任務一直存在于wg隊列中,主協程會認為還有任務沒有完成便會一直處于阻塞Wait狀態,造成死鎖。
            wg.Done方法其實在底層調用的也是wg.Add方法,只是Add的是-1

            func (wg *WaitGroup) Done() {
                    wg.Add(-1)
            }
            

            我們看sync.WaitGroup的Add方法源碼可以發現,底層的加減操作用的是我們上面提到的sync.atomic標準包來確保原子操作,所以sync.WaitGroup是并發安全的。

            作者簡介

            郭軍,奇虎360安全衛士服務端技術團隊成員,關注架構設計,GO語言等互聯網技術。

            Golang標準庫探秘(二):快速搭建HTTP服務器
            http://www.infoq.com/cn/articles/golang-standard-library-part02

            服務器闡述:

            現在市面上有很多高并發服務器,Nginx就是一個領軍人物,也是我們仰望的存在;Nginx+Lua這種組合也是高并發服務器的一個代表;PHP語言作為Nginx+FastCGI上一個優秀的解釋語言也占據大半江山。而如今的Golang也作為高并發服務器語言橫空出世,因其“語法簡單、代碼結構簡明,維護成本還低,天生高并發”等特性而被廣泛應用,尤其是各種云服務,還有Docker也是用Golang來做實現語言。

            接著我們介紹下服務器編程模型,只從線程的角度,不談并發模型。

            單線程:從線程的角度,可以分為“單線程”,“多線程”2種。

            整個進程只有一個線程,因為只有一個線程的緣故,當請求來的時候只能一個個按照順序處理,要想實現高性能只能用“non-blocking IO + IO multiplexing”組合 (非阻塞io + io復用)。        Nginx采用的就是多進程 + 單線程( 非阻塞io+io復用)模式。

            多線程:

            進程有多個線程,多個線程就不好控制,還帶來一些問題:鎖競爭,數據污染、山下文切換帶來的開銷,但是可以充分利用CPU。要實現高性能也是“non-blocking IO + IO multiplexing”組合。

            所以,其實不管單線程還是多線程都是要用“non-blocking IO + IO multiplexing”組合的。還有一種用戶級線程,整個線程的庫都是自己維護,“創建,撤銷,切換”,內核是不知道用戶級線程存在的,缺點是阻塞時會阻塞整個進程。

            其實想實現高并發服務器最好用單線程(不處理邏輯的情況下),節省很多上下文切換開銷(CPU分配時間片給任務,CPU加載上下文),但一定要采用io上“非阻塞和異步”。因為多線程很難控制,鎖,數據依賴,不同場景會讓多線程變成串行,控制起來相當繁瑣,犧牲很多并發性能(Golang采用的搶占式調度),但正常情況下多線程還是挺不錯的。下面我們說下Golang實現的高并發。

            在Golang的調度器里用的也是“csp”并發模型,有3個重要的概念P、M、G。

            P是Processor,G是Goroutine,M是Machine。

            簡述:M是執行G的機器線程,跟P綁定才可以執行,P存放G的隊列。看到這里大家會問到剛剛不是說多線程切換上下文開銷很大嗎?其實每個M都有一個g0棧內存,用來執行運行時管理命令。調度時候M的g0會從每個G棧取出棧(現場),調度之后再保存到G,這樣不同的M就可以接著調度了。所有上下文都是自己在切換,省去了內核帶來的開銷,而且Golang會觀察,長時間不調度的G會被其他G搶占(搶占調度其實就是一個標記)。

            采用異步的方式運行G,這樣就實現了并發(M可不止一個啊,感興趣看下Go并發實戰

            看到上面估計大家可能稍微了解點Golang的優勢了吧。不要擔心GC問題,選擇場景問題。

            實戰

            現在我們進入實戰部分,手把手教你實現CGI,FastCGI,HTTP服務器,主要是用Golang的HTTP包。TCP實戰就不在這次說了,TCP其實是塊難啃的骨頭,簡單的幾乎話說不清楚,如果是簡單寫一個“hello world”的例子,讓大家似懂非懂的,不如單獨開篇講解一下,從Tcp 到 Protobuf 再到RPC,然后寫一個稍微復雜點的tcp服務器,我們也可以處理下“粘包,丟包”等問題(Protobuf解決或者做一個分包算法),如果簡單的demo可能會導致你丟失興趣的。

            首先了解什么是CGI?CGI和FastCGI的區別是什么?

            CGI:全拼(Common Gateway Interface)是能讓web服務器和CGI腳本共同處理客戶的請求的協議。Web服務器把請求轉成CGI腳本,CGI腳本執行回復Web服務器,Web服務回復給客戶端。

            CGI fork一個新的進程來執行,讀取參數,處理數據,然后就結束生命期。

            FastCGI采用tcp鏈接,不用fork新的進程,因為程序啟動的時候就已經開啟了,等待數據的到來,處理數據。

            看出來差距在哪里了吧?就是CGI每次都要fork進程,這個開銷很大的。(感興趣的看下linux進程相關知識)。

            現在我們來做我們的CGI服務器

            CGI服務器

            需要用到的包:

            "net/http/cgi"
            "net/http"

            簡單的2個包就可以實現CGI服務器了。“高秀敏:準備好了嗎?希望別看到老頭子他又錯了的場景啊”。我們按照“代碼->講解”的流程,先運行在講解。

             package main;
                import (
                   "net/http/cgi"
                   "fmt"
                   "net/http"
                )
                
                funcmain() {
                    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
                        handler := new(cgi.Handler);
                        handler.Path = "/usr/local/go/bin/go";
                        script := "/Users/liujinlong/cgi-script" + r.URL.Path;
                        fmt.Println(handler.Path);
                        handler.Dir = "/Users/liujinlong/cgi-script";
                        args := []string{"run", script};
                        handler.Args = append(handler.Args, args...);
                        fmt.Println(handler.Args);
                
                        handler.ServeHTTP(w, r);
                   });
                http.ListenAndServe(":8989",nil);
                select {}//阻塞進程
                }
                test.go
                package main
                
                import(
                    "fmt"
                )
                
                funcinit() {
                    fmt.Print("Content-Type: text/plain;charset=utf-8\n\n");
                }
                
                funcmain() {
                    fmt.Println("hello!!!!")
                }

            看來我們成功了。來看下net/http/cgi的包。

            先看host.go,這里有一個重要的結構Handler。

              // Handler runs an executable in a subprocess with a CGI environment.
                type Handler struct{
                   Path string // 執行程序
                   Root string // 處理url的根,為空的時候“/”
                   Dir string         //目錄
                   Env        []string    // 環境變量
                   InheritEnv []string    //集成環境變量
                   Logger     *log.Logger// 日志
                   Args       []string    //參數
                   PathLocationHandlerhttp.Handler //http包的handler宿主
                }
            
                func(h *Handler) ServeHTTP(rwhttp.ResponseWriter, req *http.Request)

            它也實現了ServeHttp,所有請求都會調用這個,這個后面分析HTTP源碼的時候回詳細講解它是做什么的。Handler是在子程序中執行CGI腳本的。

            funcRequest() (*http.Request, error)
            funcServe(handler http.Handler)

            先是將前端CGI請求轉換成net包的HTTP請求,然后執行Handler,然后處理response。

            接下來是FastCGI服務器,

            用到的包:

            "net"
                "net/http"
                "net/http/fcgi"

            上面已經講過,它是TCP的方式實現的,需要借助其他服務器來做轉發,這里我們只提供代碼,demo的截圖講解TCP的時候在加上。

            需要使用Nginx,我電腦上沒有。各位自己測試一下

            server {
                        listen 80;
             server_name ****;
                        ...
                        location *... {
                                include         fastcgi.conf;
                fastcgi_pass    127.0.0.1:9001;
                        }
                        ...
                }//…是省略,自己去寫一個server。(具體谷歌)
                package main
                
                import (
                   "net"
                   "net/http"
                   "net/http/fcgi"
                )
                
                type FastCGIstruct{}
                
                func(s *FastCGI) ServeHTTP(resphttp.ResponseWriter, req *http.Request) {
                resp.Write([]byte("Hello, fastcgi"))
                }
                
                funcmain() {
                   listener, _ := net.Listen("tcp", "127.0.0.1:8989")
                   srv := new(FastCGI)
                   fcgi.Serve(listener, srv)
                select {
                
                   }
                }

            HTTP服務器

            接下來就是重點了,我們的HTTP服務器,這個大家都不陌生,HTTP是最常用的方式之一,通用性很強,跨團隊協作上也比較受到推薦,排查問題也相對來說簡單。

            我們接下來以3種方式來展現Golang的HTTP服務器的簡潔和強大。

            1. 寫一個簡單的HTTP服務器
            2. 寫一個稍微復雜帶路由的HTTP服務器
            3. 分析源碼,然后實現一個自定義Handler的服務器

            然后我們對照net/http包來進行源碼分析,加強對http包的理解。

            1、寫一個簡單的HTTP服務器:

            package main;
                
                import (
                    "net/http"
                )
                
                funchello(w http.ResponseWriter, req *http.Request) {
                    w.Write([]byte("Hello"))
                }
                funcsay(w http.ResponseWriter, req *http.Request) {
                    w.Write([]byte("Hello"))
                }
                funcmain() {
                    http.HandleFunc("/hello", hello);
                    http.Handle("/handle",http.HandlerFunc(say));
                    http.ListenAndServe(":8001", nil);
                    select{};//阻塞進程
                }

            是不是很簡單,我用2種方式演示了這個例子,HandleFunc和Handle方式不同,卻都能實現一個路由的監聽,其實很簡單,但是很多人看到這都會有疑惑,別著急,咱們源碼分析的時候你會看到。

            2、寫一個稍微復雜帶路由的HTTP服務器:

            對著上面的例子想一個問題,我們在開發中會遇到很多問題,比如handle/res,handle/rsa…等等路由,這兩個路由接受的參數都不一樣,我們應該怎么寫。我先來個圖展示下運行結果。

            是不是挺驚訝的,404了,路由沒有匹配到。可是我們寫handle這個路由了。

            問題:

            1. 什么原因導致的路由失效
            2. 如何解決這種問題,做一個可以用Controller來控制的路由

            問題1:

            我們在源碼閱讀分析的時候會解決。

            問題2:

            我們可以設定一個控制器Handle,它有2個action,我們的執行handle/res對應的結果是調用Handle的控制器下的res方法。這樣是不是很酷。

            來我們先上代碼:

            靜態目錄:

            1. css
            2. js
            3. image

            靜態目錄很好實現,只要一個函數http.FileServer(),這個函數從文字上看就是文件服務器,他需要傳遞一個目錄,我們常以http.Dir("Path")來傳遞。

            其他目錄大家自己實現下,我們來實現問題2,一個簡單的路由。

            我們來看下代碼

            package main;
                
                import (
                   "net/http"
                   "strings"
                   "reflect"
                   "fmt"
                )
                
                funchello(w http.ResponseWriter, req *http.Request) {
                    w.Write([]byte("Hello"));
                }
                
                
                type Handlers struct{
                
                }
                
                func(h *Handlers) ResAction(w http.ResponseWriter, req *http.Request)  {
                    fmt.Println("res");
                    w.Write([]byte("res"));
                }
                funcsay(w http.ResponseWriter, req *http.Request) {
                   pathInfo := strings.Trim(req.URL.Path, "/");
                   parts := strings.Split(pathInfo, "/");
                   varaction = "";
                   fmt.Println(strings.Join(parts,"|"));
                   if len(parts) >1 {
                      action = strings.Title(parts[1]) + "Action";
                   }
                   fmt.Println(action);
                   handle := &Handlers{};
                   controller := reflect.ValueOf(handle);
                   method := controller.MethodByName(action);
                   r := reflect.ValueOf(req);
                   wr := reflect.ValueOf(w);
                   method.Call([]reflect.Value{wr, r});
                }
                funcmain() {
                    http.HandleFunc("/hello", hello);
                    http.Handle("/handle/",http.HandlerFunc(say));
                    http.ListenAndServe(":8081", nil);
                select{};//阻塞進程
                }

            上面代碼就可以實現handle/res,handle/rsa等路由監聽,把前綴相同的路由業務實現放在一個文件里,這樣也可以解耦合,是不是清爽多了。其實我們可以在做的更加靈活些。在文章最后我們放出來一個流程圖,按照流程圖做你們就能寫出一個簡單的mvc路由框架。接下來看運行之后的結果。

            如下圖: 

            (點擊放大圖像)

            3、分析源碼,然后實現一個自定義Handler的服務器

            現在我們利用這個例子來分析下http包的源碼(只是服務器相關的,Request我們此期不講,簡單看看就行。)

            其實使用Golang做web服務器的方式有很多,TCP也是一種,net包就可以實現,不過此期我們不講,因為HTTP服務器如果不懂,TCP會讓你更加不明白。

            我們從入口開始,首先看main方法里的http.HandleFunc和http.Handle這個綁定路由的方法,上面一直沒解釋有啥區別。現在我們來看一下。

            // HandleFunc registers the handler function for the given pattern
                // in the DefaultServeMux.
                // The documentation for ServeMux explains how patterns are matched.
                funcHandleFunc(pattern string, handler func(ResponseWriter, *Request)) 
                funcHandle(pattern string, handler Handler)

            Handle 和HandleFunc都是注冊路由,從上面也能看出來這兩個函數都是綁定注冊路由函數的。如何綁定的呢?我們來看下。

            上面2個函數通過DefaultServeMux.handle,DefaultServeMux.handleFunc把pattern和HandleFunc綁定到ServeMux的Handle上。

            為什么DefaultServeMux會把路由綁定到ServeMux上呢?

            // DefaultServeMux is the default ServeMux used by Serve.
                varDefaultServeMux = NewServeMux()

            因為DefaultServeMux就是ServeMux的實例對象。導致我們就把路由和執行方法綁注冊好了。不過大家請想下handle/res的問題?

            從上面的分析我們要知道幾個重要的概念。

            HandlerFunc
            
                // The HandlerFunc type is an adapter to allow the use of
                // ordinary functions as HTTP handlers.  If f is a function
                // with the appropriate signature, HandlerFunc(f) is a
                // Handler object that calls f.
                type HandlerFuncfunc(ResponseWriter, *Request)
                
                // ServeHTTP calls f(w, r).
                func(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
                   f(w, r)
                }

            上面的大概意思是,定義了一個函數適配器(可以理解成函數指針)HandleFunc,通過HandlerFunc(f)來進行適配。其實調用的實體是f本身。

            package main
                import "fmt"
                type A func(int, int)
                func(f A)Serve() {
                    fmt.Println("serve2")
                }
                funcserve(int,int) {
                    fmt.Println("serve1")
                }
                funcmain() {
                   a := A(serve)
                   a(1,2)//這行輸出的結果是serve1
                   a.Serve()//這行輸出的結果是serve2
                }

            上面結果是serve1,serve2

            Golang的源碼里用了很多HandleFunc這個適配器。

            接下來我們看第二個,ServeMux結構,最終我們是綁定它,也是通過它來解析。

            type ServeMuxstruct{
                   mu    sync.RWMutex//讀寫鎖
                   m     map[string]muxEntry//路由map,pattern->HandleFunc
                   hosts bool//是否包含hosts        
                }
                
                type muxEntrystruct{
                   explicit bool//是否精確匹配,這個在Golang實現里是ture
                   h        Handler //這個路由表達式對應哪個handler
                   pattern  string//路由
                }

            看到explicit的時候是不是就明白為啥handle/res不能用handle來監聽了?原來如此。大致綁定流程大家看明白了嗎?如果不理解可以回去再看一遍。

            接下來我們來看實現“啟動/監聽/觸發”服務器的代碼。

            http.ListenAndServe(":8081", nil);

            上面這句就是,”:8081”是監聽的端口,也是socket監聽的端口,第二個參數就是我們的Handler,這里我們寫nil。

             funcListenAndServe(addr string, handler Handler) error {
                   server := &Server{Addr: addr, Handler: handler}
                   return server.ListenAndServe()
                }

            從這個代碼看出來,Server這個結構很重要。我們來看看他是什么。

            type Server struct {
                Addr           string        // 監聽的地址和端口
                Handler        Handler       // 所有請求需要調用的Handler(
                ReadTimeouttime.Duration // 讀的最大Timeout時間
                WriteTimeouttime.Duration // 寫的最大Timeout時間
                MaxHeaderBytesint           // 請求頭的最大長度
                TLSConfig      *tls.Config   // 配置TLS
                    ...   //結構太長我省略些,感興趣大家自己看下
                }

            Server提供的方法有:

             func(srv *Server) Serve(l net.Listener) error   //對某個端口進行監聽,里面就是調用for進行accept的處理了
             func(srv *Server) ListenAndServe() error  //開啟http server服務
             func(srv *Server) ListenAndServeTLS(certFile, keyFile string) error //開啟https server服務

            Server的ListenAndServe方法通過TCP的方式監聽端口,然后調用Serve里的實現等待client來accept,然后開啟一個協程來處理邏輯(go c.serve)。

            它的格式

            func(srv *Server) ListenAndServe() error 

            看到這里我們要了解幾個重要的概念。

            ResponseWriter:生成Response的接口

            Handler:處理請求和生成返回的接口

            ServeMux:路由,后面會說到ServeMux也是一種Handler

            Conn : 網絡連接

            這幾個概念看完之后我們下面要用。

            type conn struct

            這個結構是一個網絡間接。我們暫時忽略。

            這個c.serve里稍微有點復雜,它有關閉這次請求,讀取數據的,刷新緩沖區的等實現。這里我們主要關注一個c.readRequest(),通過redRequest可以得到Response,就是輸出給客戶端數據的一個回復者。

            它里面包含request。如果要看懂這里的實現就要搞懂三個接口。

            ResponseWriter, Flusher, Hijacker
            
                // ResponseWriter的作用是被Handler調用來組裝返回的Response的
                type ResponseWriter interface {
                    // 這個方法返回Response返回的Header供讀寫
                    Header() Header
                
                    // 這個方法寫Response的Body
                    Write([]byte) (int, error)
                
                    // 這個方法根據HTTP State Code來寫Response的Header
                
                    WriteHeader(int)
                }
                
                // Flusher的作用是被Handler調用來將寫緩存中的數據推給客戶端
                type Flusher interface {
                    // 刷新緩沖區
                    Flush()
                }
                
                // Hijacker的作用是被Handler調用來關閉連接的
                type Hijacker interface {
                    Hijack() (net.Conn, *bufio.ReadWriter, error)
                
                }

            而我們這里的w也就是ResponseWriter了。而調用了下面這句方法,就可以利用它的Write方法輸出內容給客戶端了。

            serverHandler{c.server}.ServeHTTP(w, w.req)

            這句就是觸發路由綁定的方法了。要看這個觸發器我們還要知道幾個接口。

            具體我們先看下如何實現這三個接口的,因為后面我們要看觸發路由執行邏輯片段。實現這三個接口的結構是response

            response
                // response包含了所有server端的HTTP返回信息
                type response struct {
                    conn          *conn         // 保存此次HTTP連接的信息
                    req           *Request // 對應請求信息
                    chunking      bool     // 是否使用chunk
                    wroteHeaderbool     // header是否已經執行過寫操作
                    wroteContinuebool     // 100 Continue response was written
                    header        Header   // 返回的HTTP的Header
                    written       int64    // Body的字節數
                    contentLength int64    // Content長度
                    status        int      // HTTP狀態
                    needSniffbool  
            //是否需要使用sniff。(當沒有設置Content-Type的時候,開啟sniff能根據HTTP body來確定Content-Type)
                    closeAfterReplybool    
            //是否保持長鏈接。如果客戶端發送的請求中connection有keep-alive,這個字段就設置為false。
                    requestBodyLimitHitbool 
            //是否requestBody太大了(當requestBody太大的時候,response是會返回411狀態的,并把連接關閉)
                }

            在response中是可以看到

            func(w *response) Header() Header 
            func(w *response) WriteHeader(code int) 
            func(w *response) Write(data []byte) (n int, err error) 
            func(w *response) WriteString(data string) (n int, err error) 
            // either dataB or dataS is non-zero.
            func(w *response) write(lenDataint, dataB []byte, dataS string) (n int, err error) 
            func(w *response) finishRequest()
            func(w *response) Flush() 
            func(w *response) Hijack() (rwcnet.Conn, buf *bufio.ReadWriter, err error)

            我簡單羅列一些,從上面可以看出,response實現了這3個接口。

            接下來我們請求真正的觸發者也就是serverHandle要觸發路由(hijacked finishRequest暫且不提)。先看一個接口。

            Handler
                
                type Handler interface {
                ServeHTTP(ResponseWriter, *Request)  // 具體的邏輯函數
                }

            實現了handler接口,就意味著往server端添加了處理請求的邏輯函數。

            serverHandle調用ServeHttp來選擇觸發的HandleFunc。這里面會做一個判斷,如果你傳遞了Handler,就調用你自己的,如果沒傳遞就用DefaultServeMux默認的。到這整體流程就結束了。

            過程是:

            DefaultServeMux.ServeHttp執行的簡單流程.

            1. h, _ := mux.Handler(r)
            2. h.ServeHTTP(w, r)   //執行ServeHttp函數

            查找路由,mux.handler函數里又調用了另外一個函數mux.handler(r.Host, r.URL.Path)。

            還記得我們的ServeMux里的hosts標記嗎?這個函數里會進行判斷。

            // Host-specific pattern takes precedence over generic ones
                   if mux.hosts {
                      h, pattern = mux.match(host + path)
                   }
                   if h == nil {
                     h, pattern = mux.match(path)
                   }
                   if h == nil {
                     h, pattern = NotFoundHandler(), ""
                   }

            上面就是匹配查找pattern和handler的流程了

            我們來總結一下。

            首先調用Http.HandleFunc

            按順序做了幾件事:

            1. 調用了DefaultServerMux的HandleFunc
            2. 調用了DefaultServerMux的Handle
            3. 往DefaultServeMux的map[string]muxEntry中增加對應的handler和路由規則

            別忘記DefaultServerMux是ServeMux的實例。其實都是圍繞ServeMux,muxEntry2個結構進行操作綁定。

            其次調用http.ListenAndServe(":12345", nil)

            按順序做了幾件事情:

            1. 實例化Server
            2. 調用Server的ListenAndServe()
            3. 調用net.Listen("tcp", addr)監聽端口,啟動for循環,等待accept請求
            4. 對每個請求實例化一個Conn,并且開啟一個goroutine處理請求。
            5. 如:go c.serve()
            6. 讀取請求的內容w, err := c.readRequest(),也就是response的取值過程。
            7. 調用serverHandler的ServeHTTP,ServeHTTP里會判斷Server的屬性里的header是否為空,如果沒有設置handler,handler就設置為DefaultServeMux,反之用自己的(我們后面會做一個利用自己的Handler寫服務器)
            8. 調用DefaultServeMux的ServeHttp( 因為我們沒有自己的Handler,所以走默認的)
            9. 通過request選擇匹配的handler:

              A request匹配handler的方式。Hosts+pattern或pattern或notFound

              B 如果有路由滿足,返回這個handler

              C 如果沒有路由滿足,返回NotFoundHandler

            10. 根據返回的handler進入到這個handler的ServeHTTP

            大概流程就是這個樣子,其實在net.Listen("tcp", addr)里也做了很多事,我們下期說道TCP服務器的時候回顧一下他做了哪些。

            通過上面的解釋大致明白了我們綁定觸發的都是DefaultServeMux的Handler。現在我們來實現一個自己的Handler,這也是做框架的第一步。我們先來敲代碼。

            package main;
                
                import (
                   "fmt"
                   "net/http"
                   "time"
                )
                
                type customHandlerstruct{
                
                }
                
                func(cb *customHandler) ServeHTTP( w http.ResponseWriter, r *http.Request ) {
                    fmt.Println("customHandler!!");
                    w.Write([]byte("customHandler!!"));
                }
                
                funcmain() {
                    varserver *http.Server = &http.Server{
                      Addr:           ":8080",
                      Handler:        &customHandler{},
                      ReadTimeout:    10 * time.Second,
                      WriteTimeout:   10 * time.Second,
                      MaxHeaderBytes: 1 <<20,
                   }
                server.ListenAndServe();
                select {
                   }
                }

            是不是很酷,我們可以利用自己的handler做一個智能的路由出來。

            不過還是建議使用國內Golang語言框架beego,已開源。一款非常不錯的框架,謝大維護的很用心,絕對良心框架,而且文檔支持,社區也很不錯。

            最后附上一張最早設計框架時候的一個流程圖(3年前)。大家可以簡單看看,當然也可以嘗試的動動手。起碼收獲很多。

            (點擊放大圖像)

              [1]: http://item.jd.com/11573034.html

              [2]: https://github.com/astaxie/beego

            作者簡介

            劉金龍,藝名:金灶沐 ,go語言愛好者,2015年8月加入創業團隊,負責各種“打雜”工作,之前在360電商購物小蜜java組擔任java高級工程師職位,負責購物小蜜服務開發。14年開始用go語言做高并發服務并且嘗試閱讀go語言的源碼來學習go語言的特性。

            posted on 2017-08-01 17:19 思月行云 閱讀(522) 評論(0)  編輯 收藏 引用 所屬分類: Golang
            99久久综合国产精品免费| 亚洲国产精品18久久久久久| 97久久国产亚洲精品超碰热| 久久Av无码精品人妻系列| 国产麻豆精品久久一二三| 国产Av激情久久无码天堂| 久久精品九九亚洲精品天堂| 国产香蕉97碰碰久久人人| 久久高清一级毛片| 久久久久久久精品妇女99| 久久亚洲春色中文字幕久久久| 久久综合丝袜日本网| 久久久久国产精品嫩草影院| 国产精品中文久久久久久久| 久久超乳爆乳中文字幕| 国产精品一区二区久久精品无码 | 久久香蕉超碰97国产精品| 伊人久久大香线蕉综合5g| 国产精品va久久久久久久| 四虎国产精品免费久久| 97视频久久久| 久久一本综合| 国产成人精品久久综合| 国产V亚洲V天堂无码久久久| 久久人人爽人人爽人人av东京热| 精品无码久久久久久国产| 久久福利青草精品资源站| 色偷偷久久一区二区三区| 思思久久99热只有频精品66| 久久久久亚洲AV无码去区首| 久久免费视频观看| 久久精品一区二区| 久久se精品一区精品二区| 精品久久久久久无码专区| 99久久99久久精品国产片果冻| 久久久久久久久66精品片| 亚洲а∨天堂久久精品| 青青草国产97免久久费观看| 久久一区二区免费播放| 欧美色综合久久久久久| 日韩电影久久久被窝网|