0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WebSocket使ってみた(チャットツールを作ってみる)

Posted at

やること

チャットツールを作ってみたいということでとりあえず試しにChatGPTに作ってもらってそこから読み解いてみようとしました。

WebSocketとは

ここに詳しくまとめてくださってるので読んでみましょう。
とりあえず双方向の通信を行えるかつ、低コストの通信ができるそうです。
今回私が作ろうとしたチャットツールはチャット情報に揮発性を持たせたかったので、採用しました。
(つまりWebSocketだけだとクライアントとつなげた後の情報しか得られないので履歴とかを残したいならDBとか一緒に使うのがいいのかな)

今回ChatGPTに作ってみてもらったもの

https://github.com/takaryo1010/webSocketChat
React(TypeScript)とGoを使ってクライアントとサーバーを作りました。

読解 ~サーバー編~

main.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が使われています。一度にたくさんリクエストが来たときに処理する工夫でしょう。

そしたら、出てきた二つの関数を見ていきます。

main.go
// 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に追加していきます。

main.go
var rooms = make(map[string]map[*websocket.Conn]bool)

ということでmapを使って誰が接続しているかチェックしているっぽいですね。

最後にfor無限ループでメッセージを受信しています。json形式でメッセージのやり取りをしているので、ws.ReadJSONを使ってテキストを持ってきています。
うまくいったら最後にroomMessagesというチャネルに送ります。
どこでそのチャネルを受け取るのか、それは先ほどgoを付けていたhandleMessagesです!

main.go
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://----)
    だったなと。

読解 ~クライアント編~

App.tsx
  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データをセットするようにします。その後送信ボタンを押した際に以下の関数を実行し、サーバーに送ります。
App.tsx
  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に作ってもらいましたが、技術選定するため、試作品を作るときにすごく便利ですね。
なんとなく双方向通信って難しそうって思っていたので実装できて面白かったです。
私はこれを使って開発を進めるので、皆さんもぜひ作ってみてください!!!

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?