Go实现TCP、UPD连接及粘包问题解决

UDP连接

//服务端
func main() {
	udpConn, err := net.ListenUDP("udp", &net.UDPAddr{
		IP: net.IPv4(127, 0, 0, 1),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("udp listen failed err:", err)
		return
	}
	defer udpConn.Close()
	var tmp [1024]byte
	for {
		n, udpAddr, err := udpConn.ReadFromUDP(tmp[:]) //接收数据
		if err != nil {
			fmt.Println("readform udp failed err:", err)
			return
		}
		fmt.Printf("data:%v;addr:%v;count:%d", string(tmp[:n]), udpAddr, n)
		_, err = udpConn.WriteToUDP(tmp[:n], udpAddr) //发送数据
		if err != nil {
			fmt.Println("write udp failed err:", err)
			return
		}
	}

}

//客户端
func main() {
	udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP: net.IPv4(127, 0, 0, 1),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("udp conn failed err:", err)
		return
	}
	defer udpConn.Close() //写在错误判断的后面
	var tmp = []byte("hello")
	_, err = udpConn.Write(tmp) //写发送信息
	if err != nil {
		fmt.Println("udp write failed err:", err)
		return
	}
	var data [1024]byte
	n, addr, err := udpConn.ReadFromUDP(data[:]) //读取服务端返回的信息
	if err != nil {
		fmt.Println("udp read from server failed err:", err)
		return
	}
	fmt.Printf("data:%v;addr:%v;count:%d", string(tmp[:n]), addr, n)

}

TCP连接

//tcp服务端
//1.本地端口启动服务
//2.等待别人请求连接
//3.与客户端通信(为每一个客户端开辟一个独立的并发与其IO)
func main() {
	//1.
	listener, err := net.Listen("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("Listen failed err:", err)
		return
	}

	for {
		//2.
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("accept failed err:", err)
			return
		}
		//3.
		go processConn(conn)
	}

}

func processConn(conn net.Conn) {
	//3.
	var tmp [128]byte
	reader := bufio.NewReader(os.Stdin) //获取终端输入
	for {
		n, err := conn.Read(tmp[:])
		if err != nil {
			fmt.Println("read msg failed err:", err)
			return
		}
		fmt.Println("收到消息:",string(tmp[:n]))
		fmt.Print("请回复:")
		msg, err := reader.ReadString('\n')
		_, err = conn.Write([]byte(msg))
		if err != nil {
			fmt.Println("conn write to client failed,err:", err)
			return
		}
	}
}

//tcp客户端
//1.与server端建立连接
//2.发送信息
func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("dial failed err :", err)
		return
	}
	var tmp [128]byte
	reader := bufio.NewReader(os.Stdin) //获取终端输入 os.Args 也可以获取终端输入
	for {
		fmt.Print("请写入发送内容:")
		msg, err := reader.ReadString('\n')
		if msg == "exit" {
			break
		}
		msg = strings.TrimSpace(msg)
		_, err = conn.Write([]byte(msg))
		n, _ := conn.Read(tmp[:])
		fmt.Print("收到消息:",string(tmp[:n]))
		if err != nil {
			fmt.Println("conn write to server failed,err:", err)
			return
		}
	}
	conn.Close()
}

TCP粘包问题

原因:
tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。
“粘包”可发生在发送端也可发生在接收端:
由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来
说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段
时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后
通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP
缓冲区中存放了几段数据。

解决:
对数据包进行封包和拆包的操作。即给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了;包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包

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

猜你喜欢

转载自blog.csdn.net/wzb_wzt/article/details/107450265