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

Fork me on GitHub
隨筆 - 215  文章 - 13  trackbacks - 0
<2017年8月>
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789


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

常用鏈接

留言簿(1)

隨筆分類

隨筆檔案

相冊

Awesome

Blog

Book

GitHub

Link

搜索

  •  

積分與排名

  • 積分 - 220946
  • 排名 - 117

最新評論

閱讀排行榜

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

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

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

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

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

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

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

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

我們選擇用Golang的container標(biāo)準(zhǔn)包中的鏈表來做對象池的底層數(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)庫實(shí)現(xiàn)的是一個(gè)雙向鏈表,并且已經(jīng)為我們封裝好了各種Front/Back方法。不過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
 }

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

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

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

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

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

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

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

fatal error: all goroutines are asleep - deadlock

不過在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)庫中的并發(fā)控制-WaitGroup

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

1、time.Sleep()

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

2、channel

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

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ù)量最終無法變成0,會(huì)造成死鎖,比如上面例子我add(2)但是我自始至終只有一個(gè)Done,那剩下的任務(wù)一直存在于wg隊(duì)列中,主協(xié)程會(huì)認(rèn)為還有任務(wù)沒有完成便會(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)包來確保原子操作,所以sync.WaitGroup是并發(fā)安全的。

作者簡介

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

Golang標(biāo)準(zhǔn)庫探秘(二):快速搭建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語言作為Nginx+FastCGI上一個(gè)優(yōu)秀的解釋語言也占據(jù)大半江山。而如今的Golang也作為高并發(fā)服務(wù)器語言橫空出世,因其“語法簡單、代碼結(jié)構(gòu)簡明,維護(hù)成本還低,天生高并發(fā)”等特性而被廣泛應(yīng)用,尤其是各種云服務(wù),還有Docker也是用Golang來做實(shí)現(xiàn)語言。

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

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

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

多線程:

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

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

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

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

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

簡述:M是執(zhí)行G的機(jī)器線程,跟P綁定才可以執(zhí)行,P存放G的隊(duì)列。看到這里大家會(huì)問到剛剛不是說多線程切換上下文開銷很大嗎?其實(shí)每個(gè)M都有一個(gè)g0棧內(nèi)存,用來執(zhí)行運(yùn)行時(shí)管理命令。調(diào)度時(shí)候M的g0會(huì)從每個(gè)G棧取出棧(現(xiàn)場),調(diào)度之后再保存到G,這樣不同的M就可以接著調(diào)度了。所有上下文都是自己在切換,省去了內(nèi)核帶來的開銷,而且Golang會(huì)觀察,長時(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)勢了吧。不要擔(dān)心GC問題,選擇場景問題。

實(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)就不在這次說了,TCP其實(shí)是塊難啃的骨頭,簡單的幾乎話說不清楚,如果是簡單寫一個(gè)“hello world”的例子,讓大家似懂非懂的,不如單獨(dú)開篇講解一下,從Tcp 到 Protobuf 再到RPC,然后寫一個(gè)稍微復(fù)雜點(diǎn)的tcp服務(wù)器,我們也可以處理下“粘包,丟包”等問題(Protobuf解決或者做一個(gè)分包算法),如果簡單的demo可能會(huì)導(dǎo)致你丟失興趣的。

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

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

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

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

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

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

CGI服務(wù)器

需要用到的包:

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

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

看來我們成功了。來看下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,所有請求都會(huì)調(diào)用這個(gè),這個(gè)后面分析HTTP源碼的時(shí)候回詳細(xì)講解它是做什么的。Handler是在子程序中執(zhí)行CGI腳本的。

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

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

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

用到的包:

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

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

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

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ù)器

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

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

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

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

1、寫一個(gè)簡單的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)程
    }

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

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

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

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

問題:

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

問題1:

我們在源碼閱讀分析的時(shí)候會(huì)解決。

問題2:

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

來我們先上代碼:

靜態(tài)目錄:

  1. css
  2. js
  3. image

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

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

我們來看下代碼

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í)我們可以在做的更加靈活些。在文章最后我們放出來一個(gè)流程圖,按照流程圖做你們就能寫出一個(gè)簡單的mvc路由框架。接下來看運(yùn)行之后的結(jié)果。

如下圖: 

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

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

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

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

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

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

上面2個(gè)函數(shù)通過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í)例對象。導(dǎo)致我們就把路由和執(zhí)行方法綁注冊好了。不過大家請想下handle/res的問題?

從上面的分析我們要知道幾個(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,通過HandlerFunc(f)來進(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è)適配器。

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

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á)式對應(yīng)哪個(gè)handler
       pattern  string//路由
    }

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

接下來我們來看實(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è)代碼看出來,Server這個(gè)結(jié)構(gòu)很重要。我們來看看他是什么。

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

Server提供的方法有:

 func(srv *Server) Serve(l net.Listener) error   //對某個(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方法通過TCP的方式監(jiān)聽端口,然后調(diào)用Serve里的實(shí)現(xiàn)等待client來accept,然后開啟一個(gè)協(xié)程來處理邏輯(go c.serve)。

它的格式

func(srv *Server) ListenAndServe() error 

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

ResponseWriter:生成Response的接口

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

ServeMux:路由,后面會(huì)說到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)閉這次請求,讀取數(shù)據(jù)的,刷新緩沖區(qū)的等實(shí)現(xiàn)。這里我們主要關(guān)注一個(gè)c.readRequest(),通過redRequest可以得到Response,就是輸出給客戶端數(shù)據(jù)的一個(gè)回復(fù)者。

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

ResponseWriter, Flusher, Hijacker

    // ResponseWriter的作用是被Handler調(diào)用來組裝返回的Response的
    type ResponseWriter interface {
        // 這個(gè)方法返回Response返回的Header供讀寫
        Header() Header
    
        // 這個(gè)方法寫Response的Body
        Write([]byte) (int, error)
    
        // 這個(gè)方法根據(jù)HTTP State Code來寫Response的Header
    
        WriteHeader(int)
    }
    
    // Flusher的作用是被Handler調(diào)用來將寫緩存中的數(shù)據(jù)推給客戶端
    type Flusher interface {
        // 刷新緩沖區(qū)
        Flush()
    }
    
    // Hijacker的作用是被Handler調(diào)用來關(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 // 對應(yīng)請求信息
        chunking      bool     // 是否使用chunk
        wroteHeaderbool     // header是否已經(jīng)執(zhí)行過寫操作
        wroteContinuebool     // 100 Continue response was written
        header        Header   // 返回的HTTP的Header
        written       int64    // Body的字節(jié)數(shù)
        contentLength int64    // Content長度
        status        int      // HTTP狀態(tài)
        needSniffbool  
//是否需要使用sniff。(當(dāng)沒有設(shè)置Content-Type的時(shí)候,開啟sniff能根據(jù)HTTP body來確定Content-Type)
        closeAfterReplybool    
//是否保持長鏈接。如果客戶端發(fā)送的請求中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)

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

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

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

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

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

過程是:

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

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

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

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

按順序做了幾件事:

  1. 調(diào)用了DefaultServerMux的HandleFunc
  2. 調(diào)用了DefaultServerMux的Handle
  3. 往DefaultServeMux的map[string]muxEntry中增加對應(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請求
  4. 對每個(gè)請求實(shí)例化一個(gè)Conn,并且開啟一個(gè)goroutine處理請求。
  5. 如:go c.serve()
  6. 讀取請求的內(nèi)容w, err := c.readRequest(),也就是response的取值過程。
  7. 調(diào)用serverHandler的ServeHTTP,ServeHTTP里會(huì)判斷Server的屬性里的header是否為空,如果沒有設(shè)置handler,handler就設(shè)置為DefaultServeMux,反之用自己的(我們后面會(huì)做一個(gè)利用自己的Handler寫服務(wù)器)
  8. 調(diào)用DefaultServeMux的ServeHttp( 因?yàn)槲覀儧]有自己的Handler,所以走默認(rèn)的)
  9. 通過request選擇匹配的handler:

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

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

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

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

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

通過上面的解釋大致明白了我們綁定觸發(fā)的都是DefaultServeMux的Handler。現(xiàn)在我們來實(shí)現(xiàn)一個(gè)自己的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做一個(gè)智能的路由出來。

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

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

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

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

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

作者簡介

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

posted on 2017-08-01 17:19 思月行云 閱讀(546) 評論(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>
            99精品福利视频| 91久久夜色精品国产网站| 美女精品视频一区| 日韩天堂av| 激情综合电影网| 亚洲久久成人| 国产欧美精品va在线观看| 久久青草久久| 欧美日韩免费高清一区色橹橹| 国产精品国产精品国产专区不蜜| 欧美一区精品| 另类图片综合电影| 黄色精品一区| 亚洲高清免费| 一区二区三区国产精华| 亚洲欧美日本在线| 欧美成ee人免费视频| 午夜精品久久久久久久99黑人| 亚洲免费观看高清完整版在线观看| 99综合在线| 亚洲欧美日韩国产| 久久综合狠狠| 久久综合伊人77777| 欧美日韩高清在线| 国产丝袜一区二区三区| 老司机精品视频网站| 欧美日韩蜜桃| 久久久成人精品| 欧美日韩一区二区视频在线观看 | 久久不见久久见免费视频1| 亚洲黄色成人网| 欧美国产精品劲爆| 午夜久久影院| 国产一区二区日韩精品欧美精品 | 校园激情久久| 久久久精品五月天| 国产亚洲成av人在线观看导航| 日韩天堂在线视频| 午夜精品福利视频| 亚洲一区在线观看视频| 亚洲最新视频在线| 亚洲免费不卡| 久久综合狠狠综合久久综青草| 亚洲激情女人| 欧美精品激情在线| 在线观看欧美亚洲| 麻豆精品国产91久久久久久| 一区二区三区国产盗摄| 国产在线视频欧美一区二区三区| 欧美国产日韩在线观看| 欧美一区视频| 午夜精彩视频在线观看不卡| 欧美成人一区在线| 亚洲精品久久久久| 亚洲欧美日韩系列| 欧美日韩国产高清视频| 久久视频在线看| 久久精品一区二区三区四区| 激情欧美丁香| 一区二区三区久久网| 91久久国产综合久久蜜月精品| 欧美极品在线播放| 久久久91精品国产| 国产综合久久久久久| 亚洲精品中文字幕女同| 你懂的网址国产 欧美| 欧美日韩国产页| 久久国产综合精品| 久久亚洲二区| 蜜桃av综合| 亚洲激情在线激情| 影音先锋亚洲精品| 久久久www免费人成黑人精品| 亚洲欧美怡红院| 亚洲网友自拍| 国产欧美一区二区三区久久人妖| 欧美jizzhd精品欧美喷水 | 亚洲人体1000| 亚洲综合三区| 亚洲亚洲精品在线观看 | 亚洲精品国产拍免费91在线| 欧美影院成年免费版| 国产精品自拍视频| 欧美激情一区二区三区在线视频| 蘑菇福利视频一区播放| 一区二区三区**美女毛片| 国产精品一区久久久久| 亚洲高清av| 亚洲综合国产激情另类一区| 国产一区二区在线观看免费播放 | 欧美日韩久久| 亚洲精品综合在线| 久久久在线视频| 麻豆视频一区二区| 先锋影音久久| 亚洲欧美日韩成人| 欧美中日韩免费视频| 久久一区二区三区av| 亚洲人被黑人高潮完整版| 欧美色大人视频| 欧美日韩国产综合网| 久久精品色图| 午夜精品视频在线| 欧美日韩你懂的| 亚洲自拍高清| 亚洲一区二区三区免费视频| 欧美激情一区二区三区| 亚洲国产日韩一区二区| 欧美激情第三页| 亚洲视屏一区| 制服丝袜激情欧洲亚洲| 久久五月激情| 亚洲欧洲一区二区在线观看 | 久久精品国产999大香线蕉| 欧美性久久久| 欧美激情五月| 久久免费视频一区| 欧美日韩调教| 在线电影国产精品| 亚洲欧美日韩在线不卡| 一区二区高清| 欧美18av| 亚洲免费伊人电影在线观看av| 亚洲欧美日韩视频二区| 欧美韩日一区二区三区| 国产日韩欧美在线播放不卡| 欧美一区二区三区视频免费播放 | 久久综合久久美利坚合众国| 一区二区三区福利| 国产精品成人午夜| 国产精品一区二区三区久久| 欧美一区二区| 欧美一区二区三区免费观看| 久久国产精品72免费观看| 欧美国产日韩a欧美在线观看| 欧美激情片在线观看| 久热精品视频在线观看一区| 国产免费一区二区三区香蕉精| 欧美日韩中文精品| 加勒比av一区二区| 久久久久久久一区二区三区| 亚洲国产毛片完整版| 免费在线欧美黄色| 午夜精品av| 久久久激情视频| 久久国产精品72免费观看| 欧美视频精品在线| 日韩一级欧洲| 一区二区三区久久久| 国产精品免费观看在线| 久久免费视频在线观看| 久久激情网站| 国产一区二区三区免费观看| 免费av成人在线| 欧美日本一区| 蜜桃av噜噜一区| 欧美日韩一区在线播放| 亚洲欧美伊人| 久久综合色播五月| 欧美—级高清免费播放| 欧美一区二区日韩一区二区| 久久久久综合网| 亚洲一区欧美一区| 欧美精品大片| 欧美成人一区二区三区| 一区二区三区在线观看欧美 | 亚洲日本欧美| 欧美亚一区二区| 久久成人综合网| 久久av在线| 亚洲福利视频专区| 国产一区二区三区电影在线观看| 亚洲性图久久| 久久精品99久久香蕉国产色戒| 国产精品久久中文| 亚洲靠逼com| 亚洲欧洲精品一区二区三区波多野1战4| 亚洲欧美日韩中文播放| 99亚洲一区二区| 欧美女激情福利| 亚洲视频一区二区在线观看| 国产专区一区| 久久偷看各类wc女厕嘘嘘偷窃| 欧美aⅴ99久久黑人专区| 亚洲免费高清| 欧美日韩一区二区三区四区五区| 日韩小视频在线观看专区| 亚洲一二三区在线| 亚洲区在线播放| 欧美激情一区二区在线| 一区二区三区精品| 伊甸园精品99久久久久久| 欧美成ee人免费视频| 一区二区三区视频观看| 免费成人在线观看视频| 亚洲一区区二区| 亚洲欧美文学| 亚洲精品一区在线观看| 国产自产2019最新不卡| 欧美人妖另类|