https://socket.io/docs/v4/server-initialization/
Server Initialization | Socket.IO
Once you have installed the Socket.IO server library, you can now init the server. The complete list of options can be found here.
socket.io
채팅 앱에서는 유저가 메시지를 실시간으로 주고받는 것뿐만 아니라, 누가 온라인인지도 즉시 보여주는 기능이 중요합니다.
이를 위해 Socket.IO를 도입했습니다.
서버(Node.js) 에서는 socket.io 라이브러리를 사용하였고, 클라리언트(React) 에서는 socket.io-client 라이브러리를 사용하였습니다.
socket.js: 소켓 서버 설정
socket.js 파일을 새로 만들어 기본 서버 위에 Socket.IO 서버를 올립니다.
import { Server } from 'socket.io';
import http from 'http';
import express from 'express';
import jwt from 'jsonwebtoken';
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: ['http://localhost:5173'],
},
});
export function getReceiverSocketId(userId) {
return userSocketMap[userId];
}
const userSocketMap = {};
io.on('connection', (socket) => {
const token = socket.handshake.auth.token;
if (!token) {
console.log('토큰이 제공되지 않았습니다.');
return socket.disconnect();
}
try {
const decoded = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET);
const userId = decoded.id;
if (userId) userSocketMap[userId] = socket.id;
io.emit('getOnlineUsers', Object.keys(userSocketMap));
socket.on('disconnect', () => {
console.log('disconnected', socket.id);
delete userSocketMap[userId];
io.emit('getOnlineUsers', Object.keys(userSocketMap));
});
} catch (error) {
console.error('유효하지 않은 토큰:', error);
return socket.disconnect();
}
});
export { io, app, server };
- Server는 Socket.IO 서버 객체이며, http 서버 위에 올려야 작동합니다.
- userSocketMap은 현재 온라인 상태인 유저들의 socket.id를 userId 기준으로 저장합니다.
- handshake.query.userId를 통해 클라이언트가 접속 시 전달한 userId를 확인할 수 있습니다.
- io.emit('getOnlineUsers', ...)를 통해 현재 접속 중인 모든 유저에게 온라인 목록을 전송합니다.
- 연결이 끊기면 해당 유저를 userSocketMap에서 제거하고 다시 전체에 알립니다.
index.js 수정
기존에 express로만 서버를 실행했다면, 이제는 socket.js에서 만든 server 객체로 실행해야 합니다.
import { app, server } from './lib/socket.js';
// 기존 app.listen → server.listen 으로 변경
server.listen(PORT, () => {
console.log('Server running on PORT:', PORT);
connectToDB();
});
메시지 전송 시 소켓 사용 (메시지 컨트롤러)
유저가 메시지를 전송할 때, 해당 메시지를 받는 유저가 현재 접속 중이라면 실시간으로 전달합니다.
const receiverSocketId = getReceiverSocketId(receiverId);
if (receiverSocketId) {
io.to(receiverSocketId).emit('newMessage', newMessage);
}
- getReceiverSocketId()를 통해 수신자의 소켓 ID를 얻습니다.
- io.to(socketId).emit(...)은 특정 유저에게만 메시지를 전송합니다.
클라이언트 측: 소켓 연결 (zustand)
useAuthStore
유저가 로그인할 때 소켓 연결을 열고, 로그아웃할 때는 연결을 끊는 함수를 만들어 두었으며, 로그인/로그아웃 시 해당 함수를 호출해주면 됩니다.
connectSocket: () => {
const { authUser, accessToken } = get();
if (!authUser || !accessToken || get().socket?.connected) return;
const socket = io(BASE_URL, {
auth: {
token: accessToken,
},
});
socket.connect();
set({ socket });
socket.on('getOnlineUsers', (userIds) => {
set({ onlineUsers: userIds });
});
},
disconnectSocket: () => {
if (get().socket?.connected) get().socket.disconnect();
},
useChatStore
선택된 유저로부터 새로운 메시지를 수신할 때만 상태를 업데이트합니다.
subscribeToMessages: () => {
const { selectedUser } = get();
if (!selectedUser) return;
const socket = useAuthStore.getState().socket;
socket?.on('newMessage', (newMessage) => {
const isMessageSentFromSelectedUser = newMessage.senderId === selectedUser._id;
if (!isMessageSentFromSelectedUser) return;
set({
messages: [...get().messages, newMessage],
});
});
},
unsubscribeFromMessages: () => {
const socket = useAuthStore.getState().socket;
socket?.off('newMessage');
},
'Node.js > Chat App' 카테고리의 다른 글
Cloudinary를 이용한 프로필 업데이트 기능 구현 (0) | 2025.04.29 |
---|