やること
チャットツールを作ってみたいということでとりあえず試しにChatGPTに作ってもらってそこから読み解いてみようとしました。
WebSocketとは
ここに詳しくまとめてくださってるので読んでみましょう。
とりあえず双方向の通信を行えるかつ、低コストの通信ができるそうです。
今回私が作ろうとしたチャットツールはチャット情報に揮発性を持たせたかったので、採用しました。
(つまりWebSocketだけだとクライアントとつなげた後の情報しか得られないので履歴とかを残したいならDBとか一緒に使うのがいいのかな)
今回ChatGPTに作ってみてもらったもの
https://github.com/takaryo1010/webSocketChat
React(TypeScript)とGoを使ってクライアントとサーバーを作りました。
読解 ~サーバー編~
func main() {
// ルートディレクトリ
fs := http.FileServer(http.Dir("./public"))
http.Handle("/", fs)
// WebSocket接続
http.HandleFunc("/ws", handleConnections)
go handleMessages()
fmt.Println("Chat server started on :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("error:", err)
}
}
とりあえず、main.goから
ルートディレクトリに関しては./publicに何も置いていないので飛ばします。
localhost/wsでhandleConnecitons
を呼び出してますね。
次にhandleMessages
。goroutineが使われています。一度にたくさんリクエストが来たときに処理する工夫でしょう。
そしたら、出てきた二つの関数を見ていきます。
// WebSocketアップグレード用
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type roomMessage struct {
room string
message string
}
type Message struct {
Text string `json:"text"`
}
var rooms = make(map[string]map[*websocket.Conn]bool)
var roomMessages = make(chan roomMessage)
func handleConnections(w http.ResponseWriter, r *http.Request) {
// WebSocket接続をアップグレード
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println(err)
return
}
defer ws.Close()
// ルーム名を取得(例: クエリパラメータ "room")
room := r.URL.Query().Get("room")
if room == "" {
room = "default" // デフォルトのルーム
}
// ルームにクライアントを追加
if rooms[room] == nil {
rooms[room] = make(map[*websocket.Conn]bool)
}
rooms[room][ws] = true
for {
var msg Message
err := ws.ReadJSON(&msg)
if err != nil {
fmt.Println("error reading JSON:", err)
fmt.Println("Received data:", msg)
delete(rooms[room], ws)
break
}
// メッセージをルームにブロードキャスト
roomMessages <- roomMessage{room: room, message: msg.Text}
}
}
上から読んでいきます。
WebSocketをアップグレード...?
http(s)接続を普段は使っていますが、そこからWebSocket用にアップグレードが必要らしいです。これによってプロトコルが変わり、接続方法を決定できるらしいです。後述しますが、リクエスト方法もhttp://localhost:8080
からws://localhost:8080
に変更されます!http以外に初めて出会えてうれしいです。
次にクエリパラメータにroom名を入れて通信するのでそれを取ってきます。
そこからroomsに追加していきます。
var rooms = make(map[string]map[*websocket.Conn]bool)
ということでmapを使って誰が接続しているかチェックしているっぽいですね。
最後にfor無限ループでメッセージを受信しています。json形式でメッセージのやり取りをしているので、ws.ReadJSON
を使ってテキストを持ってきています。
うまくいったら最後にroomMessages
というチャネルに送ります。
どこでそのチャネルを受け取るのか、それは先ほどgo
を付けていたhandleMessages
です!
func handleMessages() {
for {
// ブロードキャストチャンネルからメッセージを取得
msg := <-roomMessages
// ルームに接続しているすべてのクライアントに送信
for client := range rooms[msg.room] {
err := client.WriteJSON(msg.message)
if err != nil {
fmt.Println("error:", err)
client.Close()
delete(rooms[msg.room], client)
}
}
}
}
for無限ループでroomMessages
に値が入るまで待っている処理をしています。
msg
に入ったら処理が始まります。WriteJSON
を使うことによってrooms
に入っているクライアントさん全員に対してJSON形式で送信できます。
バックエンドに関しては以上です。
面白かったことは
- ゴルーチン使ってる
- ws通信(ws://----)
だったなと。
読解 ~クライアント編~
useEffect(() => {
// WebSocketサーバーに接続
const ws = new WebSocket(`ws://localhost:8080/ws?room=${room}`);
// 接続が開いたときのイベント
ws.onopen = () => {
console.log("WebSocket connected");
setIsConnected(true);
};
// メッセージを受信したとき
ws.onmessage = (event) => {
setMessages((prev) => [...prev, event.data]);
};
// 接続が閉じたとき
ws.onclose = () => {
console.log("WebSocket disconnected");
setIsConnected(false);
};
setSocket(ws);
// クリーンアップ
return () => {
ws.close();
};
}, [room]); // ルームが変更されたときに再接続
WebSocket型にはいろいろメソッドがあるっぽいですね。
今回は
- onopen
- onmessage
- onclose
を使っています。
接続はいいとして、messageを受信した時を見ます。
eventデータをセットするようにします。その後送信ボタンを押した際に以下の関数を実行し、サーバーに送ります。
const sendMessage = () => {
if (socket && isConnected && input.trim() !== "") {
const msg = { text: input }; // メッセージをオブジェクト形式で送信
socket.send(JSON.stringify(msg)); // JSONとして送信
setInput("");
} else {
console.log("WebSocket is not connected or input is empty");
}
};
またWebSocketのメソッドを使います。今回はsend
。これでJSONを送ります。
クライアントはこれだけです。
フロントエンドが苦手な私でも理解できる程度でした。
まとめ
クライアントとサーバーどちらもChatGPTに作ってもらいましたが、技術選定するため、試作品を作るときにすごく便利ですね。
なんとなく双方向通信って難しそうって思っていたので実装できて面白かったです。
私はこれを使って開発を進めるので、皆さんもぜひ作ってみてください!!!