[ 1. 관련 Struct (Room, Message, Client) ]
type Room struct {
Forward chan *message
Join chan *Client
Leave chan *Client
Clients map[*Client]bool
}
type message struct {
Name string
Message string
Time int64
}
type Client struct {
Send chan message
Room *Room
Name string
Socket *websocket.Conn
}
Room
- Forward(전달) : 수신 되는 메시지를 저장하는 채널
- Join(참여자) : Socket에 연결된 Client 채널
- Leave(탈락자) : Socket에서 나간 Client 채널 (후처리용)
- Clients : client를 index로 갖는 T/F map : (map[c1] = T)
Client
- Socket *websocket.Conn : 모듈 ⇒ websocket package에서 소켓 정보를 가지는 Conn struct
Room 초기화
func NewRoom() *Room {
return &Room{
Forward: make(chan *message),
Join: make(chan *Client),
Leave: make(chan *Client),
Clients: make(map[*Client]bool),
}
}
👉
new( ) : 메모리 공간 할당 (초기 구조 없음)
make( ) : 공간 할당 + 초기 구조 구현
⇒ chan/map/slice는 내부 구조를 구현해줘야 사용 가능 = make
make( ) : 공간 할당 + 초기 구조 구현
⇒ chan/map/slice는 내부 구조를 구현해줘야 사용 가능 = make
[ 2. Upgrader : HTTP를 WebSocket으로 ]
websocket.Upgrader : HTTP connection을 WebSocket으로 업그레이드
var Upgrader = &websocket.Upgrader{
ReadBufferSize: types.SocketBufferSize,
WriteBufferSize: types.MessageBufferSize,
CheckOrigin: func(r *http.Request) bool { return true }}
CheckOrigin
: 요청 Origin(출처)를 확인해서 함수 조건에 맞지 않으면 거부⇒ request를 받아서 boolean형식의 리턴을 가지는 함수
👉실제로는 아래와 같이 사용 (특정 도메인만 허용)goCheckOrigin: func(r *http.Request) bool { return r.Header.Get("Origin") == " https://mytrusteddomain.com " }
[ 3. 소켓 통신 & Run(Switch-Case) ]
socket : HTTP request를 바탕으로 Upgrade의 결과물 : *Conn
authCookie : Request 쿠키의 auth 값
func (r *Room) SocketServe(c *gin.Context) {
socket, err := Upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
panic(err)
}
authCookie, err := c.Request.Cookie("auth")
if err != nil {
panic(err)
}
client := &Client{
Send: make(chan *message, types.MessageBufferSize),
Room: r,
Name: authCookie.Value,
Socket: socket,
}
r.Join <- client
defer func() { r.Leave <- client }()
//logic
}
- 결국 gin.Context에서 추출한 데이터로 Socket을 만들고
- Client정보를 만들어서
- room의 Join에 Client 추가
- defer 함수를 이용해서 logic이 끝난 후 SocketServe 함수를 탈출하기 직전 Leave에 Client 추가👉defer func ( ) : 함수를 탈출할 때 실행되도록 설정
[ 채널 Select-Case 문 ]
⇒ 특정 channel로 들어오는 데이터를 분기 (핵심 : 분기 지점이 채널)
func (r *Room) RunInit() {
for {
select {
case client := <-r.Join:
r.Clients[client] = true
case client := <-r.Leave:
r.Clients[client] = false
close(client.Send)
delete(r.Clients, client)
case msg := <-r.Forward:
for client := range r.Clients {
client.Send <- msg
}
}
}
}
r.Forward를 통해 msg가 들어오면
- r.Clients의 모든 Client들의 Client.Send에 msg 추가 (Broadcasting)
⇒ client, msg는 그냥 어디에 담을지 정도, 분기의 기준은 channel (r.Join, r.Leave, r.Forward)