概要
Go言語によるWebアプリケーション開発(O'Reilley)のChapter1.2.4までの内容をモデル図を書いて要約する
設計を書いてからコードを書くor 読むと納得しやすいので参考までに
モデル化
ユーザー
client.go
type client struct {
socket *websocket.Conn
send chan []byte
room *room
}
チャットルーム
ソースコード
room.go
type room struct {
forward chan []byte
join chan *client
leave chan *client
clients map[*client]bool
}
メッセージの送受信
当初client.read()およびclient.write()がサーバーサイドから見た読み書きであることがわからず困惑した。
ソースコード
client.go
func (c *client) read() {
for {
if _, msg, err := c.socket.ReadMessage(); err == nil {
c.room.forward <- msg
} else {
break
}
}
c.socket.Close()
}
func (c *client) write() {
for msg := range c.send {
if err := c.socket.WriteMessage(websocket.TextMessage, msg); err != nil {
break
}
}
c.socket.Close()
}
room.go
func (r *room) run() {
for {
select {
// caseが同時に実行されることはない
case client := <-r.join:
// 参加
r.clients[client] = true
case client := <-r.leave:
// 退室
delete(r.clients, client)
close(client.send)
case msg := <-r.forward:
// すべてのクライアントにメッセージを転送
for client := range r.clients {
select {
// メッセージを送信
case client.send <- msg:
default:
// 送信に失敗
delete(r.clients, client)
close(client.send)
}
}
}
}
}
ユーザーとの接続の確立
ついでに、送受信のモデルで説明しなかった最初にユーザーがチャットルームに入るときのフローをまとめておく
- Websocketを使えるようにHTTP接続をアップグレードする
- ユーザーがチャットルームに入室するHTTPリクエストを飛ばす
- room.ServeHTTPメソッドが呼び出される
- upgrader.UpgradeメソッドがWebSocketコネクションを取得
- コネクション取得に成功したならば、clientを生成し、room.joinチャネルに渡す
- クライアント終了時にルームから退出するように遅延処理
- 並行処理でメッセージ受信を待機
- メインスレッドでメッセージ送信を待機
ソースコード
room.go
const (
socketBufferSize = 1024
messageBuffuerSize = 256
)
// HTTP接続をアップグレードしてWebSocketを使えるようにする
var upgrader = &websocket.Upgrader{ReadBufferSize: socketBufferSize,WriteBufferSize: socketBufferSize}
func (r *room) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// WebSocketコネクションを取得
socket, err := upgrader.Upgrade(w,req,nil)
if err != nil {
log.Fatal("ServeHTTP:", err)
return
}
client := &client{
socket: socket,
send: make(chan []byte,messageBuffuerSize),
room: r,
}
r.join <- client
defer func() {r.leave <- client}()
go client.write()
client.read()
}