Understand Go language network programming [tcp, udp]

Posted by Clandestinex337 on Wed, 19 Jan 2022 08:39:47 +0100

preface

This article is not about http Net package, which only introduces the traditional network communication (the http.net package will be updated separately later)

1, Hierarchy of the Internet

It is roughly divided into four layers. If subdivided, it can be divided into seven layers

1. Application layer

Application layer, presentation layer and session layer

2. Transport layer

Transport layer

3. Network layer

network layer

4. Network interface layer

Data link layer, physical layer

5. Illustration

	among socket The first floor is for us,We can implement some functions in this layer to meet the needs of users

2, tcp protocol overview

1. Three handshakes

First handshake

Client sends a TCP of SYN The packet at flag position 1 indicates the port of the server to which the customer intends to connect,
And initial serial number X,Serial number saved in the header(Sequence Number)Field.

The Second Handshake

The server sends back a confirmation packet(ACK)answer. Namely SYN Flag bit sum ACK All flag bits are 1
 When, the serial number will be confirmed(Acknowledgement Number)Set as customer's I S N Add 1 to.Namely X+1. 

Third handshake

The client sends the confirmation packet again(ACK) SYN The flag bit is 0,ACK The flag bit is 1.And put the clothes
 From the server ACK Serial number field for+1,Put it in the OK field and send it to the other party.And write in the data segment ISN of+1

2. What is TCP usually used for?

  • Due to the precise transmission of tcp packets, it can be used to transmit important data such as files, pictures and so on
  • tcp has high transmission accuracy but low efficiency. It is not easy to transmit data with large amount and low importance

3. The code realizes tcp communication

The client code is as follows:

package main

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

// tcp/client/main.go

// receive data 
func getMsg(conn net.Conn) {
	for {
		buf := [512]byte{}
		// n is the obtained data length
		n, err := conn.Read(buf[:])
		if err != nil {
			fmt.Println("recv failed, err:", err)
			return
		}
		fmt.Println(string(buf[:n]))
	}

}

// send data
func sendMsg(conn net.Conn) {
	for {
		// Create a buffer reader
		inputReader := bufio.NewReader(os.Stdin)
		// Receive user input with \ n as Terminator
		input, _ := inputReader.ReadString('\n') // Read user input
		// Remove the acquired \ n
		inputInfo := strings.Trim(input, "\r\n")

		// Judge whether the exit conditions are met
		if strings.ToUpper(inputInfo) == "Q" {
			// Send exit request to server
			_, _ = conn.Write([]byte("q"))
			return
		}
		// Write data to the server (data is transmitted in bytes between the two ends)
		_, err := conn.Write([]byte(inputInfo))
		if err != nil {
			return
		}
	}
}

// client
func main() {
	// Use tcp to connect 127.0.0.1:20000
	conn, err := net.Dial("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("err :", err)
		return
	}
	// Close the connection when the function reaches the end
	defer conn.Close()
	go getMsg(conn)
	go sendMsg(conn)
	time.Sleep(time.Hour)
}

The server code is as follows:

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)
// TCP server side

func sendMsg(conn net.Conn) {
	for {
		inputer := bufio.NewReader(os.Stdin)
		input, _ := inputer.ReadString('\n')
		input = strings.Trim(input, "\r\n")
		conn.Write([]byte(input))
	}

}

// Receive processing function
func process(conn net.Conn) {
	fmt.Println("line:", conn, "Connection succeeded!")
	defer conn.Close() // Close connection
	for {
		reader := bufio.NewReader(conn)
		var buf [128]byte
		n, err := reader.Read(buf[:]) // Read data
		if err != nil {
			fmt.Println("read from client failed, err:", err)
			break
		}
		recvStr := string(buf[:n])
		if recvStr == "q" {
			fmt.Println(conn, "Disconnected!")
			return
		}
		fmt.Println(conn, ":", recvStr)
		fmt.Printf("Please enter:")
	}
}

func main() {
	// Bind 127.0.0.1:20000 of the gateway for data transmission in the form of tcp
	listen, err := net.Listen("tcp", "127.0.0.1:20000")

	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	for {
		// Waiting for client to connect
		conn, err := listen.Accept() // Establish connection
		if err != nil {
			fmt.Println("accept failed, err:", err)
			continue
		}
		// Add the accepted connection to the daemon
		go process(conn)
		go sendMsg(conn)
	}
}

3, udp protocol overview

1. What is UDP usually used for?

  • udp has high transmission efficiency, but it is easy to lose data
  • It can be used for webcast and video call
  • Keep important information and try to keep unnecessary information

2. Code implementation

The client code is as follows:

package main

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

// UDP client

func main() {
	socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   net.IPv4(127, 0, 0, 1),
		Port: 40000,
	})
	if err != nil {
		fmt.Println("Failed to connect to the server, err:", err)
		return
	}
	defer socket.Close()
	var reply [1024]byte
	reader := bufio.NewReader(os.Stdin)
	for {
		fmt.Print("Please enter:")
		msg, _ := reader.ReadString('\n')
		socket.Write([]byte(msg))
		// Received data
		n, _, err := socket.ReadFromUDP(reply[:])
		if err != nil {
			fmt.Println("redv reply msg failed,err:", err)
			return
		}
		fmt.Println("Reply received:", string(reply[:n]))
	}
}

The server code is as follows:

package main

import (
	"fmt"
	"net"
	"strings"
)

// UDP server

func main() {
	// There is no need to use the accept method to establish a UDP server
	conn, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(127, 0, 0, 1),
		Port: 40000,
	})
	if err != nil {
		fmt.Println("listen UDP failed,err:", err)
		return
	}
	defer conn.Close()

	// There is no need to establish a connection and send and receive data directly
	var data [1024]byte
	for {
		n, addr, err := conn.ReadFromUDP(data[:])
		if err != nil {
			fmt.Println("read from UDP failed,err:", err)
			return
		}
		fmt.Println(data[:n])
		reply := strings.ToUpper(string(data[:n]))
		// send data
		conn.WriteToUDP([]byte(reply), addr)
	}
}

4, Sticking package problem [and solutions]

1. Why does it stick?

  • Due to the frequent sending and receiving of data, in order to improve the sending and receiving efficiency, the bottom layer will check whether there are other data to be sent before sending messages
  • If it is sent too frequently, the last packet will stick to the next datagram in summer, resulting in very chaotic data
  • Sticky packets at the sender caused by Nagle algorithm: Nagle algorithm is an algorithm to improve network transmission efficiency. In short, when we submit a piece of data to TCP for transmission, TCP does not immediately send this piece of data, but waits for a short period of time to see if there is still data to be sent during the waiting period. If so, it will send these two pieces of data at one time
/*
	Receive 20 tomhellos sent by the client at the server and print them

	Printing effect under sticky package (various information may be directly connected)
	hello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello
	 Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tomhello Tom
	--------------
	hello Tomhello Tomhello Tom
	--------------

	The normal print result is:
	--------------
	hello Tomhello 
	--------------
	hello Tomhello 
	--------------
	hello Tomhello 
	--------------
	hello Tomhello 
	--------------
	hello Tomhello 
	--------------
	hello Tomhello 
	--------------
	hello Tomhello 
	--------------
	hello Tomhello 
	--------------
	......
*/

2. Solutions

  • Save the size of each data packet, write a header of the data packet to store the size of the data packet, and read the data first each time
  • Read the packet size and read the data information entity
  • Reduce the frequency of sending data

3. Encoding and decoding function

The encoding and decoding function code is as follows:

package edcode

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

/*
	Small end low low: low address memory low
	Big end high and low: high site storage low
	Encode and decode the information to be sent (packaging processing)
	The first four bytes in the packet are used to store the size of the packet, and the following bytes are used to store the main content of the information
*/
// decode
// Take out the first four bytes of bytes, convert them to int32 type, and then take out the remaining message body
func Mydecode(reader *bufio.Reader) (string, error) {
	Size, _ := reader.Peek(4)
	lenthbuff := bytes.NewBuffer(Size)
	var lenth int32
	// Read the contents in lenthbuff. After reading, put the data in lenth. The reading method is small end
	err := binary.Read(lenthbuff, binary.LittleEndian, &lenth)
	// Judge whether there is an error. If the length is not enough, throw an exception
	if err != nil || int32(reader.Buffered()) < lenth+4 {
		return "", err
	}
	//
	pack := make([]byte, int(lenth+4))
	// After read, there is less data in the buffer
	_, err = reader.Read(pack)
	if err != nil {
		return "", err
	}
	return string(pack[4:]), nil

}

// code
// First calculate the length of the string, and then package it
func MyEncode(message string) ([]byte, error) {
	// Read the length of the message and convert it to int32 type (4 bytes)
	var length = int32(len(message))
	var pkg = new(bytes.Buffer)
	// Write header
	err := binary.Write(pkg, binary.LittleEndian, length)
	if err != nil {
		return nil, err
	}
	// Write message entity
	err = binary.Write(pkg, binary.LittleEndian, []byte(message))
	if err != nil {
		return nil, err
	}
	return pkg.Bytes(), nil
}

The client code is as follows:
package main

import (
	"fmt"
	"net"

	ed "aCorePackage/edcode"
)

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println(err)
	}
	// Fast sending information will still stick together, but the information can be well separated through certain encoding and decoding methods
	for i := 0; i < 20; i++ {
		str := "hello Tom"
		mybtys, err := ed.MyEncode(str)
		if err != nil {
			fmt.Println(err)
		}
		conn.Write(mybtys)
		fmt.Println("-------------------")
	}

}

The server code is as follows:

package main

import (
	ed "aCorePackage/edcode"
	"bufio"
	"fmt"
	"io"
	"net"
)

func readMsd(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	for {
		str, err := ed.Mydecode(reader)
		if err == io.EOF {
			return
		}
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(str)
		fmt.Println("--------------")
	}

}

func main() {
	listener, err := net.Listen("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println(err)
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println(err)
			return
		}
		go readMsd(conn)

	}
}

summary

TCP needs a connection, UDP is connectionless, and there is no need to establish a connection before sending data. TCP provides reliable services. The data transmitted through TCP connection is error free and does not lose. TCP logical communication channel is a reliable full duplex channel, and UDP is an unreliable channel. They have their own advantages and take what they need.

GO GO GO !

Topics: network udp Network Communications TCP/IP