Socket — это уровень интерфейса, предоставляемый после инкапсуляции TCP/UDP.Мы можем использовать Socket для записи сервера и клиента, а затем позволить клиенту и серверу установить соединение TCP или UDP.
Функциональный интерфейс для программирования сокетов Unix
Программирование сокетов в Unix/Linux в основном выполняется путем вызоваlisten
, accept
, write
read
и другие функции для достижения.Как показано на следующем рисунке:
Модель программирования сокетов в 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
Пунктирная линия указывает на изменение состояния пассивной стороны, а толстая красная линия указывает на изменение состояния активной стороны:
Изменения состояния, наблюдаемые с точки зрения клиента сервера: