Программирование сокетов с помощью Go | Началось с Луо Ченом

задняя часть Go

Socket — это уровень интерфейса, предоставляемый после инкапсуляции TCP/UDP.Мы можем использовать Socket для записи сервера и клиента, а затем позволить клиенту и серверу установить соединение TCP или UDP.

Функциональный интерфейс для программирования сокетов Unix

Программирование сокетов в Unix/Linux в основном выполняется путем вызоваlisten, accept, write readи другие функции для достижения.Как показано на следующем рисунке:

UnixSocketUnixSocket

Модель программирования сокетов в Golang

По сравнению с программированием Socket для Linux, программирование Socket для go намного проще, сервер можно реализовать напрямую через режим Listen + Accept:

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

Клиент может вызвать Dial напрямую:

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

Реализовать сервер, который принимает разные команды

Мы реализуем сервер, который принимает следующие команды:

  • pingКоманда зонда, сервер вернет "pong"
  • echoСервер вернет полученную строку
  • quitСервер закроет соединение после получения этой команды

Конкретный код сервера выглядит следующим образом:

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

После компиляции приведенного выше кода сервера и его запуска мы используем telnet для проверки правильности работы сервера.Результат показан на следующем рисунке:

Мы ввели по очереди следующие три команды в telnet:

  • ping
  • echo hello, hbliu
  • quit

Реализация клиента

Мы можем сами реализовать клиент для связи с нашим сервером для достижения функций, подобных telnet.Код выглядит следующим образом:

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

Мы можем скомпилировать приведенный выше код и выполнить команды, поддерживаемые сервером, после запуска, как показано на следующем рисунке:

Диаграмма перехода состояния TCP

Пунктирная линия указывает на изменение состояния пассивной стороны, а толстая красная линия указывает на изменение состояния активной стороны:

Изменения состояния, наблюдаемые с точки зрения клиента сервера:

References