zoukankan      html  css  js  c++  java
  • Go网络编程TCP

    1. 服务端

    package main
    
    import (
    	"bufio"
    	"fmt"
    	"net"
    	"os"
    	"strings"
    )
    
    // tcp server端
    func processConn(conn net.Conn) {
    	defer conn.Close()
    	// 3. 与客户端通信
    	var tmp [128]byte
    	reader := bufio.NewReader(os.Stdin)
    	for {
    		n, err := conn.Read(tmp[:])
    		if err != nil {
    			fmt.Println("read from conn failed,err:", err)
    			return
    		}
    		fmt.Println(string(tmp[:n]))
    		fmt.Print("请回复:")
    		msg, _ := reader.ReadString('
    ') // 读到换行
    		msg = strings.TrimSpace(msg)
    		if msg == "exit" {
    			break
    		}
    		conn.Write([]byte(msg))
    	}
    }
    
    func main() {
    	// 1. 本地端口启动服务
    	listener, err := net.Listen("tcp", "127.0.0.1:20000")
    	if err != nil {
    		fmt.Println("start tcp server on 127.0.0.1:20000 failed, err:", err)
    		return
    	}
    	// 2. 等待别人来跟我建立连接
    	for {
    		conn, err := listener.Accept()
    		if err != nil {
    			fmt.Println("accept failed,err:", err)
    			return
    		}
    		go processConn(conn)
    	}
    
    }
    

    2. 客户端 

    package main
    
    import (
       "bufio"
       "fmt"
       "net"
       "os"
       "strings"
    )
    
    // tcp client
    
    func main() {
       // 1. 与server端建立连接
       conn, err := net.Dial("tcp", "127.0.0.1:20000")
       if err != nil {
          fmt.Println("dial 127.0.0.1:20000 failed,err:", err)
          return
       }
       // 2. 发送数据
       reader := bufio.NewReader(os.Stdin)
       for {
          fmt.Print("请说话:")
          msg, _ := reader.ReadString('
    ') // 读到换行
          msg = strings.TrimSpace(msg)
          if msg == "exit" {
             break
          }
          conn.Write([]byte(msg))
       }
       conn.Close()
    }
    

     3. 黏包

    为什么会出现粘包

    主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。

    “粘包”可发生在发送端也可发生在接收端:

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

    解决办法

    出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。

    封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。

    我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。

    package main
    
    import (
    	"bufio"
    	"fmt"
    	"io"
    	"net"
    
    	proto "code.oldboyedu.com/day08/09nianbao_jiejue/protocol"
    )
    
    // socket_stick/server/main.go
    
    func process(conn net.Conn) {
    	defer conn.Close()
    	reader := bufio.NewReader(conn)
    	for {
    		recvStr, err := proto.Decode(reader)
    		if err == io.EOF {
    			return
    		}
    		if err != nil {
    			fmt.Println("decode failed,err:", err)
    			return
    		}
    		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)
    	}
    }
    
    package main
    
    import (
    	"fmt"
    	"net"
    
    	proto "code.oldboyedu.com/day08/09nianbao_jiejue/protocol"
    )
    
    // 黏包 client
    
    // socket_stick/client/main.go
    
    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, Hello. How are you?`
    		// 调用协议编码数据
    		b, err := proto.Encode(msg)
    		if err != nil {
    			fmt.Println("encode failed,err:", err)
    			return
    		}
    		conn.Write(b)
    		// time.Sleep(time.Second)
    	}
    }
    
    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) //Buffer缓冲区
    	// 写入消息头
    	err := binary.Write(pkg, binary.LittleEndian, length)
    	if err != nil {
    		return nil, err
    	}
    	// 写入消息实体
    	err = binary.Write(pkg, binary.LittleEndian, []byte(message)) //binary.LittleEndian小段(低位在最右边)
    	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
    }
    
  • 相关阅读:
    Java基础-3y
    对线面试官面试系列-3y
    从零单排学Redis【青铜】
    mock官方文档
    route路由组件传递参数
    axios拦截器与vue代理设置
    Sass使用方法
    Less使用方法
    Vue-cli
    Vue-组件注册
  • 原文地址:https://www.cnblogs.com/yzg-14/p/12285155.html
Copyright © 2011-2022 走看看