Socket.IO는 실시간, 양방향 통신을 웹 애플리케이션에 쉽게 구현할 수 있게 해주는 JavaScript 라이브러리입니다. Node.js 서버와 클라이언트(웹 브라우저) 사이에서 WebSocket을 사용하여 실시간 데이터 교환을 가능하게 합니다.
먼저, NestJS 프로젝트를 생성하고, 필요한 Socket.IO 및 관련 패키지를 설치합니다. 프로젝트가 이미 존재한다면, 해당 디렉토리로 이동해서 다음 명령어를 실행합니다.
npm install @nestjs/platform-socket.io @nestjs/websockets socket.io
Socket.IO를 사용하기 위해 NestJS에서는 게이트웨이라는 개념을 사용합니다. 게이트웨이는 특정 이벤트에 대해 소켓 연결을 관리합니다. 다음 명령어를 사용하여 새로운 게이트웨이를 생성합니다.
nest generate gateway chat
이 명령은 chat.gateway.ts
파일을 생성합니다. 이 파일을 수정하여 Socket.IO 이벤트를 처리할 수 있습니다.
ChatGateway
설정chat.gateway.ts
파일을 열고, 간단한 채팅 로직을 추가합니다. 이 예시에서는 클라이언트로부터 "chat message" 이벤트를 받아 모든 클라이언트에게 메시지를 브로드캐스트합니다.
import { WebSocketGateway, SubscribeMessage, MessageBody, WebSocketServer } from '@nestjs/websockets';
import { Server } from 'socket.io';
@WebSocketGateway({
cors: {
origin: '*', // 실제 배포 시에는 보안을 위해 특정 도메인으로 제한해야 합니다.
},
})
export class ChatGateway {
@WebSocketServer()
server: Server;
@SubscribeMessage('chat message')
handleMessage(@MessageBody() message: string): void {
this.server.emit('chat message', message);
}
}
이벤트 핸들링: @SubscribeMessage()
데코레이터를 사용하여 특정 메시지 이벤트를 수신하고 처리할 메서드를 정의할 수 있습니다. 이 메서드 내에서 클라이언트로부터 받은 데이터를 처리하고, 필요한 경우 응답을 보낼 수 있습니다.
클라이언트와의 통신: 게이트웨이 내에서 @WebSocketServer()
데코레이터를 사용하면, Socket.IO의 Server
인스턴스에 접근할 수 있습니다. 이를 통해 연결된 모든 클라이언트에게 메시지를 브로드캐스트하거나, 특정 클라이언트에게만 메시지를 보낼 수 있습니다.
네임스페이스와 룸: Socket.IO의 개념인 네임스페이스와 룸을 사용하여 통신을 더 세밀하게 제어할 수 있습니다. 네임스페이스는 서로 다른 통신 채널을 생성하여 애플리케이션 내에서 분리된 통신 영역을 만들 수 있게 해줍니다. 룸은 네임스페이스 내에서 더 세분화된 그룹을 만들어, 특정 그룹의 클라이언트에게만 메시지를 보낼 수 있게 합니다.
cors: 이 설정은 WebSocket 연결을 통해 다른 출처에서 서버로의 접근을 허용하기 위한 CORS 정책을 구성합니다. CORS 정책은 HTTP 헤더를 사용하여 구현되며, 이 헤더들은 브라우저가 다른 출처의 리소스에 안전하게 접근할 수 있도록 정보를 제공합니다.
origin: origin
설정은 특정 출처에서 오는 요청만을 허용하도록 서버를 구성할 때 사용됩니다. origin: '*'
는 모든 출처에서 오는 요청을 허용한다는 것을 의미합니다. 하지만, 이는 보안상 위험할 수 있으므로 실제 배포 환경에서는 허용할 특정 도메인을 지정하는 것이 좋습니다. 예를 들어, origin: 'https://www.example.com'
은 오직 https://www.example.com
에서 오는 요청만을 허용하도록 설정합니다.
CORS는 웹의 개방성과 보안 사이에서 균형을 맞추려는 시도 중 하나이며, 웹 애플리케이션 개발 시 이를 적절히 구성하는 것이 중요합니다. CORS(Cross-Origin Resource Sharing, 교차 출처 리소스 공유)는 웹 페이지가 다른 도메인의 리소스에 접근할 수 있도록 허용하는 메커니즘입니다. 기본적으로 웹 브라우저는 같은 출처 정책(Same-Origin Policy)을 따르기 때문에, 한 출처(origin)에서 로드된 문서나 스크립트가 다른 출처의 리소스와 상호작용하는 것을 제한합니다. 이 정책은 보안상의 이유로 중요하지만, 때로는 다른 출처의 리소스에 접근할 필요가 있을 때 CORS가 사용됩니다.
index.html
파일을 생성하고 다음과 같이 작성합니다.
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
/* 여기에 채팅 UI 스타일을 추가하세요 */
</style>
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script>
document.addEventListener('DOMContentLoaded', function () {
const socket = io();
// 폼 제출 이벤트 리스너를 추가합니다.
document.querySelector('form').addEventListener('submit', function(e) {
// 기본 폼 제출 동작을 방지합니다.
e.preventDefault();
// 사용자가 입력한 메시지를 서버로 전송합니다.
const inputElement = document.getElementById('input');
socket.emit('chat message', inputElement.value);
// 입력 필드를 비웁니다.
inputElement.value = '';
return false;
});
// 서버로부터 메시지를 받았을 때의 처리를 정의합니다.
socket.on('chat message', function(msg){
// 새 메시지를 위한 li 요소를 생성합니다.
var newElement = document.createElement('li');
newElement.textContent = msg;
// 메시지 리스트에 새 메시지를 추가합니다.
document.getElementById('messages').appendChild(newElement);
});
});
</script>
</body>
</html>
클라이언트 측 라이브러리는 CDN을 통해 쉽게 가져올 수 있습니다. HTML 파일 내에 다음 스크립트 태그를 추가하세요.
<script src="/socket.io/socket.io.js"></script>
또는 특정 버전을 지정하여 CDN에서 가져올 수도 있습니다.
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
네스트JS에서 Socket.IO를 사용하기 위해 게이트웨이를 생성합니다. 게이트웨이는 특정 네임스페이스(/posts
)에 대한 소켓 연결을 처리합니다. 다음은 게이트웨이를 생성하는 명령어입니다:
nest generate gateway posts/posts
이 명령어는 posts
폴더 내에 posts.gateway.ts
파일을 생성합니다.
posts.gateway.ts
파일을 열고, 게시글별로 채팅 채널을 구성하기 위해 게이트웨이 클래스를 수정합니다. 각 게시글의 ID를 룸 이름으로 사용하여, 게시글에 대한 채팅을 별도로 관리할 수 있도록 합니다.
import { WebSocketGateway, SubscribeMessage, WebSocketServer, ConnectedSocket, MessageBody } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({
namespace: '/posts' // 네임스페이스 설정
})
export class PostsGateway {
@WebSocketServer()
server: Server;
@SubscribeMessage('joinRoom')
handleJoinRoom(@MessageBody() data: { postId: string }, @ConnectedSocket() client: Socket) {
const { postId } = data;
client.join(postId); // 클라이언트를 게시글 ID에 해당하는 룸에 참여시킴
}
@SubscribeMessage('leaveRoom')
handleLeaveRoom(@MessageBody() data: { postId: string }, @ConnectedSocket() client: Socket) {
const { postId } = data;
client.leave(postId); // 클라이언트를 게시글 ID에 해당하는 룸에서 탈퇴시킴
}
// 게시글 ID에 해당하는 룸에 메시지 보내기
@SubscribeMessage('sendMessage')
handleMessage(@MessageBody() data: { postId: string; message: string }) {
this.server.to(data.postId).emit('newMessage', data.message);
}
}
위 코드에서는 네임스페이스(/posts
)를 설정하고, joinRoom
, leaveRoom
, sendMessage
이벤트를 처리하는 메소드를 구현했습니다. 클라이언트가 특정 게시글의 채팅에 참여하거나 탈퇴할 때, 그리고 메시지를 보낼 때 사용됩니다.
클라이언트 측에서는 Socket.IO 클라이언트 라이브러리를 사용하여 서버와 통신합니다. 게시글 페이지에 들어갈 때 joinRoom
이벤트를, 페이지를 떠날 때 leaveRoom
이벤트를 발송하고, 메시지를 보낼 때는 sendMessage
이벤트를 사용합니다.