Socket是BSD UNIX的進程通訊機制,一般也稱做」套接字」,用於描述IP地址和端口,是一個通訊鏈的句柄。Socket能夠理解爲TCP/IP網絡的API,它定義了許多函數或例程,程序員能夠用它們來開發TCP/IP網絡上的應用程序。電腦上運行的應用程序一般經過」套接字」向網絡發出請求或者應答網絡請求。
Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket後面,對用戶來講只須要調用Socket規定的相關函數,讓Socket去組織符合指定的協議數據而後進行通訊。html
TCP/IP(Transmission Control Protocol/Internet Protocol) 即傳輸控制協議/網間協議,是一種面向鏈接(鏈接導向)的、可靠的、基於字節流的傳輸層(Transport layer)通訊協議,由於是面向鏈接的協議,數據像水流同樣傳輸,會存在黏包問題。程序員
一個TCP服務端能夠同時鏈接不少個客戶端,Go語言中建立多個goroutine實現併發很是方便和高效,因此能夠每創建一次連接就建立一個goroutine去處理。
TCP服務端程序的處理流程:算法
//TCP server端 func process(conn net.Conn) { defer conn.Close() //關閉鏈接 for { reader := bufio.NewReader(conn) var buf [128]byte n,err := reader.Read(buf[:]) //讀取數據 if err != nil{ fmt.Println("鏈接客戶端失敗,錯誤信息:",err) } recvStr := string(buf[:n]) fmt.Println("收到客戶端信息:",recvStr) conn.Write([]byte(recvStr)) //發送數據 } } func main() { listen,err := net.Listen("tcp","127.0.0.1:8888") if err != nil{ fmt.Println("監聽失敗,錯誤:",err) return } for { conn,err := listen.Accept() //創建鏈接 if err!= nil{ fmt.Println("創建鏈接失敗,錯誤:",err) continue } go process(conn) //啓動一個goroutine處理鏈接 } }
一個TCP客戶端進行TCP通訊的流程以下:編程
TCP客戶端:設計模式
//客戶端 func main() { conn ,err := net.Dial("tcp","127.0.0.1:8888") if err != nil { fmt.Println("鏈接失敗,錯誤:",err) return } defer conn.Close() inputReader := bufio.NewReader(os.Stdout) for { input, _ := inputReader.ReadString('\n') //讀取用戶輸入 inputInfo := strings.Trim(input,"\r\n") if strings.ToUpper(inputInfo) == "q"{ return //若是輸入q就退出 } _,err = conn.Write([]byte(inputInfo)) //發送數據 if err != nil{ return } buf := [512]byte{} n,err := conn.Read(buf[:]) if err != nil{ fmt.Println("接受失敗,錯誤:",err) return } fmt.Println(string(buf[:n])) } }
先啓動server,後啓動client:服務器
$go run main.go 我是客戶端 我是客戶端 $go run main.go 收到客戶端信息: 我是客戶端
UDP協議(User Datagram Protocol)中文名稱是用戶數據報協議,是OSI(Open System Interconnection,開放式系統互聯)參考模型中一種無鏈接的傳輸層協議,不須要創建鏈接就能直接進行數據發送和接收,屬於不可靠的、沒有時序的通訊,可是UDP協議的實時性比較好,一般用於視頻直播相關領域。網絡
//服務端 func main() { listen,err := net.ListenUDP("udp",&net.UDPAddr{ IP:net.IPv4(0,0,0,0), Port:8888, }) if err != nil{ fmt.Println("監聽失敗,錯誤:",err) return } defer listen.Close() for { var data [1024]byte n,addr,err := listen.ReadFromUDP(data[:]) if err != nil{ fmt.Println("接收udp數據失敗,錯誤:",err) continue } fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n) _ ,err = listen.WriteToUDP(data[:n],addr) //發送數據 if err != nil{ fmt.Println("發送數據失敗,錯誤:",err) continue } } }
//客戶端 func main() { socket,err := net.DialUDP("udp",nil,&net.UDPAddr{ IP:net.IPv4(0,0,0,0), Port:8888, }) if err != nil{ fmt.Println("鏈接服務器失敗,錯誤:",err) return } defer socket.Close() sendData := []byte("hello world!") _,err = socket.Write(sendData) if err != nil{ fmt.Println("發送數據失敗,錯誤:",err) return } data := make([]byte,4096) n,remoteAddr,err := socket.ReadFromUDP(data) if err != nil{ fmt.Println("接受數據失敗,錯誤:",err) return } fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n) }
先啓動server,後啓動client:併發
$go run main.go recv:hello world! addr:127.0.0.1:8888 count:12 $go run main.go data:hello world! addr:127.0.0.1:51222 count:12
超文本傳輸協議(HTTP,HyperText Transfer Protocol)是互聯網上應用最爲普遍的一種網絡傳輸協議,全部的WWW文件都必須遵照這個標準。設計HTTP最初的目的是爲了提供一種發佈和接收HTML頁面的方法。socket
net/http包是對net包的進一步封裝,專門用來處理HTTP協議的數據。tcp
// http server func sayHi(w http.ResponseWriter,r *http.Request) { fmt.Fprintln(w,"你好,ares!") } func main() { http.HandleFunc("/",sayHi) err := http.ListenAndServe(":8888",nil) if err != nil{ fmt.Println("Http 服務創建失敗,err:",err) return } }

func main() { resp, err := http.Get("https://www.baidu.com/") if err != nil { fmt.Println("get failed, err:", err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) fmt.Printf("%T\n",body) fmt.Println(string(body)) }
執行以後就能在終端輸出www.baidu.com網站首頁的內容了。
//粘包 func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) var buf [1024]byte for { n,err := reader.Read(buf[:]) if err == io.EOF{ break } if err != nil{ fmt.Println("讀取客戶端失敗,err",err) break } recvStr := string(buf[:n]) fmt.Println("收到client發來的數據:",recvStr) } } func main() { listen,err := net.Listen("tcp","127.0.0.1:8888") if err != nil{ fmt.Println("監聽失敗,err",err) return } defer listen.Close() for { conn,err := listen.Accept() if err != nil{ fmt.Println("接受失敗,err",err) continue } go process(conn) } }
func main() { conn,err := net.Dial("tcp","127.0.0.1:8888") if err != nil{ fmt.Println("鏈接失敗,err",err) return } defer conn.Close() for i:=0;i<20;i++{ msg := "Ares is a bird!" conn.Write([]byte(msg)) } }
先啓動服務端再啓動客戶端,能夠看到服務端輸出結果以下:
$go run main.go 收到client發來的數據: Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird! 收到client發來的數據: Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!
客戶端分10次發送的數據,在服務端並無成功的輸出10次,而是多條數據「粘」到了一塊兒。
在socket網絡程序中,TCP和UDP分別是面向鏈接和非面向鏈接的。所以TCP的socket編程,收發兩端(客戶端和服務器端)都要有成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小、數據量小的數據,合併成一個大的數據塊,而後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。
對於UDP,不會使用塊的合併優化算法,這樣,實際上目前認爲,是因爲UDP支持的是一對多的模式,因此接收端的skbuff(套接字緩衝區)採用了鏈式結構來記錄每個到達的UDP包,在每一個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對於接收端來講,就容易進行區分處理了。因此UDP不會出現粘包問題。
1發送端須要等緩衝區滿才發送出去,形成粘包
2接收方不及時接收緩衝區的包,形成多個包接收
具體點:
(1)發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一包數據。若連續幾回發送的數據都不多,一般TCP會根據優化算法把這些數據合成一包後一次發送出去,這樣接收方就收到了粘包數據。
(2)接收方引發的粘包是因爲接收方用戶進程不及時接收數據,從而致使粘包現象。這是由於接收方先把收到的數據放在系統接收緩衝區,用戶進程從該緩衝區取數據,若下一包數據到達時前一包數據還沒有被用戶進程取走,則下一包數據放到系統接收緩衝區時就接到前一包數據以後,而用戶進程根據預先設定的緩衝區大小從系統接收緩衝區取數據,這樣就一次取到了多包數據。
參考:TCP通訊粘包問題分析和解決
出現」粘包」的關鍵在於接收方不肯定將要傳輸的數據包的大小,所以咱們能夠對數據包進行封包和拆包的操做。
自定義一個協議,好比數據包的前4個字節爲包頭,裏面存儲的是發送的數據的長度。
// Encode 將消息編碼 func Encode(message string)([]byte ,error) { // 讀取消息的長度,轉換成int32類型(佔4個字節) var length = int32(len(message)) var pkg = new(bytes.Buffer) //寫入消息頭 err := binary.Write(pkg,binary.LittleEndian,length) if err != nil{ return nil,err } //寫入消息實體 err = binary.Write(pkg,binary.LittleEndian,[]byte(message)) if err != nil{ return nil,err } return pkg.Bytes(),nil } // Decode 消息解碼 func Decode(reader *bufio.Reader)(string,error) { //讀取消息長度 lengthByte,_ := reader.Peek(4) //讀取前4個字節數據 lengthBuff := bytes.NewBuffer(lengthByte) var length int32 err := binary.Read(lengthBuff,binary.LittleEndian,&length) if err != nil{ return "",err } // Buffered返回緩衝中現有的可讀取的字節數。 if int32(reader.Buffered()) < length+4{ return "",err } //讀取真正的消息數據 pack := make([]byte,int(4+length)) _,err = reader.Read(pack) if err != nil{ return "",err } return string(pack[4:]),nil }
server端:
func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { msg,err := proto.Decode(reader) if err == io.EOF{ return } if err != nil{ fmt.Println("decode 失敗,err",err) return } fmt.Println("收到client數據:",msg) } } func main() { listen,err := net.Listen("tcp","127.0.0.1:8888") if err != nil{ fmt.Println("監聽失敗,err",err) return } defer listen.Close() for { conn,err := listen.Accept() if err != nil{ fmt.Println("接受失敗,err",err) continue } go process(conn) } }
client端:
func main() { conn,err := net.Dial("tcp","127.0.0.1:8888") if err != nil{ fmt.Println("dial失敗,err",err) return } defer conn.Close() for i:=0;i<20;i++{ msg := "Hello Ares!" data,err := proto.Encode(msg) if err != nil{ fmt.Println("encode失敗,err",err) return } conn.Write(data) } }
先啓動服務端再啓動客戶端,能夠看到服務端輸出結果以下:
go run main.go 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares! 收到client數據: Hello Ares!