이 글은 아래의 메세지 채팅 기능 구현하기 (프론트 코드)와 이어지는 글입니다! 이번 글에서는 백엔드 코드에 대해서 적어볼 예정입니다.
메세지 채팅 기능 (웹소켓, redis, 실시간 알람 기능)
미리보기 📁 채팅 기능 미리보기 실시간 소통을 위해서 웹소켓 통신 방법을 사용하였고, 채팅 데이터를 저장하기 위해서 redis를 사용했다. redis 를 사용하게 된 이유는 기존에 사용하던 mysql 데
in-my-universe23.tistory.com
미리보기
📁 채팅 기능 미리보기

백엔드
웹소켓을 사용해서 실시간 통신을 구축하고 redis를 통해서 유저들 소켓 아이디, 소켓 채팅 데이터 등을
- Redis 클라이언트 설정 및 연결 : 데이터 베이스 연결을 설정하고 관리
- 소켓 이벤트 핸들러 : 클라이언트와 실시간 통신 관리
- login : 사용자가 로그인할때마다 사용자 소켓 아이디를 저장하여 나중에 사용자에게 메세지를 보낼 수 있도록 함
- REQUEST_DATA : 사용자가 채팅페이지를 열 때 이전 채팅 데이터를 불러오는데 사용
- START_CHAT : 새로운 채팅방을 개설할려고 했을때 사용
- SEND_MESSAGE : 사용자가 채팅방에 메세지를 보낼때 사용
실제 구현한 코드
Redis 클라이언트 설정 및 연결 : 서버와 데이터베이스 사이의 연결 담당
const redisClient = redis.createClient({
url: `redis://${process.env.REDIS_USERNAME}:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
legacyMode: true, // 반드시 설정 !!
});
redisClient.on("connect", () => {
console.info("Redis connected!");
});
redisClient.on("error", (err: any) => {
console.error("Redis Client Error", err);
});
redisClient.connect().then(); // redis v4 연결 (비동기)
const redisCli = redisClient.v4; // 기본 redisClient 객체는 콜백기반인데 v4버젼은 프로미스 기반이라 사용
io.on("connection", (socket: any) => {
//connection
socket.on("disconnect", async () => {
await redisCli.SREM("currentUser", `${socket.id}`).then(() => {
clearInterval(socket.interval);
});
console.log("클라이언트 접속 해제");
});
//* 에러 시
socket.on("error", (error: any) => {
console.error(error);
});
login 이벤트 : 로그인하면 유저 소켓아이디 데이터 저장하고 현재 접속한 유저들을 보여주는 currentUser 집합에도 넣어줌
- key: 유저 아이디, value: 유저 소켓아이디 형식으로 저장
- currentUser 키를 중심으로 소켓아이디를 계속 넣어줌.
🚨 만약 현재 유저에 대한 소켓아이디 데이터가 존재한다면, 이전 데이터는 지우고 새로 저장
socket.on("login", async (data: any) => {
await socket.join("client");
await Users.findOne({
where: {
email: data.email,
},
}).then(async (r: any) => {
const checkKey = await redisCli.EXISTS(`${r.user_id}`);
if (checkKey) {
await redisCli.GET(`$(r.user_id)`).then(() => {
redisCli.DEL(`${r.user_id}`);
});
}
await redisCli.SET(`${r.user_id}`, `${socket.id}`);
await redisCli.SADD("currentUser", `${socket.id}`);
const checkCurrent = await redisCli.SMEMBERS("currentUser");
});
// Redis에 userID와 socketID를 저장한다.
});
START_CHAT : 유저가 새로운 채팅을 시작할려고 할 경우, 해당 채팅방 존재 유무를 확인하고 이미 존재할 경우 해탕 채팅방의 메세지 데이터를 넘겨주고 채팅방이 존재하지 않는 경우 새로운 채팅방 정보를 redis에 저장.
socket.on("START_CHAT", async (data: any) => {
const roomId = `chat-${data.users.sort().join("-")}`;
// Redis에서 해당 roomId의 존재 여부를 확인
const chatExist = await redisCli.EXISTS(roomId);
if (chatExist) {
// 채팅방이 이미 존재하는 경우, 해당 채팅방의 메시지 데이터 불러오기
const allData = await redisCli.ZRANGE(roomId, 0, -1);
if (allData.length > 0) {
io.to(socket.id).emit("BEFORE_DATA", roomId);
}
} else {
// 채팅방이 존재하지 않는 경우, 새로운 채팅방 정보를 Redis에 저장
console.log("Creating new chat room:", roomId);
await redisCli.SET(`roomId:${roomId}`, JSON.stringify(data.users));
}
});
REQUEST_DATA : 페이지가 처음 로딩될때 프론트로부터 현재 로그인한 유저 정보를 받아서 유저가 속한 모든 채팅방 데이터를 조회함
⇒ 조회 후에 RESPOND_DATA 통신으로 프론트에 조회한 데이터를 보내줌
// 메세지 페이지 처음 로딩할때 체팅 데이터 가져오기
socket.on("REQUEST_DATA", async (data: any) => {
const findData = await redisCli.KEYS(`chat-*${data.id}*`);
let respondData: any = [];
await Promise.all(
findData.map(async (t: any) => {
let chatData = await redisCli.ZRANGE(`${t}`, 0, -1);
if (chatData[0] === undefined) {
return redisCli.DEL(t);
} else {
chatData.forEach((item: any) => {
const parsedItem = JSON.parse(item);
return respondData.push(parsedItem);
});
}
})
);
io.to(socket.id).emit("RESPOND_DATA", respondData);
});
SEND_MESSAGE : 프론트에서 보낸 채팅 데이터를 받고 redis에 저장후 메세지를 받는 사람의 소켓 id로 실시간으로 메세지 전송
socket.on("SEND_MESSAGE", async (m: any) => {
console.log(`채팅 도착 ${m.message}`);
let messageData = {
send: `${m.send}`,
receive: `${m.receive}`,
message: `${m.message}`,
date: `${m.date}`,
roomId: `${m.roomId}`,
};
let change = JSON.stringify(messageData);
let score = Number(m.score);
await redisCli.ZADD(`${m.roomId}`, { score: score, value: change });
// 메시지를 받는 사람의 소켓 ID를 Redis에서 조회
let getSocketId = await redisCli.GET(`${m.receive}`);
// 조회한 소켓 ID로 실시간으로 메시지 전송
if (getSocketId) {
io.to(getSocketId).emit("RECEIVE_MESSAGE", change);
} else {
console.log("소켓 ID를 찾을 수 없습니다.");
}
});
참고사이트
NodeJS를 이용한 채팅서버 구축 하기(express, Socket.io, Redis) -2-
Socket.IO - Redis adapter Redis 어댑터는 Pub/Sub 메커니즘에 의존합니다.
[REDIS] 📚 Node.js 에서 redis 모듈 사용법 (캐싱 & 세션 스토어)
[REDIS] 📚 레디스 소개 & 사용처 (캐시 / 세션) - 한눈에 쏙 정리
AWS Elasticbeanstalk + Node.js + Socket.io + Redis를 이용한 채팅서버 (2)
'sns 프로젝트' 카테고리의 다른 글
JSON 문자열 JAVASCRIPT 객체 변환 오류 (0) | 2024.01.25 |
---|---|
실시간 채팅 기능 수정하기(실시간 채팅 업데이트) (1) | 2024.01.24 |
메세지 채팅 기능 (웹소켓, redis, 실시간 알람 기능) (1) | 2024.01.24 |
SearchBar 검색기능 만들기 (1) | 2024.01.23 |
트위터 팔로우 기능 구현하기(React, CSS hover기능) (1) | 2024.01.22 |