原文: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的read
和write
都應該使用非阻塞的模式,這樣才能最大限度的提升系統吞吐。例如,假設正在對某個socket調用阻塞的write
,當數據沒有徹底發送完成前,write
將沒法返回,從而阻止了整個epoll
進入下一個循環,若是這個時候其餘的socket
有讀就緒的話,將沒法第一時間響應。因此非阻塞的讀寫將在某個fd讀寫較慢的時候,馬上返回,而不會一直等到讀寫結束。這樣才能提升吞吐。然而,採用非阻讀寫將大大提升編程難度。服務器
紫色線程負責將數據進行解碼並放入隊列中,等待工做線程處理;工做線程有數據要發送時,也將數據放入發送隊列,並經過某種機制通知紫色線程對應的socket有數據要寫,進而使得數據在紫色線程中寫入socket。網絡
這種模型的編程難度主要體如今:併發
線程少(也不能太多),致使一個線程須要處理多個描述符,從而存在對描述符狀態的維護問題。甚至,業務層面的會話等都須要當心維護socket
非阻塞IO調用,使描述符的狀態更爲複雜tcp
隊列的同步處理
不得不說,能用C或C++來寫服務器的是真大神!
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
的併發同步機制。channel
相似隊列,是goroutine
安全的。因此結合goroutine
和channel
能夠垂手可得的實現併發編程。
經過參考多個Golang的開源程序,筆者得出的結論是:肆無忌憚的用goroutine
吧。因而一個Golang版的網絡模型大體是這樣的:
上圖是單個客戶端鏈接的服務器模塊結構,一樣的一個顏色表明一個協程:
綠色goroutine
依然是接受TCP連接
當完成握手accept
返回conn
對象後,使用一個單獨的goroutine
來阻塞讀
(紫色),使用一個單獨的goroutine
來阻塞寫
(紅色)
讀到的數據經過解碼後放入讀channel
,並由藍色的goroutine
來處理
須要寫數據時,藍色的goroutine
將數據寫入寫channel
,從而觸發紅色的goroutine
編碼並寫入conn
能夠看到,針對一個客戶端,服務端至少有3個goroutine
在單獨爲這個客戶端服務。若是從線程的角度來看,簡直是浪費啊,然而這就是協程的好處。這個模型很容易理解,由於跟人們的正常思惟方式是一致的。而且都是阻塞的調用,因此無需維護狀態。
再來看看多個客戶端的狀況:
在多個客戶端之間,雖然用了相同的顏色表示goroutine
,但實際上他們都是獨立的goroutine
,能夠想象goroutine
的數量將是驚人的。然而,根本不用擔憂!這樣的應用程序可能真正的線程只有幾個而已。