1. 程式人生 > 實用技巧 >12.Go語言-網路程式設計

12.Go語言-網路程式設計

3.網路程式設計

3.1.TCP程式設計

  • server
package main

import (
	"bufio"
	"fmt"
	"net"
)

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("read from client failed, err:",err)
			break
		}
		// 把接收資料轉換string型別
		recvStr := string(buf[:n])
		fmt.Println("收到client端發來的資料:",recvStr)
		write, _ := conn.Write([]byte(recvStr))
		fmt.Println("傳送資料量:",write, " 位元組!")
	}
}

func main() {
	// 監聽套接字
	listen, err := net.Listen("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("listen failed err:",err)
		return
	}
	for {
		// 建立連線
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept faield, err:", err)
			continue
		}
		// 啟動一個goroutine處理連線
		go process(conn)
	}
}
  • client
package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)

func main () {
	conn, err := net.Dial("tcp", "127.0.0.1:20000")
	// 連線發生錯誤,列印錯誤
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	defer conn.Close()
	// 輸入傳入資訊
	inputReader := bufio.NewReader(os.Stdin)
	for {
		fmt.Print("請輸入傳輸資訊:")
		// 讀取輸入資訊
		input, _ := inputReader.ReadString('\n')
		// \r\n 分割
		inputInfo := strings.Trim(input, "\r\n")
		// 如果輸入Q推出
		if strings.ToUpper(inputInfo) == "Q" {
			return
		}
		// 傳輸訊息
		_, err = conn.Write([]byte(inputInfo))
		if err != nil {
			return
		}
		// 接收訊息大小
		buf := [512]byte{}
		// 接收訊息
		n, err := conn.Read(buf[:])
		if err != nil {
			fmt.Println("recv failed err:", err)
			return
		}
		fmt.Println(string(buf[:n]))
	}
}

3.2.UDP程式設計

  • server
package main

import (
	"fmt"
	"net"
)

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
package main

import (
	"fmt"
	"net"
)

func main() {
	// 建立udp連線套接字
	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 world")
	// 傳送資料
	_, 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)
}

3.3TCP粘包

  • server
package main

import (
	"bufio"
	"fmt"
	"io"
	"net"
)

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("read from client failed, err:",err)
			break
		}
		recvStr := string(buf[:n])
		fmt.Println("接收到client發來的資料:", recvStr)
	}
}

func main() {
	listen, err := net.Listen("tcp", "127.0.0.1:30000")
	if err != nil{
		fmt.Println("listen failed, err:",err)
		return
	}
	defer listen.Close()
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept failed,err:",err)
			continue
		}
		go process(conn)
	}
}
  • client
package main

import (
	"fmt"
	"net"
)

func main() {
	conn, err := net.Dial("tcp","127.0.0.1:30000")
	if err != nil{
		fmt.Println("dial failed,err",err)
		return
	}
	defer conn.Close()
	for i:=0;i<20;i++ {
		msg := "hello,hi my name is Tom"
		conn.Write([]byte(msg))
	}
}
  • 結果
 my name is Tomhello,hi my name is Tomhello,hi my name is Tomhello,hi my name is Tomhello,hi my name is Tom
接收到client發來的資料: hello,hi my name is Tomhello,hi my name is Tomhello,hi my name is To my name is Tomhello,hi my name is Tomhello,hi my name is Tomhello,hi my name is Tomhello,hi my name is Tomhello,hi my name is Tomhello,hi my name is Tomhello,hi my name is Tomhello,hi my name is Tom

  • 客戶端分10次傳送資料,但是在服務端沒有成功輸出10次,使多條資料粘到了一起。

3.3.1為什麼會出現粘包

  • 主要原因tcp資料傳輸為流式模式,在保持長連線的時候可以進行多次的收發訊息。

  • 粘包有可能發生在傳送端,也有可能發生在接收端。

     1.由Nagle演算法造成的傳送端的粘包:Nagle演算法是一種改善網路傳輸效率的演算法。簡單來說就是當我們提交一段資料給TCP傳送時,TCP並不立刻傳送此段資料,而是等待一小段時間看看在等待期間是否還有要傳送的資料,若有則會一次把這兩段資料傳送出去。
        2.接收端接收不及時造成的接收端粘包:TCP會把接收到的資料存在自己的緩衝區中,然後通知應用層取資料。當應用層由於某些原因不能及時的把TCP的資料取出來,就會造成TCP緩衝區中存放了幾段資料。
    

3.3.2解決辦法

  • 出現”粘包”的關鍵在於接收方不確定將要傳輸的資料包的大小,因此我們可以對資料包進行封包和拆包的操作。

  • 封包是給一段資料加上包頭,這樣資料包就分為包頭和包體。包頭長度固定,並且它儲存了包體長度。根據包頭長度固定以及包頭重含有包體長度的變數就能正確的拆分出一個完整資料包。

  • 可以自定義一個協議,比如資料包的前4個位元組為包頭,裡面儲存的是傳送的資料的長度

package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
)

func Encode(message string) ([]byte, error){
	// 讀取訊息的長度,轉換成int32型別(佔4個位元組)
	var length = int32(len(message))
	fmt.Printf("length:%v\n",length)// length:11
	var pkg = new(bytes.Buffer)
	//fmt.Println(pkg) //空物件
	// 寫入訊息頭
	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
	}
	fmt.Printf("message:%v\n",pkg)//message:hello world
	fmt.Println(pkg.Bytes())//[11 0 0 0 104 101 108 108 111 32 119 111 114 108 100]
	return pkg.Bytes(), nil
}



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

}

func main (){
	msg := "hello world"
	a,_ := Encode(msg)
	fmt.Printf("type:%T\n",a)
	// data, _:=Decode(a)
	// fmt.Println(data)
}
  • 更改server
package main

import (
	"bufio"
	"fmt"
	"io"
	"mypro/tcp_stick/proto"
	"net"
)

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	// var buf [1024]byte
	for {
		msg, err := proto.Decode(reader)
		// n, err := reader.Read(buf[:])
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("read from client failed, err:",err)
			break
		}
		// recvStr := string(buf[:n])
		fmt.Println("接收到client發來的資料:", msg)
	}
}

func main() {
	listen, err := net.Listen("tcp", "127.0.0.1:30000")
	if err != nil{
		fmt.Println("listen failed, err:",err)
		return
	}
	defer listen.Close()
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept failed,err:",err)
			continue
		}
		go process(conn)
	}
}

  • client
package main

import (
	"fmt"
	"mypro/tcp_stick/proto"
	"net"
)

func main() {
	conn, err := net.Dial("tcp","127.0.0.1:30000")
	if err != nil{
		fmt.Println("dial failed,err",err)
		return
	}
	defer conn.Close()
	for i:=0;i<20;i++ {
		msg := "hello,hi my name is Tom"
		data, err := proto.Encode(msg)
		if err != nil {
			fmt.Println("encode msg failed, err:",err)
		}
		conn.Write(data)
	}
}