青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

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)載,并在文章開(kāi)頭給出了原文出處,如有再轉(zhuǎn),敬請(qǐng)保留相關(guān)信息,這是大家對(duì)原創(chuàng)作者勞動(dòng)成果的自覺(jué)尊重!!如為您帶來(lái)不便,請(qǐng)于本博下留言,謝謝配合。

常用鏈接

留言簿(1)

隨筆分類

隨筆檔案

相冊(cè)

Awesome

Blog

Book

GitHub

Link

搜索

  •  

積分與排名

  • 積分 - 220944
  • 排名 - 117

最新評(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)和讀寫(xiě)鎖(RWLock),互斥鎖和讀寫(xiě)鎖的區(qū)別是:互斥鎖無(wú)論是讀操作還是寫(xiě)操作都會(huì)對(duì)目標(biāo)加鎖也就是說(shuō)所有的操作都需要排隊(duì)進(jìn)行,讀寫(xiě)鎖是加鎖后寫(xiě)操作只能排隊(duì)進(jìn)行但是可以并發(fā)進(jìn)行讀操作,要注意一點(diǎn)就是讀的時(shí)候?qū)懖僮魇亲枞模瑢?xiě)操作進(jìn)行的時(shí)候讀操作是阻塞的。類型sync.Mutex/sync.RWMutex的零值表示了未被鎖定的互斥量。也就是說(shuō),它是一個(gè)開(kāi)箱即用的工具。只需對(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ù)的存在
上面的代碼可以改寫(xiě)為

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í)行完成。文章開(kāi)始我們說(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)的開(kā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é)省很多上下文切換開(kāi)銷(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ō)多線程切換上下文開(kāi)銷很大嗎?其實(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)的開(kā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)單寫(xiě)一個(gè)“hello world”的例子,讓大家似懂非懂的,不如單獨(dú)開(kāi)篇講解一下,從Tcp 到 Protobuf 再到RPC,然后寫(xiě)一個(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)開(kāi)啟了,等待數(shù)據(jù)的到來(lái),處理數(shù)據(jù)。

看出來(lái)差距在哪里了吧?就是CGI每次都要fork進(jìn)程,這個(gè)開(kāi)銷很大的。(感興趣的看下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;
            }
            ...
    }//…是省略,自己去寫(xiě)一個(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. 寫(xiě)一個(gè)簡(jiǎn)單的HTTP服務(wù)器
  2. 寫(xiě)一個(gè)稍微復(fù)雜帶路由的HTTP服務(wù)器
  3. 分析源碼,然后實(shí)現(xiàn)一個(gè)自定義Handler的服務(wù)器

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

1、寫(xiě)一個(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)聽(tīng),其實(shí)很簡(jiǎn)單,但是很多人看到這都會(huì)有疑惑,別著急,咱們?cè)创a分析的時(shí)候你會(huì)看到。

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

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

是不是挺驚訝的,404了,路由沒(méi)有匹配到。可是我們寫(xiě)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)聽(tīng),把前綴相同的路由業(yè)務(wù)實(shí)現(xiàn)放在一個(gè)文件里,這樣也可以解耦合,是不是清爽多了。其實(shí)我們可以在做的更加靈活些。在文章最后我們放出來(lái)一個(gè)流程圖,按照流程圖做你們就能寫(xiě)出一個(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ì)讓你更加不明白。

我們從入口開(kāi)始,首先看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//讀寫(xiě)鎖
       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)聽(tīng)了?原來(lái)如此。大致綁定流程大家看明白了嗎?如果不理解可以回去再看一遍。

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

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

上面這句就是,”:8081”是監(jiān)聽(tīng)的端口,也是socket監(jiān)聽(tīng)的端口,第二個(gè)參數(shù)就是我們的Handler,這里我們寫(xiě)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)聽(tīng)的地址和端口
    Handler        Handler       // 所有請(qǐng)求需要調(diào)用的Handler(
    ReadTimeouttime.Duration // 讀的最大Timeout時(shí)間
    WriteTimeouttime.Duration // 寫(xiě)的最大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)聽(tīng),里面就是調(diào)用for進(jìn)行accept的處理了
 func(srv *Server) ListenAndServe() error  //開(kāi)啟http server服務(wù)
 func(srv *Server) ListenAndServeTLS(certFile, keyFile string) error //開(kāi)啟https server服務(wù)

Server的ListenAndServe方法通過(guò)TCP的方式監(jiān)聽(tīng)端口,然后調(diào)用Serve里的實(shí)現(xiàn)等待client來(lái)accept,然后開(kāi)啟一個(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供讀寫(xiě)
        Header() Header
    
        // 這個(gè)方法寫(xiě)Response的Body
        Write([]byte) (int, error)
    
        // 這個(gè)方法根據(jù)HTTP State Code來(lái)寫(xiě)Response的Header
    
        WriteHeader(int)
    }
    
    // Flusher的作用是被Handler調(diào)用來(lái)將寫(xiě)緩存中的數(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ò)寫(xiě)操作
        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í)候,開(kāi)啟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)聽(tīng)端口,啟動(dòng)for循環(huán),等待accept請(qǐng)求
  4. 對(duì)每個(gè)請(qǐng)求實(shí)例化一個(gè)Conn,并且開(kāi)啟一個(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寫(xiě)服務(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,已開(kāi)源。一款非常不錯(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ǔ)言愛(ài)好者,2015年8月加入創(chuàng)業(yè)團(tuán)隊(duì),負(fù)責(zé)各種“打雜”工作,之前在360電商購(gòu)物小蜜java組擔(dān)任java高級(jí)工程師職位,負(fù)責(zé)購(gòu)物小蜜服務(wù)開(kāi)發(fā)。14年開(kāi)始用go語(yǔ)言做高并發(fā)服務(wù)并且嘗試閱讀go語(yǔ)言的源碼來(lái)學(xué)習(xí)go語(yǔ)言的特性。

posted on 2017-08-01 17:19 思月行云 閱讀(546) 評(píng)論(0)  編輯 收藏 引用 所屬分類: Golang
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            久久精品麻豆| 亚洲欧美综合精品久久成人| 欧美日韩和欧美的一区二区| 亚洲国产成人精品视频| 牛牛国产精品| 久久中文字幕导航| 亚洲国产综合91精品麻豆| 亚洲黄一区二区| 欧美日韩视频在线| 欧美aⅴ一区二区三区视频| 麻豆av一区二区三区久久| 亚洲麻豆av| 亚洲欧美精品在线观看| 尤物九九久久国产精品的分类| 亚洲欧美日韩国产中文在线| 久久久久九九视频| 亚洲免费观看视频| aa成人免费视频| 国产日本欧美一区二区三区| 亚洲国产精品视频一区| 亚洲夫妻自拍| 欧美在线观看视频| 久久精品国产亚洲一区二区三区| 欧美乱大交xxxxx| 亚洲第一在线视频| 亚洲视频在线观看视频| 久久夜精品va视频免费观看| 欧美在线中文字幕| 国内精品免费在线观看| 99成人精品| 亚洲午夜精品一区二区| 国产精品hd| 国产亚洲欧美色| 亚洲综合色网站| 欧美一区亚洲| 欧美日一区二区在线观看 | 欧美一级视频免费在线观看| 欧美性猛交一区二区三区精品| 91久久精品一区| 悠悠资源网亚洲青| 久久人人爽人人| 亚洲国产影院| 亚洲欧美国产另类| 国产亚洲成av人在线观看导航 | 9l国产精品久久久久麻豆| 香蕉av777xxx色综合一区| 久久在线精品| 一本色道久久综合亚洲精品不| 欧美日韩亚洲综合一区| 亚洲动漫精品| 亚洲欧美日韩国产综合在线| 国内精品久久久久久影视8| 久久久亚洲一区| 性欧美大战久久久久久久久| 欧美激情小视频| 久久久久久夜| 亚洲少妇最新在线视频| 国产欧美精品国产国产专区| 久久免费国产| 一本色道88久久加勒比精品| 性欧美激情精品| 亚洲精品一二三| 国产午夜一区二区三区| 欧美日韩一区二区视频在线观看 | 欧美 日韩 国产一区二区在线视频| 午夜精品一区二区三区电影天堂| 久久色中文字幕| 亚洲一区二区视频| 亚洲国产另类精品专区| 国产精品久久久一本精品| 欧美高潮视频| 午夜宅男久久久| 91久久在线观看| 亚洲国产影院| 欧美高清一区| 久久综合电影| 欧美 日韩 国产精品免费观看| 亚洲欧美日韩综合| 欧美亚洲一区在线| 亚洲欧美国产精品va在线观看| 亚洲精品视频一区| 91久久精品国产91性色tv| 亚洲第一中文字幕在线观看| 亚洲黄色免费电影| 尹人成人综合网| 亚洲黄色毛片| 一卡二卡3卡四卡高清精品视频| 亚洲国产欧美在线人成| 亚洲精品一区二区三区婷婷月| 日韩一区二区精品视频| 亚洲欧美另类久久久精品2019| 日韩视频在线一区二区三区| 99re6这里只有精品| 中国女人久久久| 麻豆成人在线| 亚洲视频电影在线| 久久免费视频这里只有精品| 欧美激情视频网站| 国产精品视频网| 日韩午夜精品视频| 久久狠狠婷婷| 亚洲美女视频网| 欧美激情一区二区三区不卡| 国产精品久久久一区麻豆最新章节| 国产精品免费区二区三区观看| 久久精品国产精品亚洲精品| 亚洲少妇诱惑| 欧美顶级少妇做爰| 亚洲激情综合| 欧美亚洲色图校园春色| 鲁大师成人一区二区三区| 99热在这里有精品免费| 欧美激情亚洲自拍| 一区二区三区亚洲| 亚洲影院一区| 99精品欧美一区二区蜜桃免费| 久久久久国内| 亚洲日韩欧美视频一区| 亚洲高清不卡在线| 午夜亚洲性色视频| 国产精品欧美经典| 先锋影音国产一区| 夜夜夜精品看看| 欧美激情第一页xxx| 亚洲精品在线一区二区| 性感少妇一区| 久久av红桃一区二区小说| 欧美日一区二区三区在线观看国产免| 国产精品久久久久久久久免费樱桃 | 国产精品入口福利| 一区在线观看视频| 久久亚洲欧洲| 欧美精品成人| 亚洲欧美国产一区二区三区| 亚洲精品一区在线观看| 欧美日韩一区二区免费视频| 亚洲欧美日韩网| 亚洲视频视频在线| 国产精品久久久免费| 久久蜜臀精品av| 欧美午夜不卡在线观看免费| 久久久免费av| 欧美中文字幕久久| 亚洲欧美日韩一区二区三区在线| 欧美精品入口| 亚洲欧美国内爽妇网| 亚洲高清色综合| 99视频在线观看一区三区| 国产一区在线免费观看| 99re在线精品| 亚洲二区在线观看| 亚洲在线1234| 亚洲国产美女| 久久婷婷亚洲| 亚洲精品欧美在线| 日韩午夜免费| 国产精品永久在线| 亚洲国产日韩欧美| 亚洲狠狠婷婷| 久久久久99精品国产片| 亚洲一区日本| 国产精品免费网站在线观看| 免费观看在线综合| 国产综合视频| 午夜精品在线观看| 欧美一级大片在线观看| 久久亚洲精品伦理| 久久综合导航| 国产精品亚洲人在线观看| 亚洲免费av观看| 亚洲一区3d动漫同人无遮挡| 欧美激情一区二区三区| 欧美在线网站| 韩国av一区二区三区在线观看| 亚洲国产成人久久综合| 亚洲三级国产| 国产精品vvv| 性欧美激情精品| 亚洲片国产一区一级在线观看| 亚洲国产一区二区a毛片| 亚洲在线免费观看| 亚洲国产小视频| 国产精品一级久久久| 裸体歌舞表演一区二区| 亚洲最新视频在线| 欧美激情免费在线| 欧美一区二区三区久久精品| 亚洲高清不卡av| 国产日韩精品在线播放| 欧美喷潮久久久xxxxx| 久久精品视频在线| 亚洲影视在线| 亚洲欧洲视频在线| 久久久久久亚洲精品不卡4k岛国| 一区二区三区日韩精品| 91久久在线观看| 久久免费视频网| 久久动漫亚洲| 99热在线精品观看| 尤物精品在线|