우리의 프로젝트는 원래 존재했던 엘리스랩의 좌석 예약 시스템에 대해 불편함을 느끼고 기존의 웹사이트를 뜯어 고친 사이트이다.
기존에는 엘리스랩 관리자와 채팅을 할 수 있는 기능이 없었고, 디코로 문의사항을 주고 받았었어야 했다.
이에 불편함을 느껴 실시간 채팅 기능을 구현하게 되는데...
1탄에서는 websocket, socket.io의 개념과 선언 방식, API 정리에 대해 서술해보겠다.
2탄에서는 경험했던 이벤트 기획, 구현과정 중 경험했던 이슈와 해결방안에 대해 서술하고 결과 코드를 작성할 예정이다.
socket을 사용하여 실시간 채팅을 구현 썰을 풀기 전에 socket에 대해 설명해보겠다.
우리는 지속적인 연결을 유지하여 실시간 채팅을 구현할 것이니 당연히 socket통신을 선택하였다.
우리는 연결된 사용자들을 세밀하게 관리해야 하는 서비스이다. 관리자와 이용자의 n:1 채팅방이고 각각의 1:1 채팅방을 구현했어야 했기에 유지보수 측면에서 이점을 얻기 위해 socket.io를 사용하기로 결정
데이터 전송이 많은 경우는 빠르고 비용이 적은 표준 WebSocket을 이용하는 게 바람직하다고 한다.
웹소켓과 socket.io
스택오버플로우 질문
이벤트를 주고 받기 위해선 socket.io의 API 사용법에 대해 알아야한다.
용도에 따라 사용할 메서드가 달라지기 때문이다.
초반에 아무것도 모르고 socket.on
, socket.emit
으로만 했다가 큰코다쳤다.
우선 다음은 기본적으로 서버와 프론트에서의 socket 선언 방식이다.
1. npm 설치
npm install socket.io@version
2. HTTP 서버와 함께 사용할 때 선언 방식
socket.io 서버만 사용할 때의 선언 방식과 HTTP서버와 함께 사용할 때 선언방식이 나뉘어져 있다. 그에 대한 방식은 공식문서에 잘 나와있다.
cors 설정하는 방법도 공식문서에 잘 나와있다.
Handling CORS
const app = express();
const server = http.createServer(app); // HTTP 서버를 생성
const io = new Server(server, { // 소켓 서버 인스턴스 생성, 첫 번째 인자로 HTTP 서버 전달
cors: { // 두번째 인자는 서버 구성 옵션 객체, CORS활성화
origin: true,
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
},
});
server.listen(PORT, () => {
console.log(`Socket server on!!`);
}); // 서버 시작하고 지정된 포트에서 수신 대기
1. npm 설치
npm install socket.io-client
2. 선언 방식
클라이언트 선언 방식은 비교적 간단하다.
우선 설치한 라이브러리를 불러온다음, 클라이언트를 생성한다.
공식문서에 클라이언트 선언 방식을 보면, 첫번째 인자 url밖에 없을 것이다.
처음에 url만 입력했었을 때 연결이 제대로 되지 않았고, 두번째 인자에 여러 설정값들을 제대로 입력하고 나니 연결이 제대로 되었다.
그에 대한 내용 또한 Troubleshooting connection issues
여기 공식문서에 친절하게 설명이 되어있다.
socket.io는 첫 연결 때 Polling( http로 실시간 데이터 전송 방법)으로 연결하고 웹소켓을 사용할 수 있는 환경이면 websocket으로 업그레이드한다.
만약 처음부터 Polling 방식이 아닌 WebSocket 방식으로 연결하려면 transports: ['websocket']를 추가한다.
나는 path설정과 transports 설정을 해주었다.
import { io } from 'socket.io-client';
const socket = io(`${process.env.REACT_APP_SOCKET_ENDPOINT}`, {
path: '/socket.io/', // socket.io 경로 설정, Socket.IO는 기본적으로 "/socket.io/" 경로를 사용하여 서버와 클라이언트 간의 통신을 처리한다.
transports: ['websocket'], // websocket을 사용하여 통신하겠다고 지정
});
서버와 클라이언트 사이에 이벤트를 보내는 방법이다.
// server-side
io.on("connection", (socket) => {
socket.emit("hello", "world");
});
// client-side
socket.on("hello", (arg) => {
console.log(arg); // world
});
이런식으로 on, emit 방법으로 이벤트를 주고 받게 된다.
공통적으로 어느 사이드에서 이벤트를 보낼 때는 emit
을 이용하고, 이벤트를 받을 때는 on
을 사용한다.
// BAD
socket.emit("hello", JSON.stringify({ name: "John" }));
// GOOD
socket.emit("hello", { name: "John" });
JSON형식으로 바꿔서 보낼 필요가 없다. 그냥 object 형식으로 보내면 된다.
// server-side
io.on("connection", (socket) => {
socket.on("update item", (arg1, arg2, callback) => {
console.log(arg1); // 1
console.log(arg2); // { name: "updated" }
callback({
status: "ok"
});
});
});
// client-side
socket.emit("update item", "1", { name: "updated" }, (response) => {
console.log(response.status); // ok
});
이벤트를 보낼 때 인자의 첫 번째는 이벤트 명이, 마지막 인자에는 콜백함수가 들어갈 수 있다.
첫 번째 이벤트 명은 무조건 들어가야하고 콜백함수는 선택이지만 무조건 맨 마지막 인자에 넣어야한다.
이벤트와 콜백 함수 사이에는 주고 받을 데이터들이 들어간다.
자세한 emit 메서드들은 다음 공식문서에 잘 나와있다.
여기서 우리가 사용한 메서드들만 추려서 설명해보겠다.
1. io.on("connection", (socket) => {/* ... */})
2. socket.on(/* ... */)
, socket.emit(/* ... */)
3. socket.join(roomId)
4. io.to(roomId).emit('/* ... */)
io.to("room1").to("room2").to("room3").emit("some event")
5. io.emit('onlineStatus', connectionData)
socket 공식문서를 잘 보면서 적절한 상황에 적절한 API를 사용하는 게 중요하다 생각이 들었다.
초반에 부조건 socket.emit, socket.on만 사용하여 백엔드와 이벤트를 주고받으려고 했는데, socket과 io의 차이점을 모르고 사용해서 메세지가 돌아오지 않는 경우가 빈번하게 발생했다.
또한 ~.to.emit과 ~.emit의 차이점도, room의 개념과 사용법을 제대로 알지 못한 상태에서 개발하여 다른 방에 연결된 사용자가 다른 방에서 보낸 메세지를 받게 되는 상황도 발생했었다.
다음 글은 백엔드 분과 함께 기획한 이벤트에 대해 소개하고 그에 따라 구현한 코드를 설명하겠다.