博客 / 詳情

返回

藉助AI像Redis作者那樣,用Go實現一個聊天程序

最近 Redis 作者 antirez 用C語言寫了一個 https://github.com/antirez/smallchat —— 聊天服務器。看了下代碼,邏輯清晰明瞭,非常適合新手學習。由於我是 Gopher,就想能否用 Go 實現一個聊天服務器。

説到聊天程序,就會想起大學時期,校內有個計算機工作室,加入要求是:自行實現一個聊天軟件。
為了調程序,在圖書館泡了好久,把Windows API編程都翻爛了,也不知道github,只會baidu,覺得自己一定能寫出一個讓人眼前一亮的軟件。最後磕磕巴巴寫了個、一不小心就會觸發bug的點對點聊天工具,總算過關。
不知道你的,第一次練手程序是什麼?
好,今天帶着當時對計算機的熱情,來重温一遍聊天程序。

原程序

先研究下這個 smallchat ,看看運行起來是什麼效果,拉取代碼:

$ git clone https://github.com/antirez/smallchat

編譯

這個是純C代碼,沒有特別的依賴,直接編譯就行。

$ cat Makefile # 查看 make 命令
$ make all # 編譯所有

啓動 server

$ ./smallchat-server # 啓動 chat server

啓動 client

# 啓動 client 連接 server, 你可以啓動多個 client
$ ./smallchat-client 127.0.0.1 7711  # 參數是 <服務IP> <服務端口> 端口號寫在代碼裏了

運行效果如下:

show

Go 實現?

看了下C代碼,inlcude 如下包:

  • socket
  • TCP
  • fd
  • stdio
  • malloc 內存管理
  • select 多路複用
  • termios 這個go也有對應實現,

這些功能 Go 都有,基本上不用引入第三方依賴就能實現。那就

開幹

但我的 C語言 基礎早已還給譚浩強老師了,寫 PHPGolang 的我,在內存管理那塊就看不懂了,所以,我準備藉助 Copilot 來幫我把C語言翻譯成Golang。

如今你還能拿出當年那份對計算機的熱情麼

Copilot

$ code .  # 啓動宇宙最強編輯器--vscode

上號vscode

這裏就藉助比較容易訪問的——Copilot,源代碼也不長,直接全文複製到 copilot-chat 中,讓它幫我們翻譯成 golang 代碼。

我把 chatlib.c smallchat-server.c 複製進去,很快就翻譯好了。

chatlib.h, smallchat-server.h, smallchat-client.h 是頭文件,只是聲明一下函數定義,在Go中沒有對應概念,不用轉。

問答的效果圖:
chat_copilot

前兩個文件的轉換都很順利,甚至,我能用 smallchat-client.c 去訪問 smallchat-server.go , 但是在翻譯 smallchat-client.c 時,還是翻車了,出問題就在 termios 這個庫的處理,我得到的回覆是:

一下代碼省略了部分
...
func main() {
    ...
    conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", os.Args[1], port))
    if err != nil {
        fmt.Println("Error connecting to server:", err)
        os.Exit(1)
    }
    defer conn.Close()

    if err := setRawMode(int(os.Stdin.Fd())); err != nil {
        fmt.Println("Error setting raw mode:", err)
        os.Exit(1)
    }

    go func() {
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
            text := scanner.Text()
            if _, err := conn.Write([]byte(text + "\n")); err != nil {
                fmt.Println("Error writing to server:", err)
                os.Exit(1)
            }
        }
    }()

    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        text := scanner.Text()
        fmt.Println(strings.TrimRight(text, "\n"))
    }
}

顯然不是我要的 termios, 而且執行也報錯。

$ go run ./chatlib.go smallchat-client.go  127.0.0.1 7711
Error setting raw mode: inappropriate ioctl for device
exit status 1

於是我去搜索: golang termios 得到的是:github.com/pkg/term/termios 到這裏就開始走偏了。

我告訴 copilot :
把以上 smallchat-client.c 翻譯成Go代碼,其中 #include <termios.h> 的替代應該是 github.com/pkg/term/termios

到這裏 Copilot 開始胡謅代碼了,例如: term, err := termios.NewTermios(os.Stdin.Fd()) 這句代碼就是屬於不存在的,更本沒有這個函數。

關於我和 Copilot 的對話,我都放到 github 上了,感興趣的小夥伴可以去看看,這裏不贅述。

換我來輔助

於是我還是打開了 smallchat-client.c 代碼,仔細研究了一下,通過 TCP 連接上 server,然後初始化一個 termios 開始接受輸入輸出信息。翻譯的 TCP 代碼邏輯沒問題,就是 terminal 這塊不對。

於是我又看了下 github.com/pkg/term/termios ,那我試試 github.com/pkg/term , RawMode 這個方法就是要找的,但我又看了下 Open 方法:

Open("/dev/ttyUSB0", Speed(19200), RawMode) 

只能接受, 可讀寫的文件路徑(string類型),而 tcp.Dial 返回的 conn 是一個 ReadWriter 對象。所以就沒法用。

在網上搜索了好久,看到了 golang.org/x/term, 並且它有方法:

func NewTerminal(c io.ReadWriter, prompt string) *Terminal

那就該是它了。

馬上告訴 Copilot ,讓它重新生成代碼。

... code ...

以上C代碼轉換為Go代碼,其中 termios 部分,使用 golang.org/x/term 實現

馬上執行試試看,

$ go run ./chatlib.go smallchat-client.go  127.0.0.1 7711
# 直接卡死。。。

好吧,生成代碼失敗。

迴歸需求

最後還是我手動完成這個 client 端。

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"

    "golang.org/x/term"
)

func main() {
    if len(os.Args) != 3 {
        fmt.Printf("Usage: %s <host> <port>\n", os.Args[0])
        os.Exit(1)
    }

    conn, err := net.Dial("tcp", os.Args[1]+":"+os.Args[2])
    if err != nil {
        fmt.Println("Error connecting:", err)
        os.Exit(1)
    }
    defer conn.Close()

    go func() {
        scanner := bufio.NewScanner(conn)
        for scanner.Scan() {
            fmt.Println(scanner.Text())
        }
    }()

    var tty = term.NewTerminal(conn, "> ")

    // get stdin in
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        tty.Write([]byte(scanner.Text() + "\n"))
    }

    return
}

碼成

完整,可運行的代碼我放——https://github.com/PaulXu-cn/smallchat 了。

附上運行效果:
go_chat_show

嗯,和 c 寫的差不多,(自言自語)

不過 client promot 這塊功能是缺失的,目前還沒搞清楚,這是和 C 版 smallchat 的差別,目前還在看 go 的API。

總結

C代碼是用 fd 管理資源,而 go 作為較為現代語言,是不希望程序猿接觸、管理 fd,期望用户操作對象來管理。這就是造成 兩邊 代碼風格比較不同的原因,同時也影響了copilot生成代碼,時不時出現了 Go 有操作Fd API的幻覺!

同時,在理解 temrnial 這塊, copilot 它自己都沒頭緒,每次都是硬答,我看了下關於 Go termimal 這塊的資料確實比較少,這可能導致 copilot 沒有學習到如何使用 Go termnial 相關的API 吧。

好,歡迎大家留言交流。

本文參與了SegmentFault 思否寫作挑戰賽活動,歡迎正在閲讀的你也加入。
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.