Go language uses tcp to establish client and server

Go language implements TCP communication

TCP/IP (Transmission Control Protocol/Internet Protocol), that is, Transmission Control Protocol/Internet Protocol, is a connection-oriented (connection-oriented), reliable, byte-stream-based transport layer (Transport layer) communication protocol, because it is Connection-oriented protocol, data is transmitted like a water flow, and there will be sticky packets.

TCP server

A TCP server can connect to many clients at the same time. For example, users from all over the world use their browsers on their computers to visit Taobao.com. Because it is very convenient and efficient to create multiple goroutines in the Go language to achieve concurrency, we can create a goroutine to process each time a link is established.

The processing flow of the TCP server program:

The listening port
receives client requests to establish connections
and creates goroutines to handle connections.

// tcp/server/main.go

// TCP server端

// 处理函数
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
		}
		recvStr := string(buf[:n])
		fmt.Println("收到client端发来的数据:", recvStr)
		conn.Write([]byte(recvStr)) // 发送数据
	}
}

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 failed, err:", err)
			continue
		}
		go process(conn) // 启动一个goroutine处理连接
	}
}

TCP client

The flow of a TCP client for TCP communication is as follows:

Establish a link with the server Send
and receive data
Close the link

// tcp/client/main.go

// 客户端
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 {
    
    
		input, _ := inputReader.ReadString('\n') // 读取用户输入
		inputInfo := strings.Trim(input, "\r\n")
		if strings.ToUpper(inputInfo) == "Q" {
    
     // 如果输入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]))
	}
}

TCP sticky packet

Sticky package example
The server code is as follows:

// socket_stick/server/main.go

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)
	}
}
客户端代码如下:

// 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?`
		conn.Write([]byte(msg))
	}
}

After saving the above code, compile it separately. First start the server and then start the client, you can see the output of the server as follows:

Received data from client: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
Received Data sent by client: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
Receive data from client: Hello, Hello. How are you?Hello, Hello. How are you?
Receive data from client Data: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
Receive data from client: Hello, Hello. How are you?Hello, Hello. How are you?
The data sent by the client in 10 times was not successfully output 10 times on the server, but multiple pieces of data were "glued" together.

Why sticky packets appear?
The main reason is that the tcp data transfer mode is a stream mode, and multiple receiving and sending can be performed while maintaining a long connection.

"Stick package" can occur at the sending end or at the receiving end:

Sticky packets at the sender caused by the Nagle algorithm: The Nagle algorithm is an algorithm that improves network transmission efficiency. To put it simply, when we submit a piece of data to TCP to send, TCP does not send this piece of data immediately, but waits for a short period of time to see if there is any data to be sent during the waiting period. Segment data is sent out.
Sticky packets at the receiving end caused by untimely receiving at the receiving end: TCP will store the received data in its own buffer, and then notify the application layer to fetch the data. When the application layer cannot take out the TCP data in time due to some reasons, several pieces of data will be stored in the TCP buffer.
Solution
The key to the occurrence of "sticky packets" is that the receiver does not know the size of the data packets to be transmitted, so we can package and unpack the data packets.

Encapsulation : Encapsulation is to add a header to a piece of data, so that the data packet is divided into two parts: the header and the body (the "end of the packet" content will be added to the packet when filtering illegal packets). The length of the header part is fixed, and it stores the length of the packet body. According to the fixed length of the header and the variable containing the length of the packet body in the header, a complete data packet can be correctly split.

We can define a protocol by ourselves. For example, the first 4 bytes of the data packet are the header, which stores the length of the sent data.

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

Next, use the Decode and Encode functions of the proto package defined above to process the data on the server and client respectively.

The server code is as follows:

// socket_stick/server2/main.go

func process(conn net.Conn) {
    
    
	defer conn.Close()
	reader := bufio.NewReader(conn)
	for {
    
    
		msg, err := proto.Decode(reader)
		if err == io.EOF {
    
    
			return
		}
		if err != nil {
    
    
			fmt.Println("decode msg failed, err:", err)
			return
		}
		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)
	}
}

The client code is as follows:

// socket_stick/client2/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?`
		data, err := proto.Encode(msg)
		if err != nil {
    
    
			fmt.Println("encode msg failed, err:", err)
			return
		}
		conn.Write(data)
	}
}

Go language implements UDP communication

UDP protocol The
Chinese name of UDP protocol (User Datagram Protocol) is User Datagram Protocol, which is a connectionless transport layer protocol in the OSI (Open System Interconnection, Open System Interconnection) reference model, which can directly transmit data without establishing a connection. Sending and receiving are unreliable and out-of-sequence communications, but the UDP protocol has better real-time performance and is usually used in fields related to live video.

The UDP server
code implemented using the net package of the Go language is as follows:

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

UDP client The
UDP client code implemented using the net package of the Go language is as follows:

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

Guess you like

Origin blog.csdn.net/ydl1128/article/details/126178604