Go TCP Socket編程

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屬性

原生Socket API提供了豐富的sockopt設置接口,但Golang有本身的網絡架構模型,golang提供的socket options接口也是基於上述模型的必要的屬性設置。包括框架

  • SetKeepAlive
  • SetKeepAlivePeriod
  • SetLinger
  • SetNoDelay (默認no delay)
  • SetWriteBuffer
  • SetReadBuffer

不過上面的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======

相關文章
相關標籤/搜索