• <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
            <2018年10月>
            30123456
            78910111213
            14151617181920
            21222324252627
            28293031123
            45678910


            專注即時(shí)通訊及網(wǎng)游服務(wù)端編程
            ------------------------------------
            Openresty 官方模塊
            Openresty 標(biāo)準(zhǔn)模塊(Opm)
            Openresty 三方模塊
            ------------------------------------
            本博收藏大部分文章為轉(zhuǎn)載,并在文章開頭給出了原文出處,如有再轉(zhuǎn),敬請(qǐng)保留相關(guān)信息,這是大家對(duì)原創(chuàng)作者勞動(dòng)成果的自覺(jué)尊重!!如為您帶來(lái)不便,請(qǐng)于本博下留言,謝謝配合。

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            相冊(cè)

            Awesome

            Blog

            Book

            GitHub

            Link

            搜索

            •  

            積分與排名

            • 積分 - 216756
            • 排名 - 118

            最新評(píng)論

            閱讀排行榜

            Golang標(biāo)準(zhǔn)庫(kù)探秘(一):sync 標(biāo)準(zhǔn)庫(kù)
            http://www.infoq.com/cn/articles/golang-standard-library-part01

            在高并發(fā)或者海量數(shù)據(jù)的生產(chǎn)環(huán)境中,我們會(huì)遇到很多問(wèn)題,GC(garbage collection,中文譯成垃圾回收)就是其中之一。說(shuō)起優(yōu)化GC我們首先想到的肯定是讓對(duì)象可重用,這就需要一個(gè)對(duì)象池來(lái)存儲(chǔ)待回收對(duì)象,等待下次重用,從而減少對(duì)象產(chǎn)生數(shù)量。

            標(biāo)準(zhǔn)庫(kù)原生的對(duì)象池

            在Golang1.3版本便已新增了sync.Pool功能,它就是用來(lái)保存和復(fù)用臨時(shí)對(duì)象,以減少內(nèi)存分配,降低CG壓力,下面就來(lái)講講sync.Pool的基本用法。

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

            很簡(jiǎn)潔,最常用的兩個(gè)函數(shù)Get/Put

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

            對(duì)象池底層數(shù)據(jù)結(jié)構(gòu)

            我們選擇用Golang的container標(biāo)準(zhǔn)包中的鏈表來(lái)做對(duì)象池的底層數(shù)據(jù)結(jié)構(gòu),它被封裝在container/list標(biāo)準(zhǔn)包里:

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

            這里是定義了鏈表中的元素,這個(gè)標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)的是一個(gè)雙向鏈表,并且已經(jīng)為我們封裝好了各種Front/Back方法。不過(guò)Front方法的實(shí)現(xiàn)和我們需要的還是有點(diǎn)差異,它只是返回鏈表中的第一個(gè)元素,但這個(gè)元素依然會(huì)鏈接在鏈表里,所以我們需要自行將它從鏈表中刪除,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
             }
            

            這樣對(duì)象池的核心部分就完成了,但注意一下,從remove函數(shù)可以看出,container/list并不是線程安全的,所以在對(duì)象池的對(duì)象個(gè)數(shù)統(tǒng)計(jì)等一些功能會(huì)有問(wèn)題。

            原子操作并發(fā)安全

            下面我們來(lái)自行解決并發(fā)安全的問(wèn)題。Golang的sync標(biāo)準(zhǔn)包封裝了常用的原子操作和鎖操作。
            sync/atomic封裝了常用的原子操作。所謂原子操作就是在針對(duì)某個(gè)值進(jìn)行操作的整個(gè)過(guò)程中,為了實(shí)現(xiàn)嚴(yán)謹(jǐn)性必須由一個(gè)獨(dú)立的CPU指令完成,該過(guò)程不能被其他操作中斷,以保證該操作的并發(fā)安全性。

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

            在Golang中,我們常用的數(shù)據(jù)類型除了channel之外都不是線程安全的,所以在這里我們需要對(duì)數(shù)量(len)和切片(conns []*conn)做并發(fā)保護(hù)。至于需要幾把鎖做保護(hù),取決于實(shí)際場(chǎng)景,合理控制鎖的粒度。
            接著介紹一下鎖操作,我們?cè)贕olang中常用的鎖——互斥鎖(Lock)和讀寫鎖(RWLock),互斥鎖和讀寫鎖的區(qū)別是:互斥鎖無(wú)論是讀操作還是寫操作都會(huì)對(duì)目標(biāo)加鎖也就是說(shuō)所有的操作都需要排隊(duì)進(jìn)行,讀寫鎖是加鎖后寫操作只能排隊(duì)進(jìn)行但是可以并發(fā)進(jìn)行讀操作,要注意一點(diǎn)就是讀的時(shí)候?qū)懖僮魇亲枞模瑢懖僮鬟M(jìn)行的時(shí)候讀操作是阻塞的。類型sync.Mutex/sync.RWMutex的零值表示了未被鎖定的互斥量。也就是說(shuō),它是一個(gè)開箱即用的工具。只需對(duì)它進(jìn)行簡(jiǎn)單聲明就可以正常使用了,例如(在這里以Mutex為例,相對(duì)于RWMutex也是同理):

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

            鎖操作一定要成對(duì)出現(xiàn),也就是說(shuō)在加鎖之后操作的某一個(gè)地方一定要記得釋放鎖,否則再次加鎖會(huì)造成死鎖問(wèn)題

            fatal error: all goroutines are asleep - deadlock
            

            不過(guò)在Golang里這種錯(cuò)誤發(fā)生的幾率會(huì)很少,因?yàn)橛衐efer延時(shí)函數(shù)的存在
            上面的代碼可以改寫為

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

            在加鎖之后馬上用defer函數(shù)進(jìn)行解鎖操作,這樣即使下面我們只關(guān)心函數(shù)邏輯而在函數(shù)退出的時(shí)候忘記Unlock操作也不會(huì)造成死鎖,因?yàn)樵诤瘮?shù)退出的時(shí)候會(huì)自動(dòng)執(zhí)行defer延時(shí)函數(shù)釋放鎖。

            標(biāo)準(zhǔn)庫(kù)中的并發(fā)控制-WaitGroup

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

            1、time.Sleep()

            讓主協(xié)程睡一會(huì),好方法,但是睡多久呢?不確定(最簡(jiǎn)單暴力)

            2、channel

            在主協(xié)程一直阻塞等待一個(gè)退出信號(hào),在其他協(xié)程完成任務(wù)后給主協(xié)程發(fā)送一個(gè)信號(hào),主協(xié)程收到這個(gè)信號(hào)后退出

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

            3、waitgroup

            給一個(gè)類似隊(duì)列似得東西初始化一個(gè)任務(wù)數(shù)量,完成一個(gè)減少一個(gè)

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

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

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

            我們看sync.WaitGroup的Add方法源碼可以發(fā)現(xiàn),底層的加減操作用的是我們上面提到的sync.atomic標(biāo)準(zhǔn)包來(lái)確保原子操作,所以sync.WaitGroup是并發(fā)安全的。

            作者簡(jiǎn)介

            郭軍,奇虎360安全衛(wèi)士服務(wù)端技術(shù)團(tuán)隊(duì)成員,關(guān)注架構(gòu)設(shè)計(jì),GO語(yǔ)言等互聯(lián)網(wǎng)技術(shù)。

            Golang標(biāo)準(zhǔn)庫(kù)探秘(二):快速搭建HTTP服務(wù)器
            http://www.infoq.com/cn/articles/golang-standard-library-part02

            服務(wù)器闡述:

            現(xiàn)在市面上有很多高并發(fā)服務(wù)器,Nginx就是一個(gè)領(lǐng)軍人物,也是我們仰望的存在;Nginx+Lua這種組合也是高并發(fā)服務(wù)器的一個(gè)代表;PHP語(yǔ)言作為Nginx+FastCGI上一個(gè)優(yōu)秀的解釋語(yǔ)言也占據(jù)大半江山。而如今的Golang也作為高并發(fā)服務(wù)器語(yǔ)言橫空出世,因其“語(yǔ)法簡(jiǎn)單、代碼結(jié)構(gòu)簡(jiǎn)明,維護(hù)成本還低,天生高并發(fā)”等特性而被廣泛應(yīng)用,尤其是各種云服務(wù),還有Docker也是用Golang來(lái)做實(shí)現(xiàn)語(yǔ)言。

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

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

            整個(gè)進(jìn)程只有一個(gè)線程,因?yàn)橹挥幸粋€(gè)線程的緣故,當(dāng)請(qǐng)求來(lái)的時(shí)候只能一個(gè)個(gè)按照順序處理,要想實(shí)現(xiàn)高性能只能用“non-blocking IO + IO multiplexing”組合 (非阻塞io + io復(fù)用)。        Nginx采用的就是多進(jìn)程 + 單線程( 非阻塞io+io復(fù)用)模式。

            多線程:

            進(jìn)程有多個(gè)線程,多個(gè)線程就不好控制,還帶來(lái)一些問(wèn)題:鎖競(jìng)爭(zhēng),數(shù)據(jù)污染、山下文切換帶來(lái)的開銷,但是可以充分利用CPU。要實(shí)現(xiàn)高性能也是“non-blocking IO + IO multiplexing”組合。

            所以,其實(shí)不管單線程還是多線程都是要用“non-blocking IO + IO multiplexing”組合的。還有一種用戶級(jí)線程,整個(gè)線程的庫(kù)都是自己維護(hù),“創(chuàng)建,撤銷,切換”,內(nèi)核是不知道用戶級(jí)線程存在的,缺點(diǎn)是阻塞時(shí)會(huì)阻塞整個(gè)進(jìn)程。

            其實(shí)想實(shí)現(xiàn)高并發(fā)服務(wù)器最好用單線程(不處理邏輯的情況下),節(jié)省很多上下文切換開銷(CPU分配時(shí)間片給任務(wù),CPU加載上下文),但一定要采用io上“非阻塞和異步”。因?yàn)槎嗑€程很難控制,鎖,數(shù)據(jù)依賴,不同場(chǎng)景會(huì)讓多線程變成串行,控制起來(lái)相當(dāng)繁瑣,犧牲很多并發(fā)性能(Golang采用的搶占式調(diào)度),但正常情況下多線程還是挺不錯(cuò)的。下面我們說(shuō)下Golang實(shí)現(xiàn)的高并發(fā)。

            在Golang的調(diào)度器里用的也是“csp”并發(fā)模型,有3個(gè)重要的概念P、M、G。

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

            簡(jiǎn)述:M是執(zhí)行G的機(jī)器線程,跟P綁定才可以執(zhí)行,P存放G的隊(duì)列。看到這里大家會(huì)問(wèn)到剛剛不是說(shuō)多線程切換上下文開銷很大嗎?其實(shí)每個(gè)M都有一個(gè)g0棧內(nèi)存,用來(lái)執(zhí)行運(yùn)行時(shí)管理命令。調(diào)度時(shí)候M的g0會(huì)從每個(gè)G棧取出棧(現(xiàn)場(chǎng)),調(diào)度之后再保存到G,這樣不同的M就可以接著調(diào)度了。所有上下文都是自己在切換,省去了內(nèi)核帶來(lái)的開銷,而且Golang會(huì)觀察,長(zhǎng)時(shí)間不調(diào)度的G會(huì)被其他G搶占(搶占調(diào)度其實(shí)就是一個(gè)標(biāo)記)。

            采用異步的方式運(yùn)行G,這樣就實(shí)現(xiàn)了并發(fā)(M可不止一個(gè)啊,感興趣看下Go并發(fā)實(shí)戰(zhàn)

            看到上面估計(jì)大家可能稍微了解點(diǎn)Golang的優(yōu)勢(shì)了吧。不要擔(dān)心GC問(wèn)題,選擇場(chǎng)景問(wèn)題。

            實(shí)戰(zhàn)

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

            首先了解什么是CGI?CGI和FastCGI的區(qū)別是什么?

            CGI:全拼(Common Gateway Interface)是能讓web服務(wù)器和CGI腳本共同處理客戶的請(qǐng)求的協(xié)議。Web服務(wù)器把請(qǐng)求轉(zhuǎn)成CGI腳本,CGI腳本執(zhí)行回復(fù)Web服務(wù)器,Web服務(wù)回復(fù)給客戶端。

            CGI fork一個(gè)新的進(jìn)程來(lái)執(zhí)行,讀取參數(shù),處理數(shù)據(jù),然后就結(jié)束生命期。

            FastCGI采用tcp鏈接,不用fork新的進(jìn)程,因?yàn)槌绦騿?dòng)的時(shí)候就已經(jīng)開啟了,等待數(shù)據(jù)的到來(lái),處理數(shù)據(jù)。

            看出來(lái)差距在哪里了吧?就是CGI每次都要fork進(jìn)程,這個(gè)開銷很大的。(感興趣的看下linux進(jìn)程相關(guān)知識(shí))。

            現(xiàn)在我們來(lái)做我們的CGI服務(wù)器

            CGI服務(wù)器

            需要用到的包:

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

            簡(jiǎn)單的2個(gè)包就可以實(shí)現(xiàn)CGI服務(wù)器了。“高秀敏:準(zhǔn)備好了嗎?希望別看到老頭子他又錯(cuò)了的場(chǎng)景啊”。我們按照“代碼->講解”的流程,先運(yùn)行在講解。

             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 {}//阻塞進(jìn)程
                }
                test.go
                package main
                
                import(
                    "fmt"
                )
                
                funcinit() {
                    fmt.Print("Content-Type: text/plain;charset=utf-8\n\n");
                }
                
                funcmain() {
                    fmt.Println("hello!!!!")
                }

            看來(lái)我們成功了。來(lái)看下net/http/cgi的包。

            先看host.go,這里有一個(gè)重要的結(jié)構(gòu)Handler。

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

            它也實(shí)現(xiàn)了ServeHttp,所有請(qǐng)求都會(huì)調(diào)用這個(gè),這個(gè)后面分析HTTP源碼的時(shí)候回詳細(xì)講解它是做什么的。Handler是在子程序中執(zhí)行CGI腳本的。

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

            先是將前端CGI請(qǐng)求轉(zhuǎn)換成net包的HTTP請(qǐng)求,然后執(zhí)行Handler,然后處理response。

            接下來(lái)是FastCGI服務(wù)器,

            用到的包:

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

            上面已經(jīng)講過(guò),它是TCP的方式實(shí)現(xiàn)的,需要借助其他服務(wù)器來(lái)做轉(zhuǎn)發(fā),這里我們只提供代碼,demo的截圖講解TCP的時(shí)候在加上。

            需要使用Nginx,我電腦上沒(méi)有。各位自己測(cè)試一下

            server {
                        listen 80;
             server_name ****;
                        ...
                        location *... {
                                include         fastcgi.conf;
                fastcgi_pass    127.0.0.1:9001;
                        }
                        ...
                }//…是省略,自己去寫一個(gè)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服務(wù)器

            接下來(lái)就是重點(diǎn)了,我們的HTTP服務(wù)器,這個(gè)大家都不陌生,HTTP是最常用的方式之一,通用性很強(qiáng),跨團(tuán)隊(duì)協(xié)作上也比較受到推薦,排查問(wèn)題也相對(duì)來(lái)說(shuō)簡(jiǎn)單。

            我們接下來(lái)以3種方式來(lái)展現(xiàn)Golang的HTTP服務(wù)器的簡(jiǎn)潔和強(qiáng)大。

            1. 寫一個(gè)簡(jiǎn)單的HTTP服務(wù)器
            2. 寫一個(gè)稍微復(fù)雜帶路由的HTTP服務(wù)器
            3. 分析源碼,然后實(shí)現(xiàn)一個(gè)自定義Handler的服務(wù)器

            然后我們對(duì)照net/http包來(lái)進(jìn)行源碼分析,加強(qiáng)對(duì)http包的理解。

            1、寫一個(gè)簡(jiǎn)單的HTTP服務(wù)器:

            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{};//阻塞進(jìn)程
                }

            是不是很簡(jiǎn)單,我用2種方式演示了這個(gè)例子,HandleFunc和Handle方式不同,卻都能實(shí)現(xiàn)一個(gè)路由的監(jiān)聽,其實(shí)很簡(jiǎn)單,但是很多人看到這都會(huì)有疑惑,別著急,咱們?cè)创a分析的時(shí)候你會(huì)看到。

            2、寫一個(gè)稍微復(fù)雜帶路由的HTTP服務(wù)器:

            對(duì)著上面的例子想一個(gè)問(wèn)題,我們?cè)陂_發(fā)中會(huì)遇到很多問(wèn)題,比如handle/res,handle/rsa…等等路由,這兩個(gè)路由接受的參數(shù)都不一樣,我們應(yīng)該怎么寫。我先來(lái)個(gè)圖展示下運(yùn)行結(jié)果。

            是不是挺驚訝的,404了,路由沒(méi)有匹配到。可是我們寫handle這個(gè)路由了。

            問(wèn)題:

            1. 什么原因?qū)е碌穆酚墒?/span>
            2. 如何解決這種問(wèn)題,做一個(gè)可以用Controller來(lái)控制的路由

            問(wèn)題1:

            我們?cè)谠创a閱讀分析的時(shí)候會(huì)解決。

            問(wèn)題2:

            我們可以設(shè)定一個(gè)控制器Handle,它有2個(gè)action,我們的執(zhí)行handle/res對(duì)應(yīng)的結(jié)果是調(diào)用Handle的控制器下的res方法。這樣是不是很酷。

            來(lái)我們先上代碼:

            靜態(tài)目錄:

            1. css
            2. js
            3. image

            靜態(tài)目錄很好實(shí)現(xiàn),只要一個(gè)函數(shù)http.FileServer(),這個(gè)函數(shù)從文字上看就是文件服務(wù)器,他需要傳遞一個(gè)目錄,我們常以http.Dir("Path")來(lái)傳遞。

            其他目錄大家自己實(shí)現(xiàn)下,我們來(lái)實(shí)現(xiàn)問(wèn)題2,一個(gè)簡(jiǎn)單的路由。

            我們來(lái)看下代碼

            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{};//阻塞進(jìn)程
                }

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

            如下圖: 

            (點(diǎn)擊放大圖像)

            3、分析源碼,然后實(shí)現(xiàn)一個(gè)自定義Handler的服務(wù)器

            現(xiàn)在我們利用這個(gè)例子來(lái)分析下http包的源碼(只是服務(wù)器相關(guān)的,Request我們此期不講,簡(jiǎn)單看看就行。)

            其實(shí)使用Golang做web服務(wù)器的方式有很多,TCP也是一種,net包就可以實(shí)現(xiàn),不過(guò)此期我們不講,因?yàn)镠TTP服務(wù)器如果不懂,TCP會(huì)讓你更加不明白。

            我們從入口開始,首先看main方法里的http.HandleFunc和http.Handle這個(gè)綁定路由的方法,上面一直沒(méi)解釋有啥區(qū)別。現(xiàn)在我們來(lái)看一下。

            // 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都是注冊(cè)路由,從上面也能看出來(lái)這兩個(gè)函數(shù)都是綁定注冊(cè)路由函數(shù)的。如何綁定的呢?我們來(lái)看下。

            上面2個(gè)函數(shù)通過(guò)DefaultServeMux.handle,DefaultServeMux.handleFunc把pattern和HandleFunc綁定到ServeMux的Handle上。

            為什么DefaultServeMux會(huì)把路由綁定到ServeMux上呢?

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

            因?yàn)镈efaultServeMux就是ServeMux的實(shí)例對(duì)象。導(dǎo)致我們就把路由和執(zhí)行方法綁注冊(cè)好了。不過(guò)大家請(qǐng)想下handle/res的問(wèn)題?

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

            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)
                }

            上面的大概意思是,定義了一個(gè)函數(shù)適配器(可以理解成函數(shù)指針)HandleFunc,通過(guò)HandlerFunc(f)來(lái)進(jìn)行適配。其實(shí)調(diào)用的實(shí)體是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)//這行輸出的結(jié)果是serve1
                   a.Serve()//這行輸出的結(jié)果是serve2
                }

            上面結(jié)果是serve1,serve2

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

            接下來(lái)我們看第二個(gè),ServeMux結(jié)構(gòu),最終我們是綁定它,也是通過(guò)它來(lái)解析。

            type ServeMuxstruct{
                   mu    sync.RWMutex//讀寫鎖
                   m     map[string]muxEntry//路由map,pattern->HandleFunc
                   hosts bool//是否包含hosts        
                }
                
                type muxEntrystruct{
                   explicit bool//是否精確匹配,這個(gè)在Golang實(shí)現(xiàn)里是ture
                   h        Handler //這個(gè)路由表達(dá)式對(duì)應(yīng)哪個(gè)handler
                   pattern  string//路由
                }

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

            接下來(lái)我們來(lái)看實(shí)現(xiàn)“啟動(dòng)/監(jiān)聽/觸發(fā)”服務(wù)器的代碼。

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

            上面這句就是,”:8081”是監(jiān)聽的端口,也是socket監(jiān)聽的端口,第二個(gè)參數(shù)就是我們的Handler,這里我們寫nil。

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

            從這個(gè)代碼看出來(lái),Server這個(gè)結(jié)構(gòu)很重要。我們來(lái)看看他是什么。

            type Server struct {
                Addr           string        // 監(jiān)聽的地址和端口
                Handler        Handler       // 所有請(qǐng)求需要調(diào)用的Handler(
                ReadTimeouttime.Duration // 讀的最大Timeout時(shí)間
                WriteTimeouttime.Duration // 寫的最大Timeout時(shí)間
                MaxHeaderBytesint           // 請(qǐng)求頭的最大長(zhǎng)度
                TLSConfig      *tls.Config   // 配置TLS
                    ...   //結(jié)構(gòu)太長(zhǎng)我省略些,感興趣大家自己看下
                }

            Server提供的方法有:

             func(srv *Server) Serve(l net.Listener) error   //對(duì)某個(gè)端口進(jìn)行監(jiān)聽,里面就是調(diào)用for進(jìn)行accept的處理了
             func(srv *Server) ListenAndServe() error  //開啟http server服務(wù)
             func(srv *Server) ListenAndServeTLS(certFile, keyFile string) error //開啟https server服務(wù)

            Server的ListenAndServe方法通過(guò)TCP的方式監(jiān)聽端口,然后調(diào)用Serve里的實(shí)現(xiàn)等待client來(lái)accept,然后開啟一個(gè)協(xié)程來(lái)處理邏輯(go c.serve)。

            它的格式

            func(srv *Server) ListenAndServe() error 

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

            ResponseWriter:生成Response的接口

            Handler:處理請(qǐng)求和生成返回的接口

            ServeMux:路由,后面會(huì)說(shuō)到ServeMux也是一種Handler

            Conn : 網(wǎng)絡(luò)連接

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

            type conn struct

            這個(gè)結(jié)構(gòu)是一個(gè)網(wǎng)絡(luò)間接。我們暫時(shí)忽略。

            這個(gè)c.serve里稍微有點(diǎn)復(fù)雜,它有關(guān)閉這次請(qǐng)求,讀取數(shù)據(jù)的,刷新緩沖區(qū)的等實(shí)現(xiàn)。這里我們主要關(guān)注一個(gè)c.readRequest(),通過(guò)redRequest可以得到Response,就是輸出給客戶端數(shù)據(jù)的一個(gè)回復(fù)者。

            它里面包含request。如果要看懂這里的實(shí)現(xiàn)就要搞懂三個(gè)接口。

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

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

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

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

            具體我們先看下如何實(shí)現(xiàn)這三個(gè)接口的,因?yàn)楹竺嫖覀円从|發(fā)路由執(zhí)行邏輯片段。實(shí)現(xiàn)這三個(gè)接口的結(jié)構(gòu)是response

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

            在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)

            我簡(jiǎn)單羅列一些,從上面可以看出,response實(shí)現(xiàn)了這3個(gè)接口。

            接下來(lái)我們請(qǐng)求真正的觸發(fā)者也就是serverHandle要觸發(fā)路由(hijacked finishRequest暫且不提)。先看一個(gè)接口。

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

            實(shí)現(xiàn)了handler接口,就意味著往server端添加了處理請(qǐng)求的邏輯函數(shù)。

            serverHandle調(diào)用ServeHttp來(lái)選擇觸發(fā)的HandleFunc。這里面會(huì)做一個(gè)判斷,如果你傳遞了Handler,就調(diào)用你自己的,如果沒(méi)傳遞就用DefaultServeMux默認(rèn)的。到這整體流程就結(jié)束了。

            過(guò)程是:

            DefaultServeMux.ServeHttp執(zhí)行的簡(jiǎn)單流程.

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

            查找路由,mux.handler函數(shù)里又調(diào)用了另外一個(gè)函數(shù)mux.handler(r.Host, r.URL.Path)。

            還記得我們的ServeMux里的hosts標(biāo)記嗎?這個(gè)函數(shù)里會(huì)進(jìn)行判斷。

            // 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的流程了

            我們來(lái)總結(jié)一下。

            首先調(diào)用Http.HandleFunc

            按順序做了幾件事:

            1. 調(diào)用了DefaultServerMux的HandleFunc
            2. 調(diào)用了DefaultServerMux的Handle
            3. 往DefaultServeMux的map[string]muxEntry中增加對(duì)應(yīng)的handler和路由規(guī)則

            別忘記DefaultServerMux是ServeMux的實(shí)例。其實(shí)都是圍繞ServeMux,muxEntry2個(gè)結(jié)構(gòu)進(jìn)行操作綁定。

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

            按順序做了幾件事情:

            1. 實(shí)例化Server
            2. 調(diào)用Server的ListenAndServe()
            3. 調(diào)用net.Listen("tcp", addr)監(jiān)聽端口,啟動(dòng)for循環(huán),等待accept請(qǐng)求
            4. 對(duì)每個(gè)請(qǐng)求實(shí)例化一個(gè)Conn,并且開啟一個(gè)goroutine處理請(qǐng)求。
            5. 如:go c.serve()
            6. 讀取請(qǐng)求的內(nèi)容w, err := c.readRequest(),也就是response的取值過(guò)程。
            7. 調(diào)用serverHandler的ServeHTTP,ServeHTTP里會(huì)判斷Server的屬性里的header是否為空,如果沒(méi)有設(shè)置handler,handler就設(shè)置為DefaultServeMux,反之用自己的(我們后面會(huì)做一個(gè)利用自己的Handler寫服務(wù)器)
            8. 調(diào)用DefaultServeMux的ServeHttp( 因?yàn)槲覀儧](méi)有自己的Handler,所以走默認(rèn)的)
            9. 通過(guò)request選擇匹配的handler:

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

              B 如果有路由滿足,返回這個(gè)handler

              C 如果沒(méi)有路由滿足,返回NotFoundHandler

            10. 根據(jù)返回的handler進(jìn)入到這個(gè)handler的ServeHTTP

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

            通過(guò)上面的解釋大致明白了我們綁定觸發(fā)的都是DefaultServeMux的Handler。現(xiàn)在我們來(lái)實(shí)現(xiàn)一個(gè)自己的Handler,這也是做框架的第一步。我們先來(lái)敲代碼。

            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做一個(gè)智能的路由出來(lái)。

            不過(guò)還是建議使用國(guó)內(nèi)Golang語(yǔ)言框架beego,已開源。一款非常不錯(cuò)的框架,謝大維護(hù)的很用心,絕對(duì)良心框架,而且文檔支持,社區(qū)也很不錯(cuò)。

            最后附上一張最早設(shè)計(jì)框架時(shí)候的一個(gè)流程圖(3年前)。大家可以簡(jiǎn)單看看,當(dāng)然也可以嘗試的動(dòng)動(dòng)手。起碼收獲很多。

            (點(diǎn)擊放大圖像)

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

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

            作者簡(jiǎn)介

            劉金龍,藝名:金灶沐 ,go語(yǔ)言愛好者,2015年8月加入創(chuàng)業(yè)團(tuán)隊(duì),負(fù)責(zé)各種“打雜”工作,之前在360電商購(gòu)物小蜜java組擔(dān)任java高級(jí)工程師職位,負(fù)責(zé)購(gòu)物小蜜服務(wù)開發(fā)。14年開始用go語(yǔ)言做高并發(fā)服務(wù)并且嘗試閱讀go語(yǔ)言的源碼來(lái)學(xué)習(xí)go語(yǔ)言的特性。

            posted on 2017-08-01 17:19 思月行云 閱讀(521) 評(píng)論(0)  編輯 收藏 引用 所屬分類: Golang
            亚洲精品无码久久久久| 久久久久久国产精品美女| 久久久久久久91精品免费观看| 久久精品国产99国产精品| 久久本道综合久久伊人| 亚洲国产成人久久综合野外| 香蕉久久夜色精品国产2020| 久久婷婷午色综合夜啪| 99精品国产在热久久无毒不卡| 久久久久久久亚洲精品| 久久www免费人成看片| 精品久久久久中文字| 精品国产乱码久久久久软件| 狠狠人妻久久久久久综合| 99精品国产免费久久久久久下载 | 色狠狠久久综合网| 狠狠狠色丁香婷婷综合久久俺| 久久天天躁狠狠躁夜夜2020| 国产精品久久永久免费| 久久人人爽人人爽AV片| 久久精品中文字幕久久| 久久久久久久久久久久中文字幕 | 2021精品国产综合久久| 久久人人爽人人爽AV片| 7777久久亚洲中文字幕| 亚洲中文字幕无码久久2017 | 日韩影院久久| 区亚洲欧美一级久久精品亚洲精品成人网久久久久 | 亚洲午夜无码久久久久| 久久九九久精品国产| 久久久91精品国产一区二区三区| 亚洲国产精品久久久天堂| 久久婷婷五月综合97色直播| 一本综合久久国产二区| 久久久久国产精品嫩草影院| 中文字幕成人精品久久不卡| 精品久久久久久国产牛牛app| 国产精品久久影院| 免费国产99久久久香蕉| 99久久免费国产精品| 精品久久人人爽天天玩人人妻|