41
30

More than 1 year has passed since last update.

GoでWebSocketを使いチャットサーバー構築

Posted at

はじめに

使わない日はないくらい仕事でもプライベートでも、チャットサービスが日常的に使われていますが、チャット機能を実装したことがなかったので、
今回はGo(1.17)でWebSocketを使いチャットサーバーを構築していきます。
なぜGoを使うかと言うと、最近仕事で使っていて、私の中でブームなのと、
そして何よりマスコットキャラクターのGopherがかわいいからです(笑)

Golang.png
The Go gopher was designed by Renée French.

余談ですが、2022/2リリース予定のバージョン1.18ではgo getのバイナリインストールの機能が削除されるため、
バージョンアップの際は手順書やスクリプトを修正する必要があります。皆さんお気をつけください。

システムイメージ

クライアントがチャットサーバーにWebSocketで接続し、そのコネクションをgoroutineでハンドリングし、メッセージの送信を受け付けます。
送信されたメッセージはchannel型の変数に送り、別のgoroutineでメッセージを拾い、それをすべてのクライアントに送信します。
Untitled Diagram.drawio (5).png
goroutineは、Goのランタイムに管理される軽量なスレッドで、並行処理を可能にします。
channelは、送受信する値の型を定義することで、goroutine間で値を送受信できるようにします。
以下のリンクからそれぞれ動きを確認できます。
Goroutines
Channels

実装

さっそくチャットサーバーの実装を進めていきます。
WebSocketを利用するために、今回はWebツールキットのgorillaを使います。🦍

まずはWebSocket、ブロードキャストするためのchannel宣言と、チャットメッセージ用に構造体を宣言します。

// 接続されるクライアント
var clients = make(map[*websocket.Conn]bool) 
// メッセージブロードキャストチャネル
var broadcast = make(chan Message)

// アップグレーダ
var upgrader = websocket.Upgrader{}

// メッセージ用構造体
type Message struct {
    Username string `json:"username"`
    Message  string `json:"message"`
}

main関数では、静的ファイルを参照するファイルサーバーとWebSocketのルーティングを行います。
※今回は割愛しますが、ファイルサーバー(public配下)にフロントで動かすHTML,JS,CSSを配置します。

func main() {
    // ファイルサーバー
    fs := http.FileServer(http.Dir("./public"))
    http.Handle("/", fs)

    // WebSocket
    http.HandleFunc("/ws", handleConnections)
    go handleMessages()

    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

次にコネクションハンドラとして動くhandleConnectionsの中身を実装します。
処理としては

  1. GETリクエストをWebSocketにアップグレード
  2. 受け取ったリクエストをクライアントとして登録し、コネクションを確立
  3. メッセージを待つ
  4. メッセージを受け取ったらチャネルに送りブロードキャスト

といった感じです。
このhandleConnectionsgoステートメントで呼び出して並行実行しています。
そしてchannelを使用し、並行実行されるgoroutine間で値(チャットメッセージ)を送受信します。

func handleConnections(w http.ResponseWriter, r *http.Request) {
    // 送られてきたGETリクエストをWebSocketにアップグレード
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer ws.Close()

    // クライアントを登録
    clients[ws] = true

    for {
        var message Message
        // 新しいメッセージをJSONとして読み込み、Message構造体にマッピング
        err := ws.ReadJSON(&message)
        if err != nil {
            log.Printf("error: %v", err)
            delete(clients, ws)
            break
        }
        // 受け取ったメッセージをbroadcastチャネルに送る
        broadcast <- message
    }
}

最後にbroadcastチャネルにメッセージが送信された時、
接続している全てのクライアントメッセージを送信します。

func handleMessages() {
    for {
        // broadcastチャネルからメッセージを受け取る
        message := <-broadcast
        // 接続中の全クライアントにメッセージを送る
        for client := range clients {
            err := client.WriteJSON(message)
            if err != nil {
                log.Printf("error: %v", err)
                client.Close()
                delete(clients, client)
            }
        }
    }
}

まとめ

これでWebSocketを使ったチャットサーバーの完成です!
簡単ですね!フロントの実装は割愛しますが、こんな感じでチャットができます。
ezgif.com-gif-maker.gif
今回Goでチャットサーバーを作ることで、
WebSocket、goroutine、channelの理解を少し深められたと思います。
普段あまり触れていなかった部分ですが、実際に書いて動かしてみると理解しやすかったです。
皆さんも是非トライしてみてください!

41
30
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
41
30