- 서버-클라이언트 간의 실시간 양방향 통신을 가능하게 하는 라이브러리입니다. 주로 웹 애플리케이션에서 클라이언트와 서버 간의 실시간 데이터 전송을 위해 사용이 됩니다. - 이는 WebSocket 프로토콜을 사용하지만, 브라우저 호환성을 위해 필요한 경우에는 풀백(fallback) 메커니즘을 사용합니다.
https://socket.io/
[ 더 알아보기 ] 💡 풀백 메커니즘
- 주어진 기술이나 프로토콜이 작동하지 않을 경우, 대체할 수 있는 다른 방법을 사용하는 것을 의미합니다. - 예를 들어, Socket.io는 WebSocket을 기본적으로 사용하지만, 만약 클라이언트의 브라우저가 WebSocket을 지원하지 않는다면, HTTP 롱 폴링 등 다른 통신 방식을 사용하여 여전히 실시간 통신을 가능하게 합니다.
💡[참고] 상세한 Socket.io에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
💡PM2(Process Manager 2) - Node.js 기반의 애플리케이션의 프로세스 관리를 위한 ‘프로세스 관리자’를 의미합니다. - 이는 내장된 로드 밸런서(Load Balancer)를 기반으로 애플리케이션이 계속 유지되고, 다운타임 (Downtime) 없이 다시 로드하여 시스템 관리를 용이하게 할 수 있습니다.
- NoSQL 데이터베이스 중 하나이며 오픈소스 소프트웨어입니다. 주요한 특징은 ‘키-값(Key-Value)’ 형태로 데이터를 저장하고 이 데이터를 ‘인-메모리 데이터 저장소’에 저장합니다.
- ‘키-값(Key-Value)’ 형태로 데이터를 저장을 하며 ‘다양한 종류의 값’을 지정 가능합니다. 값으로는 문자열, 리스트, 셋, 정렬된 셋, 해시 등을 지원합니다. - ‘인 메모리 데이터 저장소’를 사용하기에 서버의 메인 메모리에 모든 데이터를 저장하므로, 디스크 I/O를 거치는 다른 데이터베이스 시스템보다 훨씬 빠른 성능을 보여줍니다. - 메모리에 저장되는 데이터 베이스는 디스크에 저장하여 휘발성으로 사용되는 데이터를 영구적으로 저장하는 기능을 제공하여 서버가 다운되더라도 데이터를 복구할 수 있습니다. - 주로 데이터베이스, 캐시, 메시지 브로커 등 다양한 용도로 사용될 수 있습니다.
1. 앨리스에게 채팅 메시지를 보냅니다(send a chat message to Alice) - 밥이 앨리스에게 메시지를 작성하고 전송합니다.
2. "messages" 주제로 메시지를 추가하고 발행합니다(add a message, publish to “messages” topic) - 서버는 받은 메시지를 "messages" 주제로 Redis에 발행합니다. 이를 통해 다른 서버 인스턴스들도 해당 메시지를 받을 수 있습니다.
3. 앨리스가 메시지를 받습니다(Alice receives the message) - 앨리스의 클라이언트는 서버로부터 새 메시지를 수신하고 화면에 표시합니다.
- Socket.io 소켓 서버 환경에서 PM2를 이용하여 멀티 프로세스를 지원하는 형태로 구성을 하였을 때, Redis와의 연결도 포함이 되었습니다. - 실제적으로 아래의 Redis의 역할을 통해서 분산 환경에서 Socket.io 애플리케이션의 확장성, 안정성, 그리고 실시간 성능을 크게 향상합니다.
💡homebrew로 redis를 설치합니다 - 간단하게 homebrew를 이용하여 설치를 합니다. - 설치를 하여 실행을 할 때는 foreground 상태에서 내가 서버를 킨 상태인 경우에 수행이 되는 경우와 background 상태에서 PC를 키는 경우 수행되는 경우 두 개의 서비스 실행방법에 대해 알아봅니다.
# redis 설치$ brew install redis
1.2. 설치를 확인합니다.
# redis의 버전을 확인합니다.$ redis-cli --version
1.3. 설치된 Redis를 실행합니다.(background)
# background 상태에서 redis를 실행합니다.$ brew services start redis
# background 상태에서 실행되는$ brew services info redis
- cors, express, socket.io : 소켓 통신에 이용하는 기본적인 설치 합니다 - @socket.io/redis-adapter : 소켓 통신을 유지하기 위한 redis와의 사용을 위해 설치합니다. - pm2 : 클러스터 모드, 로드밸런싱을 위해 설치합니다. - nodemon: 로컬 환경에서 수정사항을 즉시 반영하기 위해 설치합니다.
# 소켓 연결 및 소켓 연결을 유지하기 위한 redis를 설치합니다.
$ npm i cors express socket.io @socket.io/redis-adapter
# 전역 설치 : 프로세스 매니저를 수행하도록 설치합니다.
$ npm i -g pm2
# nodemon을 개발 단계에서 사용하도록 설치합니다.
$ npm i nodemon --save-dev
- apps.env.REDIS_HOST와 apps.env.REDIS_HOST_PORT를 각각 지정하였고, 테스트에서는 앱 이름 “app-dev-redis”를 이용합니다. - 이 설정을 통해 Redis를 사용하는 Socket.io 애플리케이션을 개발 환경에서 클러스터 모드로 실행하기 위한 구성을 마칩니다.
💡Socket.io + Redis 구성을 설정합니다. 1. WebSocket Server 설정 : port, cors 지정 - 해당 서버로 접속할 포트를 지정하고, cors()를 통해서 다른 도메인에서의 모든 요청에 대해서 허용합니다.
2. WebSocket Server 설정값 및 socket.io 구성 - HTTP 서버를 생성하고 지정된 포트로 접근 가능하도록 설정합니다. - transports 속성을 통해서 웹 소켓 연결에 대해서 허용을 하였습니다. - 생성된 HTTP 서버를 기반으로 소켓 서버를 설정하고 socket.io에 대한 cors에 대해 모두 허용을 설정합니다.
3. Redis 설정합니다.(Pub/Sub Redis 연결 구성) - 발행자 클라이언트를 Redis와의 접속으로 연결하여 생성하며, 구독자 클라이언트는 이를 복사하여 생성합니다. - 이 발행자와 구독자를 연결하여 io 서버에 Redis Adapter를 설정합니다.
4. 루트 엔드포인트로 접속 시 health Check 수행 - 루트 경로(”/”)로 접근 시 “Hello world!” 메시지를 출력하고 로그를 출력합니다.
5. 소켓 연결 시작 -"message" 이벤트 발생 시, "message" 이벤트를 등록한 소켓에게 메시지 전달합니다. -"disconnect" 이벤트 발생 시, 소켓 연결을 종료 및 방을 나갑니다.
// import libraryconst app = require('express')(); // 외부 라이브러리 사용const cors = require('cors'); // 외부 라이브러리 사용const http = require('http'); // Node 제공 모듈 사용const redis = require('redis');
const redisAdapter = require('@socket.io/redis-adapter');
/*
* 1. WebSocket Server 설정 : port, cors 지정
*/const port = process.env.PORT;
app.set('port', port); // 웹 소켓 포트를 지정합니다
app.use(cors({ origin: '*' })); // cors를 지정해줍니다./*
* 2. WebSocket Server 설정값 및 socket.io 구성
*/const server = http.createServer(app).listen(port); // 설정 값에 따르는 서버 정보 구축// 설정 값에 따라 socket.io 구축const io = require('socket.io')(server, {
transports: ['websocket'], // 웹소켓 허용cors: {
origin: '*',
methods: ['*'], // 모든 메서드 허용
},
});
/*
* 3. Redis 설정합니다.(Pub/Sub Redis 연결 구성)
*/// 3.1. 발행자(Publisher) 클라이언트 생성const pubClient = redis.createClient({
host: process.env.REDIS_HOST,
port: process.env.REDIS_HOST_PORT,
});
// 3.2. 구독자(Subscriber) 클라이언트 생성const subClient = pubClient.duplicate();
// 3.3. Redis 클라이언트 연결Promise.all([pubClient.connect(), subClient.connect()])
.then(() => io.adapter(redisAdapter.createAdapter(pubClient, subClient))) // Redis adapter 설정
.catch((error) =>console.error('Redis connection error:', error));
/*
* 4. 루트 엔드포인트로 접속 시 health Check 수행
*/
app.get('/', (req, res, next) => {
res.send('Hello world!!!!!!'); // 화면상으로 출력console.log('====== Health Check ======='); // 로그 출력
});
/*
* 5. 소켓 연결 시작
*/
io.on('connection', (socket) => {
console.log('a user connected');
/**
* [ALL] "message" 이벤트 발생 시, "message" 이벤트를 등록한 소켓에게 메시지 전달합니다.
*/
socket.on('message', (msg) => {
io.emit('message', msg);
});
/**
* [ALL] "disconnect" 이벤트 발생 시, 소켓 연결을 종료 및 방을 나갑니다.
*/
socket.on('disconnect', () => {
console.log('a user disconnected');
socket.disconnect();
socket.leave(socket.room);
});
});
- socket/redis/app.js 경로에 파일을 구성하였고, pacakge.json 파일 내에는 “start:redis”라는 명령어로 스크립트를 구성하였습니다.
💡스크립트 설명
- “start:redis”라는 스크립트 명령어를 정의하였습니다. - 이는 PM2를 사용하여 애플리케이션을 실행하며 사전에 구성한 ecosystem 설정 파일을 “./config/ecosystem.config.js"참조하며, -only app-dev 옵션을 통해서 ‘app-dev’ 애플리케이션 설정말 실행하도록 지정하였습니다. - 이를 통해서 Redis와 Socket.io를 사용하는 Node.js 애플리케이션이 PM2를 통해 클러스터 모드로 실행됩니다.
- 왼쪽 클라이언트와 오른쪽 클라이언트 간의 ‘채팅’ 소켓 통신을 주고받고 있습니다. - 왼쪽 클라이언트는 “안녕하세요”라고 소켓 통신을 전달하면 오른쪽 클라이언트에 전달이 되고, 오른쪽 클라이언트는 “안녕하세요 저도 반갑습니다”라고 소켓통신을 전달하면 왼쪽 클라이언트에게 전달이 되어서 서로 간의 데이터 통신이 이루어짐을 확인하였습니다.