Socket有兩種:TCP Socket和UDP Socket,TCP和UDP是協議。肯定一個進程須要三元組,IP地址、協議和端口。html
IPv4的地址位數爲32位,地址格式相似這樣:127.0.0.1 172.122.121.111。nginx
IPv6採用128位地址長度,地址格式相似這樣:2002:c0e8:82e7:0:0:0:c0e8:82e7。程序員
IP類型的定義在net
包中。golang
使用下面的語句獲取一個ip類型:web
var ip net.IP = net.ParseIP("127.0.0.1")
net.TCPConn可做爲客戶端和服務器端交互的通道,利用它的Read()和Write()能夠讀寫數據。算法
net.TCPAddr表示一個TCP的地址信息:瀏覽器
type TCPAddr struct { IP IP Port int }
得到TCPAddr:安全
func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
Go語言中經過net
包中的DialTCP
函數來創建一個TCP鏈接,並返回一個TCPConn
類型的對象,當鏈接創建時服務器端也建立一個同類型的對象,此時客戶端和服務器段經過各自擁有的TCPConn對象來進行數據交換。通常而言,客戶端經過TCPConn對象將請求信息發送到服務器端,讀取服務器端響應的信息。服務器端讀取並解析來自客戶端的請求,並返回應答信息,這個鏈接只有當任一端關閉了鏈接以後才失效,否則這鏈接能夠一直在使用。創建鏈接的函數定義以下:服務器
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
net參數是"tcp4"、"tcp6"、"tcp"中的任意一個,分別表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一個)websocket
laddr表示本機地址,通常設置爲nil
示例:
package main import ( "fmt" "io/ioutil" "net" "os" ) func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0]) os.Exit(1) } service := os.Args[1] tcpAddr, err := net.ResolveTCPAddr("tcp4", service) checkError(err) conn, err := net.DialTCP("tcp", nil, tcpAddr) checkError(err) _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")) checkError(err) result, err := ioutil.ReadAll(conn) checkError(err) fmt.Println(string(result)) os.Exit(0) } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } }
從conn中讀取數據時最好不要直接用 conn.Read(),建議用:
reader := bufio.NewReader(sender.tcpConn) respStr, err := reader.ReadString('\n')
在服務器端咱們須要綁定服務到指定的非激活端口,並監聽此端口,當有客戶端請求到達的時候能夠接收到來自客戶端鏈接的請求。net包中有相應功能的函數,函數定義以下:
func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error) func (l *TCPListener) Accept() (c Conn, err os.Error)
package main import ( "fmt" "net" "os" "time" ) func main() { service := ":1200" tcpAddr, err := net.ResolveTCPAddr("tcp4", service) checkError(err) listener, err := net.ListenTCP("tcp", tcpAddr) checkError(err) for { conn, err := listener.Accept() if err != nil { continue } go handleClient(conn) } } func handleClient(conn net.Conn) { defer conn.Close() daytime := time.Now().String() conn.Write([]byte(daytime)) // don't care about return value // we're finished with this client } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } }
這裏只寫了handleClient(),其餘代碼參考上面
func handleClient(conn net.Conn) { conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout request := make([]byte, 128) // set maxium request length to 128KB to prevent flood attack defer conn.Close() // close connection before exit for { read_len, err := conn.Read(request) if err != nil { fmt.Println(err) break } if read_len == 0 { break // connection already closed by client } else if string(request) == "timestamp" { daytime := strconv.FormatInt(time.Now().Unix(), 10) conn.Write([]byte(daytime)) } else { daytime := time.Now().String() conn.Write([]byte(daytime)) } request = make([]byte, 128) // clear last read content } }
conn.SetReadDeadline()設置了超時,當必定時間內客戶端無請求發送,conn便會自動關閉,下面的for循環即會由於鏈接已關閉而跳出。須要注意的是,request在建立時須要指定一個最大長度以防止flood attack;每次讀取到請求處理完畢後,須要清理request,由於conn.Read()會將新讀取到的內容append到原內容以後。
TCP有不少鏈接控制函數,咱們日常用到比較多的有以下幾個函數:
func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)
設置創建鏈接的超時時間,客戶端和服務器端都適用,當超過設置時間時,鏈接自動關閉。
func (c *TCPConn) SetReadDeadline(t time.Time) error
func (c *TCPConn) SetWriteDeadline(t time.Time) error
用來設置寫入/讀取一個鏈接的超時時間。當超過設置時間時,鏈接自動關閉。
func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error
設置客戶端是否和服務器端保持長鏈接,能夠下降創建TCP鏈接時的握手開銷,對於一些須要頻繁交換數據的應用場景比較適用。
tcp設置超時:
conn.SetDeadline(time.Now().Add(timeout)) defer func() { var zero time.Time conn.SetDeadline(zero) // 重置 }()
tcp超時檢測:
if neterr, ok := err.(net.Error); ok && neterr.Timeout()
測試連接是否能連通:
if _, err := sender.tcpConn.Read(empty); err == io.EOF { fmt.Println("%s closed, reconnection now.") sender.Close() sender.Init() }
import ( "bytes" "fmt" "io" "net" "os" ) func Tcptest() { service := "www.csdn.net:80" // 後面要跟端口號 conn, err := net.Dial("tcp", service) checkError2(err) _, err = conn.Write([]byte("HEAR / HTTP/1.0\r\n\r\n")) checkError2(err) result, err := readFully2(conn) checkError2(err) fmt.Println(string(result)) os.Exit(0) } func checkError2(err error) { if err != nil { fmt.Fprint(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } } func readFully2(conn net.Conn) ([]byte, error) { defer conn.Close() result := bytes.NewBuffer(nil) var buf [512]byte for { n, err := conn.Read(buf[0:]) result.Write(buf[0:n]) if err != nil { if err == io.EOF { break } return nil, err } } return result.Bytes(), nil }
結果示例:
HTTP/1.1 404 Not Found Server: ngx_openresty Date: Sat, 20 Sep 2014 07:49:31 GMT Content-Type: text/html; charset=utf-8 Content-Length: 162 Connection: close <html> <head><title>404 Not Found</title></head> <body bgcolor="white"> \<center><h1>404 Not Found</h1></center> \<hr><center>nginx</center> </body> </html>
上面的兩個斜槓只是爲了能夠在頁面上顯示。
Go語言包中處理UDP Socket和TCP Socket不一樣的地方就是在服務器端處理多個客戶端請求數據包的方式不一樣,UDP缺乏了對客戶端鏈接請求的Accept函數。其餘基本幾乎如出一轍,只有TCP換成了UDP而已。UDP的幾個主要函數以下所示:
func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error) func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error) func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error) func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error)
一個UDP的客戶端代碼以下所示,咱們能夠看到不一樣的就是TCP換成了UDP而已:
package main import ( "fmt" "net" "os" ) func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0]) os.Exit(1) } service := os.Args[1] udpAddr, err := net.ResolveUDPAddr("udp4", service) checkError(err) conn, err := net.DialUDP("udp", nil, udpAddr) checkError(err) _, err = conn.Write([]byte("anything")) checkError(err) var buf [512]byte n, err := conn.Read(buf[0:]) checkError(err) fmt.Println(string(buf[0:n])) os.Exit(0) } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error ", err.Error()) os.Exit(1) } }
咱們來看一下UDP服務器端如何來處理:
package main import ( "fmt" "net" "os" "time" ) func main() { service := ":1200" udpAddr, err := net.ResolveUDPAddr("udp4", service) checkError(err) conn, err := net.ListenUDP("udp", udpAddr) checkError(err) for { handleClient(conn) } } func handleClient(conn *net.UDPConn) { var buf [512]byte _, addr, err := conn.ReadFromUDP(buf[0:]) if err != nil { return } daytime := time.Now().String() conn.WriteToUDP([]byte(daytime), addr) } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error ", err.Error()) os.Exit(1) } }
Dial()函數的原型以下:
func Dial(net, addr string) (Conn, error)
其中net參數是網絡協議的名字,addr參數是IP地址或域名,而端口號以「:」的形式跟隨在地址或域名的後面,端口號可選。若是鏈接成功,返回鏈接對象,不然返回error。 咱們來看一下幾種常見協議的調用方式。 TCP連接:
conn, err := net.Dial("tcp", "192.168.0.10:2100")
UDP連接:
conn, err := net.Dial("udp", "192.168.0.12:975")
ICMP連接(使用協議名稱):
conn, err := net.Dial("ip4:icmp", "www.baidu.com")
ICMP連接(使用協議編號):
conn, err := net.Dial("ip4:1", "10.0.0.3")
目前,Dial()函數支持以下幾種網絡協議:"tcp"、"tcp4"(僅限IPv4)、"tcp6"(僅限IPv6)、"udp"、"udp4"(僅限IPv4)、"udp6"(僅限IPv6)、"ip"、"ip4"(僅限IPv4)和"ip6" (僅限IPv6)。
在成功創建鏈接後,咱們就能夠進行數據的發送和接收。發送數據時,使用conn的Write()成員方法,接收數據時使用Read()方法
WebSocket採用了一些特殊的報頭,使得瀏覽器和服務器只須要作一個握手的動做,就能夠在瀏覽器和服務器之間創建一條鏈接通道。且此鏈接會保持在活動狀態,你可使用JavaScript來向鏈接寫入或從中接收數據,就像在使用一個常規的TCP Socket同樣。它解決了Web實時化的問題,相比傳統HTTP有以下好處:
WebSocket URL的起始輸入是ws://或是wss://(在SSL上)。
WebSocket的協議頗爲簡單,在第一次handshake經過之後,鏈接便創建成功,其後的通信數據都是以」\x00″開頭,以」\xFF」結尾。在客戶端,這個是透明的,WebSocket組件會自動將原始數據「掐頭去尾」。
瀏覽器發出WebSocket鏈接請求,而後服務器發出迴應,而後鏈接創建成功,這個過程一般稱爲「握手」 (handshaking)。請看下面的請求和反饋信息:
在請求中的"Sec-WebSocket-Key"是隨機的,對於成天跟編碼打交到的程序員,一眼就能夠看出來:這個是一個通過base64編碼後的數據。服務器端接收到這個請求以後須要把這個字符串鏈接上一個固定的字符串:
258EAFA5-E914-47DA-95CA-C5AB0DC85B11
即:f7cb4ezEAl6C3wRaU6JORA==
鏈接上那一串固定字符串,生成一個這樣的字符串:
f7cb4ezEAl6C3wRaU6JORA==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
對該字符串先用 sha1安全散列算法計算出二進制的值,而後用base64對其進行編碼,便可以獲得握手後的字符串:
rE91AJhfC+6JdVcVXOGJEADEJdQ=
將之做爲響應頭Sec-WebSocket-Accept的值反饋給客戶端。
Go語言標準包裏面沒有提供對WebSocket的支持,可是在由官方維護的go.net子包中有對這個的支持,你能夠經過以下的命令獲取該包:
go get code.google.com/p/go.net/websocket
實際上,Dial()函數是對DialTCP()、DialUDP()、DialIP()和DialUnix()的封裝。咱們也能夠直接調用這些函數,它們的功能是一致的。這些函數的原型以下:
func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err error) func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err error) func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error) func DialUnix(net string, laddr, raddr *UnixAddr) (c *UnixConn, err error)
上面TCP示例也可使用下面的代碼來實現:
import ( "fmt" "io/ioutil" "net" ) service := "www.csdn.net:80" tcpAddr, err := net.ResolveTCPAddr("tcp4", service) // 解析地址和端口 checkError2(err) conn, err := net.DialTCP("tcp", nil, tcpAddr) // 創建鏈接,參2表示本地地址,參3表示遠程地址 checkError2(err) _, err = conn.Write([]byte("HEAR / HTTP/1.0\r\n\r\n")) // 寫數據 checkError2(err) result, err := ioutil.ReadAll(conn) // 調用工具類,用Buffer來讀 checkError2(err) fmt.Println(string(result))
import ( "fmt" "net" ) func Othertest_main() { ip := net.ParseIP("127.0.0.1") fmt.Println(ip.String()) // 127.0.0.1 ip = net.ParseIP("123") fmt.Println(ip) // <nil> }
mask := net.IPv4Mask(255, 255, 0, 0) fmt.Println(mask) // ffff0000
ip := net.ParseIP("127.0.0.1") mask := ip.DefaultMask() fmt.Println(mask) // ff000000
addr := "www.baidu.com" ipaddr, err := net.ResolveIPAddr("ip", addr) if err != nil { fmt.Println(err) } else { fmt.Println(ipaddr) // 61.135.169.121 } addrs, err := net.LookupHost("www.baidu.com") if err != nil { fmt.Println(err) return } for i, v := range addrs { fmt.Println(i, "=>", v) } //0 => 61.135.169.121 //1 => 61.135.169.125
conn, err := net.Dial("udp", "baidu.com:80") if err != nil { fmt.Println(err) return "" } defer conn.Close() ipPort := conn.LocalAddr().String() return strings.Split(ipPort, ":")[0]