1. 程式人生 > 實用技巧 >Go socket程式設計

Go socket程式設計

socket

   socket應該是各種語言中網路程式設計的基礎,它介於應用層與傳輸層之間,只要學會使用它的介面即可。

TCP

   以下建立兩臺機器互相通訊。

Server

   以下是Go語言中通過socketgoroutine編寫的一個非常簡單的服務端。

   流程如下:

   建立與服務端的連結

   進行資料收發

   關閉連結

// 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
}