Socket is a layer interface provided after TCP/UDP encapsulation. We can use Socket to write the server and the client, and then let the client and the server establish TCP or UDP connection.

Unix Socket programming function interface

Socket programming in Unix/Linux is mainly realized by calling listen, accept, write read and other functions. The details are shown in the figure below:

UnixSocket

Socket programming model in Golang

Compared to Linux Socket programming, go Socket programming is much easier. The server can directly implement the Listen + Accept mode:

func connHandler(c net.Conn) {
	for {
        cnt, err := c.Read(buf)
        c.Write(buf)
	}
}
func main() {
	server, err := net.Listen("tcp", ":1208")
	for {
		conn, err := server.Accept()
		go connHandler(conn)
	}
}Copy the code

The client calls Dial directly:

func connHandler(c net.Conn) {
	for {
		c.Write(...)
		c.Read(...)
	}
}
func main() {
	conn, err := net.Dial("tcp", "localhost:1208")
	connHandler(conn)
}Copy the code

Implement a server that can accept different commands

We implement a server that accepts the following commands:

  • pingThe server will return “pong.”
  • echoThe server returns the received string
  • quitThe server receives this command and closes the connection

The specific server code is as follows:

package main import ( "fmt" "net" "strings" ) func connHandler(c net.Conn) { if c == nil { return } buf := make([]byte, 4096) for { cnt, err := c.Read(buf) if err ! = nil || cnt == 0 { c.Close() break } inStr := strings.TrimSpace(string(buf[0:cnt])) inputs := strings.Split(inStr, " ") switch inputs[0] { case "ping": c.Write([]byte("pong\n")) case "echo": echoStr := strings.Join(inputs[1:], " ") + "\n" c.Write([]byte(echoStr)) case "quit": c.Close() break default: fmt.Printf("Unsupported command: %s\n", inputs[0]) } } fmt.Printf("Connection from %v closed. \n", c.RemoteAddr()) } func main() { server, err := net.Listen("tcp", ":1208") if err ! = nil { fmt.Printf("Fail to start server, %s\n", err) } fmt.Println("Server Started ..." ) for { conn, err := server.Accept() if err ! = nil { fmt.Printf("Fail to connect, %s\n", err) break } go connHandler(conn) } }Copy the code

After compiling the above server code and starting it, we use Telnet to test whether the server works properly and the result is as shown in the following figure:

We input the following three commands in Telnet respectively:

  • ping
  • echo hello, hbliu
  • quit

Client-side implementation

We can implement a client by ourselves to communicate with our server and realize functions similar to Telnet. The code is as follows:

package main import ( "bufio" "fmt" "net" "os" "strings" ) func connHandler(c net.Conn) { defer c.Close() reader := bufio.NewReader(os.Stdin) buf := make([]byte, 1024) for { input, _ := reader.ReadString('\n') input = strings.TrimSpace(input) if input == "quit" { return } c.Write([]byte(input)) cnt, err := c.Read(buf) if err ! = nil { fmt.Printf("Fail to read data, %s\n", err) continue } fmt.Print(string(buf[0:cnt])) } } func main() { conn, err := net.Dial("tcp", "localhost:1208") if err ! = nil { fmt.Printf("Fail to connect, %s\n", err) return } connHandler(conn) }Copy the code

We can compile the above code and start it to execute the commands supported by the server, as shown below:

TCP status conversion diagram

Where the dotted line indicates the state change of the passive party, and the thick red line indicates the state change of the active party:

State changes observed from a server-side client perspective:

References