프로젝트를 진행하면서 socket.io로 채팅 기능을 만들고 있다.
자세한 socket에 대한 이야기를 적어보기 전에, io에 join 된 채팅방을 확인하고, 중복 조인이 안되도록 작업한 로직을 기록해보고자 한다.
// 기본 명령어
socket.connect ( 플랫폼에 접속한다 ) ex: kakaotalk 접속
socket.join ("e", 방에 접속한다 ) ex: kakaoTalk 가족방에 들어간다.
: join을 하게 되면 해당 방에 메세지를 받을 수 있다.
socket.emit ("e", 메세지를 보낸다 ) .to 로 특정 방에도 메세지를 보낼 수 있다.
: 양방향 소통 답게 서버에서 먼저 emit을 client로 보낼 수도 있다.
socket.on ("e", 메세지를 받는다 )
event name을 받아서 특정 event를 대상으로 주로 처리 된다.
socket.onSync("alert-new-message", (message) => {
console.log("message 받은 :>> ", message);
setUpdatedRoom(message);
});
이 기능은 chatroom이 join 된 상태에서 실행되기 때문이다.
내가 가진 채팅방의 메세지를 모두 받는 방을 조인하는 것도 대안이 될 수 있겠다.
useEffect(() => {
if (updatedRoom) {
const newChatRooms = [];
for (const value of chatList) {
if (value.chatRoom === updatedRoom.chatRoom) {
value.message = updatedRoom.message;
value.createAt = updatedRoom.createAt;
}
newChatRooms.push(value);
}
setChatList(newChatRooms);
}
}, [updatedRoom]);
useEffect(() => {
chatList?.forEach((item) => {
socket.joinRoom(String(item.chatRoom), (res) => {
if (res) {
console.log("join ===>", res);
}
});
// console.log(chatList);
});
}, [chatList]);
두개의 useEffect가 물리고 물려 계속 렌더링이 일어나는 것이다. 새로운 채팅이 들어오면 1번째 useEffect 가 실행되면서 chatlist state를 바꾼다. 그래서 다시 렌더링이 일어나게 되고, chatList를 디펜던시 어레이로 가진 2번째 useEffect가 실행되며 다시 join이 실행된다. 새로운 메세지가 올때마다 join이 다시 된다는 건 무척이나 좋지 않다. 사실 궁극적으로 해결해야하는 문제이긴 하지만, 일시방편으로 해결해보았다.
현재 socket 코드는 io의 은닉성을 위해 class로 관리되고 있다.
socket의 메서드들은 setter 함수를 만들어 사용했다.
//client socket class
joinRoom(roomId, cb) {
const socketId = this.io.id;
this.io.emit("joinRoom", { roomId, socketId }, cb);
}
일단 front 에서는 룸에 접속된 정보를 확인할 수 없다.
socket의 room 정보를 확인하기 위해 socketId를 함께 server 에 태워보낸다.
// server socket class
so.on('joinRoom', (obj: any, callback: any) => {
const { roomId, socketId } = obj;
if (!obj) callback('join에서 에러가 발생했어요.');
if (this.io.sockets.adapter.rooms.get(roomId)) {
console.log(
socketId +
'tried to join ' +
roomId +
' but the room does not exist.'
);
} else {
so.join(roomId);
callback(roomId);
console.log('room이 연결됨 :>>', roomId);
// Socket.join is not executed, hence the room not created.
}
}
먼저 에러 처리를 해주고,
this.io.sockets.adapter.rooms은 Map 자료 구조로 되어있다. 빌트인 메서드로 has, get 등을 가지고 있는데, get('key')값을 넣어주면 value 가 튀어나온다. 그래서 true 가되면 이미 존재하는 것으로 판단하고 console을 때려준다.
해당 조건에서 undefined
가 나오게 되면 조인을 추가해주고 해당 룸을 조인했다는 메세지를 client와 server에 뿌려주는 로직을 적용했다.
다른 조건으로 테스트해보니 전혀 잘못짠 코드였다. 방이 있을 때 조인이 아예 안되게 되므로 2명이 방에 못들어 가는 현상이 발생했다.
현재 채팅 접근 로직은 1개다.
chatlist => chat 으로 연결되는 로직인데, 여기서는 이미 생성된 채팅room 정보를 chatlist 에서 가지고 있기 때문에 chat 에 입장할 때 그 방을 찾아서 들어 갈 수 있었다.
장터기능을 구현하면서 추가될 사항은 새로운 채팅 접근을 뚫는 것이다.
1. seller와 user가 이미 채팅이 있는 경우, 채팅방을 찾아서 join 시켜줘야한다.
어떻게 구현할지 고민해봐야겠다.
so.on('joinRoom', (obj: any, callback: any) => {
if (!obj) callback('join에서 에러가 발생했어요.');
const { roomId, socketId } = obj;
const isRoom = this.io.sockets.adapter.rooms.get(roomId)?.size ?? 1;
//typeScirpt 때문에 undefind 를 안주기 위해 널병합 연산자로 1을 할당한다.
console.log(
'채팅방 확인>>>>>>>>>>',
this.io.sockets.adapter.rooms.get(roomId)?.has(socketId)
);
// console.log(this.io.sockets.adapter.rooms);
// console.log('위에서 찍히나요', isRoom);
// isRoom 이 있을 때, 2보다 작을 때, 내가 그 방에 없을 때
if (
isRoom &&
isRoom < 2 &&
!this.io.sockets.adapter.rooms.get(roomId)?.has(socketId)
) {
so.join(roomId);
callback(roomId);
console.log('room이 연결됨 :>>', roomId);
} else {
console.log(
socketId + 'tried to join roomNumber : ' + roomId + ' already join'
);
// Socket.join is not executed, hence the room not created.
}
});
const isRoom = this.io.sockets.adapter.rooms.get(roomId)?.size ?? 1;
if (isRoom && isRoom < 2 && !this.io.sockets.adapter.rooms.get(roomId)?.has(socketId)
)
isRoom 이 있을 때, 2보다 작을 때, 내가 그 방에 없을 때
해당 조건을 주어 join 중복을 제거했다.
chatlist > chat
MarketDetail > chat
다른 경로로 입장 시에도 Join이 잘 작동하게 했다. (중복없이)