Golang服務器的網絡層實現

原文:Golang服務器的網絡層實現html

因爲最近有接觸到一些長鏈接的服務器實現,對網絡模型有所學習。對基於C/C++的網絡模型實現和基於GoLang的實現對比下來,發現Golang的網絡模型編程難度大大下降,這得益於Golang的goroutine,能夠在編程的時候肆無忌憚的建立併發"線程",當服務器能爲每個客戶端都開啓若干"線程"的話,編程變的簡單不少。程序員

傳統語言的網絡層處理

服務須要同時服務N個客戶端,因此傳統的編程方式是採用IO複用,這樣在一個線程中對N個套接字進行事件捕獲,當讀寫事件產生後再真正read()或者write(),這樣才能提升吞吐:golang

上圖中:編程

  • 綠色線程爲接受客戶端TCP連接的線程,使用阻塞的調用socket.accept(),當有新的鏈接到來後,將socket對象conn加入IO複用隊列。安全

  • 紫色線程爲IO複用的阻塞調用,一般採用epoll等系統調用實現IO複用。當IO複用隊列中的任意socket有數據到來,或者寫緩衝區空閒時可觸發epoll調用的返回,不然阻塞epoll調用。數據的實際發送和接收都在紫色線程中完成。因此爲了提升吞吐,對某個socket的readwrite都應該使用非阻塞的模式,這樣才能最大限度的提升系統吞吐。例如,假設正在對某個socket調用阻塞的write,當數據沒有徹底發送完成前,write將沒法返回,從而阻止了整個epoll進入下一個循環,若是這個時候其餘的socket有讀就緒的話,將沒法第一時間響應。因此非阻塞的讀寫將在某個fd讀寫較慢的時候,馬上返回,而不會一直等到讀寫結束。這樣才能提升吞吐。然而,採用非阻讀寫將大大提升編程難度。服務器

  • 紫色線程負責將數據進行解碼並放入隊列中,等待工做線程處理;工做線程有數據要發送時,也將數據放入發送隊列,並經過某種機制通知紫色線程對應的socket有數據要寫,進而使得數據在紫色線程中寫入socket。網絡

這種模型的編程難度主要體如今:併發

  1. 線程少(也不能太多),致使一個線程須要處理多個描述符,從而存在對描述符狀態的維護問題。甚至,業務層面的會話等都須要當心維護socket

  2. 非阻塞IO調用,使描述符的狀態更爲複雜tcp

  3. 隊列的同步處理

不得不說,能用C或C++來寫服務器的是真大神!

Golang的goroutine

Golang是一門比較新的語言,正在快速的發展。Golang從語言層面支持一種叫協程的輕量級線程模型,稱爲goroutine。當咱們建立協程時,實際並不會建立操做系統的線程,Golang會使用現有的線程來調度協程。也就是說,從程序員的角度,協程是併發執行的,好像線程一下,而從操做系統的角度來看,程序可能只有幾個線程在運行。在同一個應用程序中,協程能夠有成千上萬個!因此能夠有成千上萬個併發任務,而這些任務的調度又十分輕量,比線程調度輕量的多的多。因此從程序員的角度,使用Golang就能夠在一個應用程序中同時開啓成千上萬個併發任務。簡直逆天!

在Golang中使用go關鍵字來開啓一個goroutine

func main() {
    log.Println("Hello, world")

    netListen, err := net.Listen("tcp", "localhost:4000")
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }

    defer netListen.Close()

    log.Println("Waiting for clients")

    for {
        conn, err := netListen.Accept()
        if err != nil {
            continue
        }

        log.Println(conn.RemoteAddr().String(), " tcp connect success")
        go handleConnection(conn)
    }
}

func handleConnection(conn net.Conn) {
    ...
}

Golang的channel

除了對併發的支持外,Golang中有一種叫channel的併發同步機制。channel相似隊列,是goroutine安全的。因此結合goroutinechannel能夠垂手可得的實現併發編程。

Golang如何實現網絡層

經過參考多個Golang的開源程序,筆者得出的結論是:肆無忌憚的用goroutine吧。因而一個Golang版的網絡模型大體是這樣的:

上圖是單個客戶端鏈接的服務器模塊結構,一樣的一個顏色表明一個協程:

  • 綠色goroutine依然是接受TCP連接

  • 當完成握手accept返回conn對象後,使用一個單獨的goroutine阻塞讀(紫色),使用一個單獨的goroutine阻塞寫(紅色)

  • 讀到的數據經過解碼後放入讀channel,並由藍色的goroutine來處理

  • 須要寫數據時,藍色的goroutine將數據寫入寫channel,從而觸發紅色的goroutine編碼並寫入conn

能夠看到,針對一個客戶端,服務端至少有3個goroutine在單獨爲這個客戶端服務。若是從線程的角度來看,簡直是浪費啊,然而這就是協程的好處。這個模型很容易理解,由於跟人們的正常思惟方式是一致的。而且都是阻塞的調用,因此無需維護狀態。

再來看看多個客戶端的狀況:

在多個客戶端之間,雖然用了相同的顏色表示goroutine,但實際上他們都是獨立的goroutine,能夠想象goroutine的數量將是驚人的。然而,根本不用擔憂!這樣的應用程序可能真正的線程只有幾個而已。

相關文章
相關標籤/搜索