Go socket程式設計
阿新 • • 發佈:2020-10-14
socket
socket
應該是各種語言中網路程式設計的基礎,它介於應用層與傳輸層之間,只要學會使用它的介面即可。
TCP
以下建立兩臺機器互相通訊。
Server
以下是Go
語言中通過socket
和goroutine
編寫的一個非常簡單的服務端。
流程如下:
建立與服務端的連結
進行資料收發
關閉連結
// D:\GoLeran\src\yunya.com\TCPServer> package main import ( "bufio" "fmt" "net" "strings" ) func main(){ listen, err := net.Listen("tcp", "127.0.0.1:9999") if err != nil { fmt.Println("listent failed, err:", err) return } for { conn, err := listen.Accept() // 建立連結 if err != nil { fmt.Println("accept failed, err:", err) // 三次握手失敗 continue } go process(conn) // 啟動多個goroutine來處理回覆 } } // 處理請求 func process(conn net.Conn) { defer conn.Close() // 關閉連結通道 for { reader := bufio.NewReader(conn) var buf [1024]byte n, err := reader.Read(buf[:]) // 讀取資料 讀取的位元組數,錯誤資訊 if err != nil { fmt.Print("read form client failed, err:", err) break } recvStr := string(buf[:n]) fmt.Println("client message:", recvStr) var inputMsg string fmt.Println("請輸入你要傳送的資訊:") fmt.Scanln(&inputMsg) inputMsg = strings.Trim(inputMsg, "\r\n") // 去除空行等,防止阻塞 conn.Write([]byte(inputMsg)) } }
Client
以下是客戶端的程式碼。
建立與服務端的連結
進行資料收發
關閉連結
D:\GoLeran\src\yunya.com\TCPClient> package main import ( "fmt" "net" "strings" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:9999") // 繫結服務端地址 if err != nil { fmt.Println("err:", err) return } defer conn.Close() // 關閉雙向連結 for { var inputMsg string fmt.Println("請輸入你要傳送的資訊:") fmt.Scanln(&inputMsg) inputMsg = strings.Trim(inputMsg, "\r\n") // 去除空行等,防止阻塞 if strings.ToUpper(inputMsg) == "quit" { return } _, err = conn.Write([]byte(inputMsg)) // 傳送資料 if err != nil { return } buf := [512]byte{} serverMsg, err := conn.Read(buf[:]) // 服務端返回的資訊 if err != nil { fmt.Println("recv failed err:", err) return } fmt.Println("server message:", string(buf[:serverMsg])) } }
UDP
Server
UDP
不用建立雙向連結,訊息不可靠。因此一般來說使用較少。
// UDP/server/main.go // UDP server端 func main() { listen, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 30000, }) if err != nil { fmt.Println("listen failed, err:", err) return } defer listen.Close() for { var data [1024]byte n, addr, err := listen.ReadFromUDP(data[:]) // 接收資料 if err != nil { fmt.Println("read udp failed, err:", 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("write to udp failed, err:", err) continue } } }
Client
客戶端程式碼如下:
// UDP 客戶端
func main() {
socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 30000,
})
if err != nil {
fmt.Println("連線服務端失敗,err:", err)
return
}
defer socket.Close()
sendData := []byte("Hello server")
_, err = socket.Write(sendData) // 傳送資料
if err != nil {
fmt.Println("傳送資料失敗,err:", err)
return
}
data := make([]byte, 4096)
n, remoteAddr, err := socket.ReadFromUDP(data) // 接收資料
if err != nil {
fmt.Println("接收資料失敗,err:", err)
return
}
fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}
TCP粘包
解決方案
由於TCP
是流式傳輸協議。所以可能會產生粘包現象,我們需要劃分每次資料的大小邊界,所以可以自定製一個收發訊息的協議。如下:
// socket_stick/proto/proto.go
package proto
import (
"bufio"
"bytes"
"encoding/binary"
)
// 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
}