In the communication between Server and Client, data packet loss is likely to occur due to network and other reasons. If the data is missing and the information received by the Server is incomplete, it will cause confusion.
We need to establish a communication protocol between Server and Client to judge whether the currently received information is complete through the rules in the protocol. Different processing methods shall be adopted according to the integrity of the information.
The core of communication protocol is to design a header. If the incoming message does not contain this header, it means that the current message is the same as the previous message. Then merge the current information and the previous information into one.
The main functions of the protocol are encapsulation and decomposition. Enpack is the data encapsulation of information by the client. After encapsulation, it can be passed to the server. Depack is the server's data analysis of information.
There is a Const section, which is used to define the header, the header length and the length of the client incoming information.
In the code, we define it as follows:
const ( ConstHeader = "Headers" ConstHeaderLength = 7 ConstMLength = 4 )
The content of the header is "Headers" and the length is 7. So ConstHeaderLenth=7
In the process of information transmission, we will convert int type to byte type. The length of an int is equal to the length of 4 bytes. Therefore, we set ConstMLength=4 Represents the size of the information sent by the client.
The code example of custom protocol is as follows:
/** * protocol * @Author: Jian Junbo * @Email: junbojian@qq.com * @Create: 2017/9/14 11:49 * * Description: Communication protocol processing */ package protocol import ( "bytes" "encoding/binary" ) const ( ConstHeader = "Headers" ConstHeaderLength = 7 ConstMLength = 4 ) //Packet func Enpack(message []byte) []byte { return append(append([]byte(ConstHeader), IntToBytes(len(message))...), message...) } //Unpack func Depack(buffer []byte) []byte { length := len(buffer) var i int data := make([]byte, 32) for i = 0; i < length; i++ { if length < i + ConstHeaderLength + ConstMLength{ break } if string(buffer[i:i+ConstHeaderLength]) == ConstHeader { messageLength := ByteToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength]) if length < i+ConstHeaderLength+ConstMLength+messageLength { break } data = buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength] } } if i == length { return make([]byte, 0) } return data } //Byte to integer func ByteToInt(n []byte) int { bytesbuffer := bytes.NewBuffer(n) var x int32 binary.Read(bytesbuffer, binary.BigEndian, &x) return int(x) } //Convert integers to bytes func IntToBytes(n int) []byte { x := int32(n) bytesBuffer := bytes.NewBuffer([]byte{}) binary.Write(bytesBuffer, binary.BigEndian, x) return bytesBuffer.Bytes() }
The Server side mainly parses the information sent by the client through the protocol. Establish a function to complete the connection processing of the received information. The channel readerChannel is established and the received information is placed in the channel.
Before putting into the channel, the information is parsed using protocol and Depack.
//Connection processing func handleConnection(conn net.Conn) { //A buffer that stores truncated data tmpBuffer := make([]byte, 0) //Receiving and unpacking readerChannel := make(chan []byte, 10000) go reader(readerChannel) buffer := make([]byte, 1024) for{ n, err := conn.Read(buffer) if err != nil{ Log(conn.RemoteAddr().String(), "connection error: ", err) return } tmpBuffer = protocol.Depack(append(tmpBuffer, buffer[:n]...)) readerChannel <- tmpBuffer //The received information is written to the channel } defer conn.Close() }
If there is an error in reading the information (including reading the information end character EOF), the error message will be printed and the loop will jump out.
Log(conn.RemoteAddr().String(), "connection error: ", err) return
Because the data in the channel is [] byte. Need to convert to string. This work is completed by a special reader (readerchannel channel [] byte) for obtaining channel data.
//Get channel data func reader(readerchannel chan []byte) { for{ select { case data := <-readerchannel: Log(string(data)) //Print information in the channel } } }
View the Server side code example:
/** * MySocketProtocalServer * @Author: Jian Junbo * @Email: junbojian@qq.com * @Create: 2017/9/14 13:54 * Copyright (c) 2017 Jian Junbo All rights reserved. * * Description: The server receives the information from the client */ package main import ( "net" "fmt" "os" "log" "protocol" ) func main() { netListen, err := net.Listen("tcp", "localhost:7373") CheckErr(err) defer netListen.Close() Log("Waiting for client ...") //After startup, wait for client access. for{ conn, err := netListen.Accept() //Listening client if err != nil { Log(conn.RemoteAddr().String(), "An error was sent:", err) continue } Log(conn.RemoteAddr().String(), "tcp connection success") go handleConnection(conn) } } //Connection processing func handleConnection(conn net.Conn) { //A buffer that stores truncated data tmpBuffer := make([]byte, 0) //Receiving and unpacking readerChannel := make(chan []byte, 10000) go reader(readerChannel) buffer := make([]byte, 1024) for{ n, err := conn.Read(buffer) if err != nil{ Log(conn.RemoteAddr().String(), "connection error: ", err) return } tmpBuffer = protocol.Depack(append(tmpBuffer, buffer[:n]...)) readerChannel <- tmpBuffer //The received information is written to the channel } defer conn.Close() } //Get channel data func reader(readerchannel chan []byte) { for{ select { case data := <-readerchannel: Log(string(data)) //Print information in the channel } } } //Log processing func Log(v ...interface{}) { log.Println(v...) } //error handling func CheckErr(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } }
The client uses Enpack to encapsulate the information to be sent to the server and writes it to the connection conn.
/** * MySocketProtocalClient * @Author: Jian Junbo * @Email: junbojian@qq.com * @Create: 2017/9/14 15:23 * Copyright (c) 2017 Jian Junbo All rights reserved. * * Description: */ package main import ( "net" "time" "strconv" "protocol" "fmt" "os" ) //Send 100 requests func send(conn net.Conn) { for i := 0; i < 100; i++ { session := GetSession() words := "{\"ID\":\""+strconv.Itoa(i)+"\",\"Session\":\""+session+"20170914165908\",\"Meta\":\"golang\",\"Content\":\"message\"}" conn.Write(protocol.Enpack([]byte(words))) fmt.Println(words) //Print sent information } fmt.Println("send over") defer conn.Close() } //Use the current time for identification. Decimal integer of the current time func GetSession() string { gs1 := time.Now().Unix() gs2 := strconv.FormatInt(gs1, 10) return gs2 } func main() { server := "localhost:7373" tcpAddr, err := net.ResolveTCPAddr("tcp4", server) if err != nil{ fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } conn, err := net.DialTCP("tcp", nil, tcpAddr) if err != nil{ fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } fmt.Println("connect success") send(conn) }
Supplement: golang uses socket programming to implement a simple http server from 0 to 1
Start programming
First code
package main import ( "fmt" "net" ) func accept_request_thread(conn net.Conn) { defer conn.Close() for { // Create a new slice to use as a buffer for saving data buf := make([]byte, 1024) n, err := conn.Read(buf) // Read the data content sent by the client from conn if err != nil { fmt.Printf("Client exit err=%v\n", err) return } fmt.Printf(" Accept message %s\n", string(buf[:n])) } } func main() { listen, err := net.Listen("tcp", ":8888") // Create socket for listening if err != nil { fmt.Println("listen err=", err) return } fmt.Println("Listening socket, created successfully, The server starts listening...") defer listen.Close() // Close the listener before the server ends // Loop waiting for client to link for { fmt.Println("Blocking waiting for client to link...") conn, err := listen.Accept() // Create socket for user data communication if err != nil { fmt.Println("Accept() err=", err) } else { fmt.Println("Communication socket, created successfully...") } // Here, a cooperation process is prepared to serve the client go accept_request_thread(conn) } }
The browser sends a get request:
http://192.168.0.20:8888/api/camera/get_ptz?camera_id=1324566666789876543
The message received by the server is as follows:
http://192.168.0.20:8888/api/camera/get_ptz?camera_id=1324566666789876543
Our next task is to parse these strings, and get the current method, request and parameters?
First define a small target and get the current method.
Handle a simple get request
package main import ( "encoding/json" "fmt" "log" "net" "strings" ) func unimplemented(conn net.Conn){ var buf string buf = "HTTP/1.0 501 Method Not Implemented\r\n" _, _ = conn.Write([]byte(buf)) buf = "Server: httpd/0.1.0\r\n" _, _ = conn.Write([]byte(buf)) buf = "Content-Type: text/html\r\n" _, _ = conn.Write([]byte(buf)) buf = "\r\n" _, _ = conn.Write([]byte(buf)) buf = "<HTML><HEAD><TITLE>Method Not Implemented\r\n" _, _ = conn.Write([]byte(buf)) buf = "</TITLE></HEAD>\r\n" _, _ = conn.Write([]byte(buf)) buf = "<BODY><P>HTTP request method not supported.\r\n" _, _ = conn.Write([]byte(buf)) buf = "</BODY></HTML>\r\n" _, _ = conn.Write([]byte(buf)) } func accept_request_thread(conn net.Conn) { defer conn.Close() var i int buf := make([]byte, 1024) n, err := conn.Read(buf) // Read the data content sent by the client from conn if err != nil { fmt.Printf("Client exit err=%v\n", err) return } // Acquisition method i = 0 var method_bt strings.Builder for(i < n && buf[i] != ' '){ method_bt.WriteByte(buf[i]) i++; } method := method_bt.String() if(method != "GET"){ unimplemented(conn) return } for(i < n && buf[i] == ' '){ i++ } //api/camera/get_ptz?camera_id=1324566666789876543 var url_bt strings.Builder for(i < n && buf[i] != ' '){ url_bt.WriteByte(buf[i]) i++; } url := url_bt.String() if(method == "GET"){ //url ---> /api/camera/get_ptz?camera_id=1324566666789876543 // Jump to the first? var path, query_string string j := strings.IndexAny(url, "?") if(j != -1){ path = url[:j] if(j + 1 < len(url)){ query_string = url[j+1:] } }else{ path = url } fmt.Print(path + "Request created\t") resp := execute(path, query_string)// =1324566666789876543 fmt.Println("return", string(resp)) header(conn, "application/json", len(resp)); _ , err := conn.Write(resp) if(err != nil){ fmt.Println(err) } } } //The response client must set the head header before the browser can parse it func header(conn net.Conn, content_type string , length int ) { var buf string buf = "HTTP/1.0 200 OK\r\n" _, _ = conn.Write([]byte(buf)) buf = "Server: httpd/0.1.0\r\n" _, _ = conn.Write([]byte(buf)) buf = "Content-Type: " + content_type + "\r\n" _, _ = conn.Write([]byte(buf)) _, _ = fmt.Sscanf(buf, "Content-Length: %d\r\n", length) buf = "Content-Type: " + content_type + "\r\n" _, _ = conn.Write([]byte(buf)) buf = "\r\n" _, _ = conn.Write([]byte(buf)) } func execute(path string, query_string string) ([]byte) { query_params := make(map[string]string) parse_query_params(query_string, query_params) if("/api/camera/get_ptz" == path){ /* * do something */ camera_id := query_params["camera_id"] resp := make(map[string]interface{}) resp["camera_id"] = camera_id resp["code"] = 200 resp["msg"] = "ok" rs, err := json.Marshal(resp) if err != nil{ log.Fatalln(err) } return rs }else if("get_abc" == path){ /* * do something */ return []byte("abcdcvfdswa") } return []byte("do't match") } /*map Arguments as functions are passed as pointers When the map is modified in the function, the value of the source map will be modified at the same time, but the expected effect will not be achieved when the map is modified to nil.*/ // camera_id=1324566666789876543&tt=%E5%88%9B%E5%BB%BA%E6%88%90%E5%8A%9F func parse_query_params(query_string string, query_params map[string]string) { kvs := strings.Split(query_string, "&") if(len(kvs) == 0){ return } for _, kv := range kvs { kv := strings.Split(kv, "=") if(len(kv) != 2){ continue } query_params[kv[0]] = kv[1] } } func main() { listen, err := net.Listen("tcp", ":8888") // Create socket for listening if err != nil { fmt.Println("listen err=", err) return } fmt.Println("Listening socket, created successfully, The server starts listening...") defer listen.Close() // Close the listener before the server ends // Loop waiting for client link for { fmt.Println("Blocking waiting client links...") conn, err := listen.Accept() // Create socket for user data communication if err != nil { panic("Accept() err= " + err.Error()) } // Here, a cooperation process is prepared to serve the client go accept_request_thread(conn) } }