Goでwebsocketを作ってみた

# どのような実現方法があるのか？
GPTによると...

・Gorilla WebSocket: 高機能で安定しているが、やや複雑。
・nhooyr.io/websocket: シンプルでコンテキスト対応、軽量だが機能は少なめ。
・golang.org/x/net/websocket: 基本的な機能を提供するが、メンテナンス頻度が低い。
・github.com/gobwas/ws: 高速で低レベル制御が可能だが、学習コストが高い。
・github.com/olahol/melody: 使いやすく多機能だが、パフォーマンスやスケーリングの面で劣ることがある。

# プロジェクトの作成
必要なければ飛ばしてください．
<details>
  <summary>詳細を表示</summary>
  go言語のインストールは<a href=https://go.dev/dl/>こちら</a>から
    
  インストール出来たら．

1. ディレクトリ作成
    ```bash
    mkdir myproject
    ```

2. ディレクトリに移動
    ```bash
    cd myproject
    ```

3. プロジェクトの初期化
    ```bash
    go mod init example.com/myproject
    ```

4. パッケージのダウンロード
    ```bash
    go get -u github.com/gorilla/websocket
    ```

  
</details>

# コード

今回は**Gorilla WebSocket**を使って簡単に座標更新をするものを作ってみたよ．
```main.go
package main

import (
	"log"
	"net/http"
	"os"
	"sync"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

// Messageの型を作る
type Message struct {
	X float64 `json:"x"`
	Y float64 `json:"y"`
	Z float64 `json:"z"`
}

// Client represents a single WebSocket connection
type Client struct {
	conn *websocket.Conn
}

// ClientManager manages all active WebSocket connections
type ClientManager struct {
	clients    map[*Client]bool
	broadcast  chan Message
	register   chan *Client
	unregister chan *Client
	mu         sync.Mutex
}

// NewClientManager creates a new ClientManager
func NewClientManager() *ClientManager {
	return &ClientManager{
		clients:    make(map[*Client]bool),
		broadcast:  make(chan Message),
		register:   make(chan *Client),
		unregister: make(chan *Client),
	}
}

// Start runs the client manager
func (manager *ClientManager) Start() {
	for {
		select {
		case client := <-manager.register:
			manager.mu.Lock()
			manager.clients[client] = true
			manager.mu.Unlock()
		case client := <-manager.unregister:
			manager.mu.Lock()
			if _, ok := manager.clients[client]; ok {
				delete(manager.clients, client)
				client.conn.Close()
			}
			manager.mu.Unlock()
		case message := <-manager.broadcast:
			manager.mu.Lock()
			for client := range manager.clients {
				err := client.conn.WriteJSON(message)
				if err != nil {
					log.Printf("error: %v", err)
					client.conn.Close()
					delete(manager.clients, client)
				}
			}
			manager.mu.Unlock()
		}
	}
}

var manager = NewClientManager()

func wsHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Println(err)
		return
	}
	client := &Client{conn: conn}
	manager.register <- client

	defer func() {
		manager.unregister <- client
	}()

	for {
		var msg Message
		err := conn.ReadJSON(&msg)
		if err != nil {
			log.Println("read:", err)
			break
		}

		log.Printf("recv: %+v", msg)
		manager.broadcast <- msg
	}
}

func main() {
	go manager.Start()

	port := os.Getenv("PORT")
	if port == "" {
		port = "8000"
	}
	http.HandleFunc("/ws", wsHandler)
	log.Println("Starting server on :" + port)
	err := http.ListenAndServe(":"+port, nil)
	if err != nil {
		log.Fatal(err)
	}
}
```

# 確認方法
### goの起動
```
go run main.go
```

### Postman
下記のURLにコネクト
```
ws://localhost:8000/ws
```

下記のjsonを送信してみよう
```
{
    "x": 1.1,
    "y": 2.0,
    "z": 3.0
}
```