與我們常見的 HTTP Server 不同,Redis 採用 TCP Server 實作,並且在這之上設計了自己的通訊協定 Redis serialization protocol,簡稱 RESP。
對於 RESP 的細節在之後的文章會再探討,讓我們先從 TCP Server 開始
net 套件在不同的平台或作業系統上進行網路通訊程式設計簡直就是災難,這是因為不同的作業系統對網路通訊有著風格各異的 syscall:Linux 上的 epoll、BSD 上的 kqueue,又或是 Windows 上的 IOCP 等。
所以 Go 的標準庫已經幫我們封裝好這些作業系統實作,利用 net 這個 go package 就可以輕鬆地在各平台上實作 TCP Server,而 Go by Example: TCP Server 就給出了一個很適合作為起點的範例。
以下是我基於 Go by Example: TCP Server 修改後的 TCP Server:
package main
import (
"bufio"
"log/slog"
"net"
"strings"
)
func main() {
slog.Info("Listen :16379 for TCP connections...")
listener, err := net.Listen("tcp", ":16379")
if err != nil {
slog.Error("failed to listen tcp:", slog.Any("error", err))
return
}
defer listener.Close()
for {
connection, err := listener.Accept()
if err != nil {
slog.Error("failed to accept connection:", slog.Any("error", err))
continue
}
go handleConnection(connection)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
message, err := reader.ReadString('\n')
if err != nil {
slog.Error("failed to read from conn:", slog.Any("error", err))
return
}
ackmsg := strings.ToUpper(strings.ToUpper(message))
response := "ACK: " + ackmsg
if _, err := conn.Write([]byte(response)); err != nil {
slog.Error("failed to write to conn:", slog.Any("error", err))
return
}
}
log.* 改為 slog.*,這是一個由 Go 1.21 之後開始提供的結構化日誌套件,可以參閱 Structured Logging with slog 這篇文章註:有的時候我會習慣將
slog.Any("error", err)封裝進一個小套件中,這樣就可以在slog.Error("something wrong:", liblogs.ErrAttr(err))直接調用