Fundamentals of golang network programming

Keywords: Go network

golang network programming

1.TCP programming

Processing flow of TCP server program:

 1.Listening port
 2.Receive client request to establish link
 3.establish goroutine Process links.

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


Server

package main

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

func process(conn net.Conn)  {
	//Delay closing connection
	defer conn.Close()
	for {
		//Read conn
		//bufio.NewReader opens a file and returns a file handle
		reader := bufio.NewReader(conn)
		//Open a 128 byte character buffer
		var buf [128]byte
		//Read the contents of the reader and put them into buf. n is the size
		n, err := reader.Read(buf[:])
		if err != nil {
			fmt.Println("Failed to read message from client..., err", err)
			break
		} else {
			fmt.Println("Received a piece of data:")
		}
		recvStr := string(buf[:n])
		fmt.Println(recvStr)
		//Reply received successfully
		fmt.Println("Send a confirmation message to the client!")
		echo := "echo: " + recvStr
		conn.Write([]byte(echo))
	}
}

func main()  {
	//monitor
	//The server starts waiting for the client to connect, and the listen function will not block
	listen, err := net.Listen("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("Failed to establish connection, err", err)
		return
	}
	fmt.Println("Ready to receive client connections...")
	for {
		//Establish connection
		//The accept function takes a connection from the established connection queue to the client program for point-to-point connection
		//The accept function returns a new available socket
		//The accept function blocks
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("connection failed, err", err)
			continue
		} else {
			fmt.Println("Connection succeeded!")
		}
		go process(conn)
	}
}

client

package main

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

func main()  {
	//Start dialing
	conn, err := net.Dial("tcp", "127.0.0.1:20000")
	if err != nil {
		fmt.Println("err: ", err)
		return
	}
	defer conn.Close()
	//Open os.Stdin and return a file handle
	inputReader := bufio.NewReader(os.Stdin)
	for  {
		//Read user input
		fmt.Println("Please enter the message to be sent:")
		//Input wrap end input
		input, _ := inputReader.ReadString('\n')
		//strings.Trim removes the matching content at the beginning and end of the string
		inputInfo := strings.Trim(input, "\r\n")
		//If you enter q, exit
		if strings.ToUpper(inputInfo) == "Q" {
			fmt.Println("Stop input and disconnect!")
			return
		}

		//send data
		fmt.Println("Start sending data...")
		_, err = conn.Write([]byte(inputInfo))
		if err != nil {
			return
		} else {
			fmt.Println("Sending succeeded!")
		}

		buf := [512]byte{}
		n, err := conn.Read(buf[:])
		if err != nil {
			fmt.Println("Receive failed, err: ", err)
			return
		} else {
			fmt.Println("Received a reply from the server:")
		}
		fmt.Println(string(buf[:n]))
	}
}

2.UDP programming

The Chinese name of UDP protocol is User Datagram Protocol. It is a connectionless transport layer protocol in OSI (Open System Interconnection) reference model. It can directly send and receive data without establishing a connection. It belongs to unreliable and non sequential communication, but UDP protocol has good real-time performance, It is usually used in the field of live video broadcasting.

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


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("Failed to establish monitoring!, err:", err)
		return
	}
	defer listen.Close()
	for {
		var data [1024]byte
		//receive data 
		n, addr, err := listen.ReadFromUDP(data[:])
		if err != nil {
			fmt.Println("Failed to receive data! err:", err)
			continue
		}
		fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
		//send data
		_, err = listen.WriteToUDP(data[:n], addr)
		if err != nil {
			fmt.Println("Failed to send data! err:", err)
			continue
		}
	}
}

client

package main

import (
	"fmt"
	"net"
)

func main() {
	socket, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP: net.IPv4(0, 0, 0, 0),
		Port: 30000,
	})
	if err != nil {
		fmt.Println("Failed to connect to the server! err:", err)
		return
	}
	defer socket.Close()
	sendData := []byte("hello server")
	//send data
	_, err = socket.Write(sendData)
	if err != nil {
		fmt.Println("Failed to send data! err:", err)
		return
	}

	//receive data 
	data := make([] byte, 4096)
	n, remoteAddr, err := socket.ReadFromUDP(data)
	if err != nil {
		fmt.Println("Failed to receive data! err:", err)
		return
	}
	fmt.Printf("recv:%v addr:%v count:%v\n\n", string(data[:n]), remoteAddr, n)
}

3. TCP sticky packet

Sticky bag scene


The following code is a TCP server and client implemented with golang, which will generate sticky packets.


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("received client Data sent:", 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, Hello. How are you?`
		conn.Write([]byte(msg))
	}
}

Output results:

received client Data sent: 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?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?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?

The data sent by the client in 10 times is not successfully output 10 times at the server, but multiple pieces of data are "stuck" together.

Cause of sticking


The main reason is that tcp data transfer mode is stream mode, which can receive and send multiple times when maintaining a long connection.

"Sticky packet" can occur at the sending end or at the receiving end:

1. Sticky packets at the sender caused by Nagle algorithm: Nagle algorithm is an algorithm to improve network transmission efficiency. Simply put, 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.
2. Packet sticking at the receiving end caused by untimely reception 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 can't take out the TCP data in time for some reasons, several segments of data will be stored in the TCP buffer.



terms of settlement


The key to "sticking packets" is that the receiver is uncertain about the size of the packets to be transmitted, so we can packet and unpack the packets.

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

The commonly used protocol is TLV coding:

Next, we will implement simple LV coding, and T will ignore it for the time being.
The first four bytes of the packet are the packet header, which stores the length of the transmitted data.


Encoding and decoding

package LVEcode

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

// Encode encodes the message
func Encode(message string) ([]byte, error)  {
	//Read the length of the message and convert it into int32 type (4 bytes)
	var length = int32(len(message))
	//Open a buffer
	var pkg = new(bytes.Buffer)
	//Write message header (write length to pkg)
	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
}

// Decode decode message
func Decode(reader *bufio.Reader) (string, error) {
	//Read the length of the message
	//Read the first 4 bytes of data (int32 takes up 4 bytes)
	lengthByte, _ := reader.Peek(4)
	//Reads lengthByte and returns a handle
	lengthBuff := bytes.NewReader(lengthByte)
	var length int32
	//Write the contents of lengthBuff to length
	err := binary.Read(lengthBuff, binary.LittleEndian, &length)
	if err != nil {
		return "", err
	}
	//Buffered returns the number of existing readable bytes in the buffer
	if int32(reader.Buffered()) < length + 4 {
		return "", err
	}

	//Read real message data
	pack := make([]byte, int(4 + length))
	//Read the data in the reader into the pack slice
	_, err = reader.Read(pack)
	if err != nil {
		return "", err
	}
	//Returns a slice of valid data
	return string(pack[4:]), nil
}

Server

package main

import (
	"NetWork/TCP3/LVEcode"
	"bufio"
	"fmt"
	"io"
	"net"
)

func process(conn net.Conn) {
	defer conn.Close()
	//Open conn and return a handle
	reader := bufio.NewReader(conn)
	for {
		//Decode the received message
		msg, err := LVEcode.Decode(reader)
		if err == io.EOF {
			return
		}
		if err != nil {
			fmt.Println("decode msg failed, err:", err)
			return
		}
		fmt.Println("received client Data sent:", 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 (
	"NetWork/TCP3/LVEcode"
	"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, Hello. How are you?`
		//Encode the message to be sent first
		data, err := LVEcode.Encode(msg)
		if err != nil {
			fmt.Println("encode msg failed, err:", err)
			return
		}
		conn.Write(data)
	}
}

Output:

received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?
received client Data sent: Hello, Hello. How are you?

It can be found that the package is no longer sticky.



4. HTTP programming


web Workflow


The working principle of Web server can be summarized as follows:

  • The client establishes a TCP connection to the server through TCP/IP protocol
  • The client sends an HTTP protocol request package to the server to request the resource document in the server
  • The server sends an HTTP protocol response package to the client. If the requested resource contains dynamic language content, the server will call the interpretation engine of dynamic language to process the "dynamic content" and return the processed data to the client
  • The client is disconnected from the server. The client interprets the HTML document and renders the graphic results on the client screen

HTTP protocol

  • HyperText Transfer Protocol (HTTP)
    Protocol is the most widely used network protocol on the Internet. It specifies the rules of communication between browser and World Wide Web server, and the data transmission protocol for transmitting World Wide Web documents through the Internet
  • The HTTP protocol is usually hosted on the TCP protocol

HTTP server

package main

import (
	"fmt"
	"net/http"
)

func main()  {
	//Register default route
	// /go is the request path
	//myHandler is a callback function written by itself
	http.HandleFunc("/go", myHandler)
	//addr: listening address
	//Handler: callback function
	http.ListenAndServe("127.0.0.1:8000", nil)
}

// Handler function
func myHandler(w http.ResponseWriter, r *http.Request)  {
	fmt.Println(r.RemoteAddr, "Connection succeeded")
	//Request method: GET POST DELETE PUT UPDATE
	fmt.Println("method:", r.Method)
	// /go
	fmt.Println("url:", r.URL.Path)
	fmt.Println("header:", r.Header)
	fmt.Println("body:", r.Body)
	//reply
	w.Write([]byte("www.51mh.com"))
}

HTTP client

package main

import (
	"fmt"
	"io"
	"net/http"
)

func main()  {
	resp, _ := http.Get("http://127.0.0.1:8000/go")
	defer resp.Body.Close()
	//200 ok
	fmt.Println(resp.Status)
	fmt.Println(resp.Header)

	buf := make([]byte, 1024)
	for {
		//Receive server information
		n, err := resp.Body.Read(buf)
		if err != nil && err != io.EOF {
			fmt.Println(err)
			return
		} else {
			fmt.Println("Read complete")
			res := string(buf[:n])
			fmt.Println(res)
			break
		}
	}
}

Server running result:

127.0.0.1:8187 Connection succeeded
method: GET
url: /go
header: map[Accept-Encoding:[gzip] User-Agent:[Go-http-client/1.1]]
body: {}

Client run results:

200 OK
map[Content-Length:[12] Content-Type:[text/plain; charset=utf-8] Date:[Thu, 28 Oct 2021 14:38:58 GMT]]
Read complete
www.51mh.com

Posted by vinnier on Thu, 28 Oct 2021 12:18:46 -0700