Go TCP Socket編程linux
從tcp socket誕生後,網絡編程架構模型也幾經演化,大體是:「每進程一個鏈接」 –> 「每線程一個鏈接」 –> 「Non-Block + I/O多路複用(linux epoll/windows iocp/freebsd darwin kqueue/solaris Event Port)」。伴隨着模型的演化,服務程序越發強大,能夠支持更多的鏈接,得到更好的處理性能。golang
目前主流web server通常均採用的都是」Non-Block + I/O多路複用」(有的也結合了多線程、多進程)。不過I/O多路複用也給使用者帶來了不小的複雜度,以致於後續出現了許多高性能的I/O多路複用框架, 好比libevent、libev、libuv等,以幫助開發者簡化開發複雜性,下降心智負擔。不過Go的設計者彷佛認爲I/O多路複用的這種經過回調機制割裂控制流的方式依舊複雜,且有悖於「通常邏輯」設計,爲此Go語言將該「複雜性」隱藏在Runtime中了:Go開發者無需關注socket是不是 non-block的,也無需親自注冊文件描述符的回調,只需在每一個鏈接對應的goroutine中以「block I/O」的方式對待socket處理便可,這能夠說大大下降了開發人員的心智負擔。web
一個典型的server端和client端程序以下,編程
server.gowindows
package main import ( "net" "fmt" "log" ) func handleConnection(conn net.Conn) { defer conn.Close() for { // read from the connection var buf = make([]byte, 10) log.Println("start to read from conn") n, err := conn.Read(buf) if err != nil { log.Println("conn read error:", err) return } log.Printf("read %d bytes, content is %s\n", n, string(buf[:n])) } } func main() { ss, err := net.Listen("tcp", ":8888") if err != nil { fmt.Println("listen error:", err) return } for { conn, err := ss.Accept() if err != nil { fmt.Println("accept error:", err) break } go handleConnection(conn) } }
client.go網絡
package main import ( "time" "net" "log" ) func main() { log.Println("begin dial...") conn, err := net.Dial("tcp", ":8888") if err != nil { log.Println("dial error:", err) return } defer conn.Close() log.Println("dial ok") conn.Write([]byte("hello world")) time.Sleep(time.Second * 10000) }
程序運行結果,多線程
2016/11/03 16:54:05 start to read from conn 2016/11/03 16:54:05 read 10 bytes, content is hello worl 2016/11/03 16:54:05 start to read from conn 2016/11/03 16:54:05 read 1 bytes, content is d 2016/11/03 16:54:05 start to read from conn
用戶層眼中看到的goroutine中的「block socket」,其實是經過Go runtime中的netpoller經過Non-block socket + I/O多路複用機制「模擬」出來的,真實的underlying socket其實是non-block的,只是runtime攔截了底層socket系統調用的錯誤碼,並經過netpoller和goroutine 調度讓goroutine「阻塞」在用戶層獲得的Socket fd上。好比:當用戶層針對某個socket fd發起read操做時,若是該socket fd中尚無數據,那麼runtime會將該socket fd加入到netpoller中監聽,同時對應的goroutine被掛起,直到runtime收到socket fd 數據ready的通知,runtime纔會從新喚醒等待在該socket fd上準備read的那個Goroutine。而這個過程從Goroutine的視角來看,就像是read操做一直block在那個socket fd上似的。架構
原生Socket API提供了豐富的sockopt設置接口,但Golang有本身的網絡架構模型,golang提供的socket options接口也是基於上述模型的必要的屬性設置。包括框架
不過上面的Method是TCPConn的,而不是Conn的,要使用上面的Method,須要type assertion:socket
tcpConn, ok := c.(*TCPConn) if !ok { //error handle } tcpConn.SetNoDelay(true)
對於listener socket, golang默認採用了 SO_REUSEADDR,這樣當你重啓 listener 程序時,不會由於address in use的錯誤而啓動失敗。而listen backlog的默認值是經過獲取系統的設置值獲得的。不一樣系統不一樣:mac 128, linux 512等。
======END======