웹소켓
웹소켓
웹소켓 프로토콜은 HTTP와는 다른 통신 프로토콜로, 웹 서버와 웹 브라우저가 서로 실시간 메시지를 교환하는 데에 사용되는 양방향 통신 프로토콜이다. 첫 번째 핸드셰이크를 주고 받은 후 클라이언트와 서버가 동시에 통신하며 데이터를 교환하며, 메시지 별로 새로운 연결을 맺을 필요가 없어 빠르고 효율적이다.
웹소켓은 애플리케이션 계층에서 동작하며, 평문으로 전송되기에 SSL/TLS 보안 방식으로 암호화되어야 데이터 탈취를 방지할 수 있다. 웹소켓을 사용하면 채팅, 알림, 주식 거래 등 실시간 통신이 필요한 기능들을 구현할 수 있다.
완전한 실시간 양방향 통신 채널
HTTP에서의 업그레이드 : 같은 TCP커넥션을 사용함
쉽게 적용 가능 : 표준화되어 있으며 대부분의 웹 브라우저들이 지원함
낮은 오버헤드 : 새로운 연결을 설정할 필요가 없음
헤더는 첫 핸드셰이크에서만 전달
연결 과정
브라우저에서 소켓 통신을 이용하고자 하자면 앞서 말한것과 같이 첫 핸드셰이크를 주고 받음으로 소켓 통신이 가능한지를 확인해야 한다. 이 첫 핸드셰이크는 헤더를 포함하며, 여기에는 웹소켓에 관한 정보가 함께 전송된다.
1
2
3
4
5
6
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
서버에서 웹소켓 통신이 가능하다면, 이에 대한 상태코드로 응답을 클라이언트로 다시 돌려보낸다. 소켓을 통한 연결이 설정되면 양방향 통신이 진행되며, 프레임이라는 작은 단위로 데이터가 전송된다. 어느 한 쪽이 연결을 종료하고자 하면 ‘닫기’ 프레임을 보내어 연결을 종료한다.
코드
자바스크립트에서 웹소켓을 사용하고자 하면 다음과 같이 구현할 수 있다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const socket = new WebSocket('ws://excample.com/socket');
socket.onopen = function(event){
console.log('Socket connection open');
socket.send('Hello, server!');
};
socket.onmessage = function(event) {
console.log('message received: ' + event.data);
};
socket.onclose = function(event) {
console.log('WebSocket connection closed');
};
정글에서 진행했던 프로젝트 ‘Motion Beat’에서는 Socket.IO 라이브러리를 사용하여 구현했다.
아래는 ‘Motion Beat’에서 사용한 예시이다:
- 연결을 열어준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Server } from "socket.io";
import { createServer } from "http";
import {app} from "../app.js"
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: "*",
methods: ["GET", "POST", "PATCH"], // Specify the allowed HTTP methods
allowedHeaders: ["Origin", "X-Requested-With", "Content-Type", "Accept", "Authorization"], // Specify the allowed headers
credentials: true
}
});
export {io, httpServer};
- socket.on을 사용하여 클라이언트가 보내는 메시지를 ‘듣고’, io.emit을 사용하여 클라이언트로 메시지를 보냈다.
- 첫 부분은 실시간 채팅을 구현한 부분이며, 두번째 부분은 방을 나갔을 때 나머지 플레이어들이 이를 볼 수 있도록 구현한 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
socket.on("sendMessage", async (message, cb) => {
try {
const user = await userController.checkUser(socket.id);
const room = await roomController.findRoomByPlayerNickname(user.nickname);
if (!room) {
return;
}
const newMessage = await chatController.saveChat(message, user, room.code);
io.to(room.code).emit(`message`, newMessage);
cb({ ok: true });
} catch (err) {
cb({ ok: false, error: err.message });
}
})
socket.on("joinRoom", async (receivedData, cb) => {
const { nickname, code } = receivedData;
socket.code = code;
try {
const roomPlayers = await roomController.getPlayerInfo(code);
socket.join(code);
io.emit(`players${code}`, roomPlayers);
const room = await Room.findOne({ code })
if (room.hostName !== nickname) {
io.to(room.code).emit('allReady', false);
}
cb({ ok: true });
} catch (err) {
cb({ ok: false, error: err.message });
}
});