Socket.IO는 클라이언트와 서버 간의 짧은 대기 시간, 양방향 및 이벤트 기반 통신을 가능하게 하는 라이브러리입니다.
이전에 살펴본 WebSocket과 매우 비슷한 특징을 가지고 있습니다.
차이점은 무엇일까요?
Socket.IO는 WebSocket의 부가기능이 아닙니다.
WebSocket은 Socket.IO가 실시간, 양방향, 이벤트 기반 통신을 제공하는 방법 중 하나입니다.
만약 당신의 브라우저나 기기가 WebSocket을 지원하지 않는다면 Socket.IO는 다른 방법을 사용해서 계속 동작할 것입니다. ex) HTTP long-polling
WebSocket 프로토콜 위에 구축되었으며 HTTP 롱 폴링 또는 자동 재연결로의 폴백과 같은 추가 보장을 제공합니다.
HTTP ling-polling fallback
WebSocket 연결을 설정할 수 없는 경우 연결이 HTTP 롱 폴링으로 대체됩니다.
Automatic reconnection
일부 특정 조건에서 서버와 클라이언트 간의 WebSocket 연결이 중단될 수 있으며 양측은 링크의 끊어진 상태를 인식하지 못합니다.
그렇기 때문에 Socket.IO에는 연결 상태를 주기적으로 확인하는 하트비트 메커니즘이 포함되어 있습니다.
클라이언트가 연결이 끊어지면 서버를 압도하지 않기 위해 기하급수적인 백오프 지연으로 자동으로 다시 연결됩니다.
Packet buffering
패킷은 클라이언트 연결이 끊길 때 자동으로 버퍼링되고 재연결 시 전송됩니다.
Acknowlegements
Sender, Receiver로 이벤트를 보내고 응답을 받는 편리한 방법을 제공합니다.
Broadcasting
서버 측에서는 연결된 모든 클라이언트 또는 클라이언트의 하위 집합에 이벤트를 보낼 수 있습니다.
Multiplexing
네임스페이스를 사용하면 단일 공유 연결을 통해 애플리케이션의 논리를 분할할 수 있습니다. 예를 들어 승인된 사용자만 참여할 수 있는 "관리자" 채널을 만들려는 경우에 유용할 수 있습니다.
npm install socket.io
import http from 'http';
import express from 'express';
import { Server } from 'socket.io';
const port = 3000;
const app = express();
app.set('view engine', 'pug');
app.set('views', __dirname + '/views');
app.use('/public', express.static(__dirname + '/public'));
app.get('/', (_, res) => res.render('home'));
app.get('/*', (_, res) => res.redirect('/'));
const httpServer = http.createServer(app);
const wsServer = new Server(httpServer);
const handleListening = () =>
console.log(`✅ Listening on http://localhost:${port}`);
httpServer.listen(3000, handleListening);
이렇게 socket.io를 설치하는 것으로 socket.IO는 http://localhost:3000/socket.io/socket.io.js
라는 url을 제공한다.
이 url을 제공하는 이유는 socket.IO가 브라우저에서 제공하는 webSocket보다 많은 부가기능을 가지고 있기 때문에 client에도 socket.io를 설치해야하기 때문이다.
그렇기 때문에 front-end에 socket.io의 url을 포함시켜야한다.
script(src="/socket.io/socket.io.js")
Back-end
const httpServer = http.createServer(app);
const wsServer = new Server(httpServer);
wsServer.on('connection', (socket) => {
console.log(socket)
})
WebSocket을 사용하듯 연결하고 socket을 콘솔에 찍어볼 것이다.
Front-end
const socket = io();
io
함수는 자동적으로 back-end socket.io와 연결해주는 함수이다.
back-end 콘솔에 webSocket과는 다른 socket이 출력되었고 연결이 되었다!😀
socket.io는 room 기능을 이미 가지고 있다.
유저가 방을 만들거나 방에 참가할 수 있는 form을 만들어 보자.
...
main
div#welcome
form
input(placeholder="room name", required, type="text")
button Enter Room
div#room
h3
ul
form
input(placeholder="message", required, type="text")
button Send
script(src="/socket.io/socket.io.js")
script(src="/public/js/app.js")
처음 보이는 입력 창에 유저가 방 이름을 입력하고 그 방이 존재한다면 방에 입장하고 존재하지 않는다면 방을 생성하고 입장한다.
방에 유무와 상관없이 방에 입장한다.
방에 입장하면 메세지를 보내는 입력창으로 바뀌어야 하므로 메세지 입력창을 따로 만들어준다.
const socket = io();
const welcome = document.getElementById("welcome");
const form = welcome.querySelector("form");
const room = document.getElementById("room");
room.hidden = true;
let roomName;
function addMessage(message) {
const ul = room.querySelector("ul");
const li = document.createElement("li");
li.innerText = message;
ul.appendChild(li);
}
function handleMessageSubmit(event) {
event.preventDefault();
const input = room.querySelector("input");
const value = input.value;
socket.emit("new_message", input.value, roomName, () => {
addMessage(`You: ${value}`);
});
input.value = "";
}
function showRoom() {
welcome.hidden = true;
room.hidden = false;
const h3 = room.querySelector("h3");
h3.innerText = `Room ${roomName}`;
const form = room.querySelector("form");
form.addEventListener("submit", handleMessageSubmit);
}
function handleRoomSubmit(event) {
event.preventDefault();
const input = form.querySelector("input");
socket.emit("enter_room", input.value, showRoom);
roomName = input.value;
input.value = "";
}
form.addEventListener("submit", handleRoomSubmit);
socket.on("welcome", () => {
addMessage("someone joined");
});
socket.on("bye", () => {
addMessage("someone left");
});
socket.on("new_message", addMessage);
입력창
1. 방에 입장하지 않은 처음 상태에는 room 부분을 가려준다.
2. 방 이름을 입력하여 방을 생성하거나, 기존 방에 입장하면 welcome 부분을 가려주고 room 부분을 보여준다.
socket.emit()
socket.emit([이벤트명], [전달할 인자], [콜백함수])
socket의 emit()
메서드에는 3개의 인자를 전달할 수 있다.
1. submit이 일어나면 socket에 이벤트를 발생시키는데 이때, 원하는 이벤트를 만들 수 있다.
2. 인자를 전달할 수 있다.
webSocket과 달리 개수나 타입에 제한이 없다.
3. 마지막 인자로 서버에서 실행할 callback 함수를 전달할 수 있다.
메세지
1. 메세지를 입력하면 서버에 메세지와 방 이름을 전달하고 메세지를 생성한다.
2. 방에 누군가 입장하거나 퇴장하면 알림 메세지를 보낸다.
wsServer.on("connection", (socket) => {
socket.on("enter_room", (roomName, done) => {
socket.join(roomName);
done();
socket.to(roomName).emit("welcome");
});
socket.on("disconnecting", () => {
socket.rooms.forEach((room) => socket.to(room).emit("bye"));
});
socket.on("new_message", (msg, room, done) => {
socket.to(room).emit("new_message", msg);
done();
});
socket.rooms
socket의 rooms를 콘솔에 찍어보면 socket이 속한 방의 이름을 볼 수 있다.
각 socket은 기본적으로 자신의 id로 된 private room에 속해있다.
기본적으로 유저와 서버 사이에 private room이 있기 때문이다.
wsServer.on("connection", (socket) => {
socket.on("enter_room", (roomName) => {
console.log(socket.rooms)
socket.join(roomName)
console.log(socket.rooms)
});
});
출력 결과
Set(1) { '[Socket의 아이디]'}
Set(2) { '[Socket의 아이디]', '[방 이름]'}
위 결과로 알 수 있듯 socket은 여러 방에(private, public) 동시에 입장할 수 있다.
enter_room
socket에서 "enter_room" 이벤트가 발생하면,
전달 받은 방에 입장시키고 그 방에 "welcome" 이벤트를 발생시킨다.
socket.join()
사용자끼리 실시간으로 대화하기 위해서는 서로 같은 방에 입장해야 한다.
socket.join([방 이름]) 메서드를 사용하면 인자로 전달한 방 이름으로 입장한다.
diconnecting
누군가 연결이 해제되어 "disconnecting" 이벤트가 발생하면,
각 방에 있는 socket의 "bye" 이벤트를 발생시킨다.
new_message
"new_message" 이벤트가 발생하면,
1. 인자로 받은 방에 "new_message" 이벤트를 발생시키고 받은 메세지를 전달한다.
2. 1번 실행 후 인자로 받은 콜백 함수를 실행한다.
callback함수는 backend에서 호출되고 frontend에서 실행된다.
현재 방 리스트를 추가로 만들어 채팅방 완성!