11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HRBrainAdvent Calendar 2024

Day 23

Gorilla WebSocketを使ってターミナル上で動くチャットツールを作る

Last updated at Posted at 2024-12-23

この記事はHRBrain Advent Calendar 2024 23日目の記事です。

はじめに

最近今年初の鍋を食べたyakiniku0220です。
好きな鍋はもつ鍋ときりたんぽです。

今回はGorilla WebSocketを使ってターミナル上で動くチャットツールを作ってみました。
自分はGoを始めてまだ1年が経っていないため勉強も兼ねて今回作ってみました。

デモ

実際の画面です。

output1.gif

このような感じでクライアント同士でのチャットをターミナル上でできます。

client側とserver側でどういう処理をしているかの説明です。

まず初めにserverディレクトリのmain.go側の説明です。

こちらはWebSocketの設定と接続管理をやってます。

server/main.go
var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

次に接続処理です。

server/main.go
func handleConnections(w http.ResponseWriter, r *http.Request) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Printf("Error upgrading connection: %v", err)
		return
	}
	defer ws.Close()

	clients[ws] = true
	log.Println("New client connected")

	for {
		var msg string
		err := ws.ReadJSON(&msg)
		if err != nil {
			log.Printf("Error reading message: %v", err)
			delete(clients, ws)
			break
		}
		broadcast <- msg
	}
}
ws, err := upgrader.Upgrade(w, r, nil)

まずgorilla/websocketライブラリのUpgrader構造体を使って、HTTP接続をWebSocket接続に切り替える処理をしています。
その後接続ができた場合、成功時のメッセージをlog.Printlnを使用して出力します。

for {
    var msg string
    err := ws.ReadJSON(&msg)
    if err != nil {
        log.Printf("Error reading message: %v", err)
        delete(clients, ws)
        break
    }
    broadcast <- msg
}

次の処理ではクライアント側からのメッセージを取得し文字列に変換後、broadcastチャネルにメッセージを送信してます。

func handleMessages() {
	for {
		msg := <-broadcast
		for client := range clients {
			err := client.WriteJSON(msg)
			if err != nil {
				log.Printf("Error sending message: %v", err)
				client.Close()
				delete(clients, client)
			}
		}
	}
}

こちらではメッセージの送信処理をしています。
broadcastチャネルからメッセージを受け取り、全クライアントに送信しています。

次にclient側です。

client/main.go
func main() {
	serverAddr := "ws://localhost:8080/ws"
	conn, _, err := websocket.DefaultDialer.Dial(serverAddr, nil)
	if err != nil {
		log.Fatalf("Failed to connect to server: %v", err)
	}
	defer conn.Close()

	go func() {
		for {
			var msg string
			err := conn.ReadJSON(&msg)
			if err != nil {
				log.Printf("Error reading message: %v", err)
				break
			}
			fmt.Println("Message:", msg)
		}
	}()

	scanner := bufio.NewScanner(os.Stdin)
	fmt.Println("Enter messages (type 'exit' to quit):")
	for scanner.Scan() {
		text := scanner.Text()
		if text == "exit" {
			break
		}
		err := conn.WriteJSON(text)
		if err != nil {
			log.Printf("Error sending message: %v", err)
			break
		}
	}
}

クライアント側ではサーバー側に接続したのち、メッセージの受信/送信をしています。

ユーザー名を付与してみる。

作ってみたコードだとどちらのクライアントがメッセージを送信しているのかが分かりにくくなっています。
なのでユーザー名を入力する処理を加えてみます。

type Message struct {
	Username string `json:"username"`
	Text     string `json:"text"`
}

reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter your username: ")
username, _ := reader.ReadString('\n')
username = username[:len(username)-1]

msgで定義していた構造体を上記のように変更し、新たにユーザーを入力するメッセージを定義しました。

これで以下のような挙動になりました。

output2.gif

まとめ

ターミナル上でのチャットツールを作ってみましたが、以前紹介したGUIと併用すればGUI上でのチャットツールも作れると思うので、色々とできそうかと思いました。

今回作ったコードはこちらです。
GUIのブログはこちらです。

最後に

株式会社HRBrainでは新しいメンバーを募集中です。
興味がある方は下記のリンクから宜しくお願い致します。

11
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?