Websocket과 Socket.io의 개념에 대해서 정리
Node.js 환경에서 실시간 1:1 채팅을 구현하기 위해서 socket.io를 사용했다.
emit과 on으로만 구현하였고, 수정이나 삭제는 구현하지 않았다.
채팅 시작 전
- 채팅 목록은 room 테이블과 연결되어 있어서 채팅 페이지에 들어가게 되면 axios 요청을 통해 mysql(DB)에서 데이터를 가져온다.
1. 채팅 시작 시
- 프론트에서 채팅에 들어온 유저와 방 번호를 emit(서버에 요청)한다. 만약 이 때 채팅했던 데이터가 있다면 axios요청을 통해 DB에서 데이터를 가져온다.
- 백에서는 받아서 접속한 유저, 방 번호를 설정한다.
// front
// 채팅 목록에서 채팅방 클릭 시 DB에서 chat 가져오기
useEffect(() => {
axios.get('chat/getData', { params: { id: chatData.id } }).then((res) => {
res.data.forEach((el, idx) => {
if (el.userId === user) {
chat.current.insertAdjacentHTML(
'beforeend',
`<div class='${styles.myChat} ${styles[categoryType]}'>` +
`<span>${el.time}</span>` +
`<div>` +
`${el.msg}` +
'</div>' +
'</div>'
);
} else {
chat.current.insertAdjacentHTML(
'beforeend',
`<div class=` +
`${styles.otherChat}>` +
`${el.time}` +
`<div>` +
`${el.msg}` +
'</div>'
);
}
});
});
}, []);
// socket으로 연결
let socket = io.connect('https://bandari.store:5000');
// 현재 채팅에 들어온 유저와 방 번호
socket.emit('loginUser', { user: user, roomId: chatData.id });
// back
// 접속한 유저, 방 번호, 방
let loginUser = '';
let roomId = '';
let rooms = [];
const io = require('socket.io')(https, {
cors: {
orgin: ['https://bandari.store'],
credentials: true,
},
});
io.on('connection', (socket) => {
// 방 입장 시 로그인 한 유저와 방이름
socket.on('loginUser', (data) => {
loginUser = data.user;
roomId = data.roomId;
// rooms에 있으면 roomId 추가 xx
if (!rooms.includes(roomId)) {
rooms.push(roomId);
}
socket.join(roomId);
});
2. 채팅 전송 후 표시
- 프론트에서 데이터를 모아서 서버에 emit(요청)하고, axios 요청으로 chat 테이블에 채팅 내용 저장(post 요청)
- 백에서는 sendMsg로 받고 다시 백에서 newMsg 데이터 요청해서 채팅 화면 표시(current.insertAdjacentHTML로 실시간 채팅방 구현
// front
/*전송이벤트 */
const btnSend = () => {
const inputText = inputRef.current.value;
// 시간
let hourMin = new Date().toTimeString().split(' ')[0];
hourMin = hourMin.substring(0, hourMin.lastIndexOf(':'));
const datas = {
msg: inputText,
time: hourMin,
userId: user,
roomId: chatData.id,
};
socket.emit('sendMsg', datas);
axios.post('chat/insert', datas);
inputRef.current.value = '';
resetScroll();
};
socket.on('newMsg', (data) => {
if (user === data.userId) {
chat.current.insertAdjacentHTML(
'beforeend',
`<div class='${styles.myChat} ${styles[categoryType]}'>` +
`<span>${data.time}</span>` +
`<div>` +
`${data.msg}` +
'</div>' +
'</div>'
);
} else {
chat.current.insertAdjacentHTML(
'beforeend',
`<div class=` +
`${styles.otherChat}>` +
`${data.time}` +
`<div>` +
`${data.msg}` +
'</div>'
);
}
// back
// 메시지 데이터
socket.on('sendMsg', (data) => {
io.to(roomId).emit('newMsg', data);
});
// axios post 요청
exports.postInsert = async (req, res) => {
const result = await chat.create(req.body);
res.send(true);
};
3. 채팅 나가기 및 채팅 종료하기
- 채팅 나가기는 x버튼을 클릭해 현재 창에서 채팅 나가기이고, 종료하기는 채팅방의 모든 내용까지 같이 삭제된다.
- 삭제되면 현재 연결되어 있는 rooms 중에서 연결이 끊긴 방만 나가게 된다
// front
/**채팅 방만 나가기 */
const onClickClose = () => {
chatRef.current.classList.add(`${styles.transparent}`);
socket.disconnect();
setSelectChat(false);
};
/** 채팅 종료 버튼 이벤트 : 채팅 삭제 */
const onClickExit = () => {
if (window.confirm(
'채팅을 종료하시겠습니까? 채팅방의 모든 내용이 삭제됩니다.')
) {
axios.delete('room/delete', { data: { id: chatData.id } }).then((res) => {
if (res.data.result === 1) alert('성공적으로 채팅방에서 나가졌습니다!');
else alert('이미 채팅방에서 나가졌습니다.');
socket.disconnect();
onClickClose();
window.location.reload();
});
}
// back
// x버튼으로 채팅방 나가기
socket.on('disconnect', () => {
rooms.forEach((el, idx) => {
if (el == roomId) {
rooms.splice(idx, 1);
}
});
console.log(`${loginUser}가 ${roomId}방을 나갔습니다.`);
console.log('delete 후의 rooms', rooms);
});
Server 쪽 채팅 전체 코드
// 접속한 유저, 방 번호, 방
let loginUser = '';
let roomId = '';
let rooms = [];
const io = require('socket.io')(https, {
cors: {
orgin: ['https://bandari.store'],
credentials: true,
},
});
io.on('connection', (socket) => {
// 방 입장 시 로그인 한 유저와 방이름
socket.on('loginUser', (data) => {
loginUser = data.user;
roomId = data.roomId;
// rooms에 있으면 roomId 추가 xx
if (!rooms.includes(roomId)) {
rooms.push(roomId);
}
socket.join(roomId);
});
// 메시지 데이터
socket.on('sendMsg', (data) => {
io.to(roomId).emit('newMsg', data);
});
// x버튼으로 채팅방 나가기
socket.on('disconnect', () => {
rooms.forEach((el, idx) => {
if (el == roomId) {
rooms.splice(idx, 1);
}
});
console.log(`${loginUser}가 ${roomId}방을 나갔습니다.`);
console.log('delete 후의 rooms', rooms);
});
});
4. 판매완료 시
- 구매자던 판매자던 판매 완료 버튼을 누르게 되면 판매 테이블(supplies)의 deal 컬럼이 false로 바뀌게 되면서 sold out로 변경되며 다른 유저는 연락할 수 없다.(판매 상세 페이지에서 채팅 버튼 비활성화)
느낀점 🤔
- 생각보다 socket.io에 있는 다양한 기능들을 써보지 않았어서, 그렇게 백쪽에서는 코드가 길지 않았던 것 같다.
- socket.io의 emit과 on으로만으로도 실시간 채팅을 구현할 수 있어서 효율적이었던 것 같다.
- socket.io를 사용해 채팅 DB와 실시간 소통을 구현하면서 상대방과 채팅을 할 때마다 DB(Mysql)에 내용을 저장하고, 채팅방에 나갔다가 다시 들어올 때 DB에서 데이터를 다시 가져오게 된다. 그래서 만약 사용자가 많아지게 되면 서버에 부하가 너무 많이 가니까 고민했었다.
- 고민했었던 것들
- 유저들이 나눴던 채팅 데이터들을 임시에 객체에 저장해놨다가, 한 번에 Insert 하기
- select로 채팅 데이터를 조회할 때 채팅방 ID를 기준으로 chat 테이블을 파티션을 하기
- 채팅 내용을 DB에 저장하지 않고, 중요한 것은 물품 거래이니까 일정 테이블을 따로 만들어서 그 일정만 저장해서 채팅내용은 휘발성으로 날리기
- 고민했었던 것들
'Sesac 웹 풀스택[새싹X코딩온] > 회고록 및 TIL' 카테고리의 다른 글
1차 프로젝트 회고록(새싹 냉장고) (0) | 2023.03.02 |
---|