본문 바로가기

Side Project

[PROJECT] Socket.io 를 이용한 채팅 애플리케이션 (5) - 채팅 기능 구현 (채팅방에 들어가보자 - 서버)

[PROJECT] Socket.io 채팅 애플리케이션 (5) - 채팅 기능 구현 (채팅방에 들어가기 - 서버)

Socket.io 채팅 애플리케이션 (5) - 채팅 기능 구현 (채팅방에 들어가보자 - 서버)

지난 시간에

지난 시간에는 채팅 창 ui 를 꾸며 보았다. 이제 채팅 관련 기능을 구현해야하는데 분량이 많으므로 5차시에 걸쳐 구현하도록 하겠다.

  • 채팅방에 들어가보자 (server / client)
  • 들어갔으니 채팅을 치자 (server / client)
  • 거의 다했으니 마무리를 하자

이번 차시에서는 채팅방에 들어가보자.

 

서버측 구현

채팅 방에 들어가는 플로우(흐름) 은 아주 간단하다. 이해하기 쉽게 클라이언트측 동작은 앞에 (c) , 서버측 동작은 앞에 (s) 를 붙히겠다.

  1. (c) 채팅방 목록중에 아무거나 클릭하면 join room 이벤트 발생
  2. (s) join room 이벤트 발생시 기존에 있던 방에서 나가고 새로운 방에 입장 socket.join()
  3. (s) 이제 방에 새로운 멤버가 들어왔으니 member list 업데이트 이벤트 발생 userlist
  4. (c) userlist 이벤트 발생시 memberWrap 에 있는 데이터를 서버한테 받은 데이터로 갱신
  5. (s) 유저가 disconnect 할때나 로그아웃할 때도 userlist update 이벤트 발생
  6. (c) 유저가 들어오거나 떠날 때 공지해주기

우선 서버측 부터 구현할 것 이므로 2, 3, 5 를 다같이 구현해보자.

 

2. (s) join room 이벤트 발생시 기존에 있던 방에서 나가고 새로운 방에 입장 socket.join()

# /app.js
io.sockets.on('connection', function(socket){
----------
    socket.on("login user", function (data, cb) {
        if (loginCheck(data)) {
            onlineUsers[data.id] = {roomId: 1, socketId: socket.id};
            socket.join('room1');
            cb({result: true, data: "로그인에 성공하였습니다."});
            updateUserList(0, 1, data.id); // 인자 설명은 3번에서...
        } else {
            cb({result: false, data: "등록된 회원이 없습니다. 회원가입을 진행해 주세요."});
            return false;
        }
    });
   	socket.on('join room', function (data) {
        let id = getUserBySocketId(socket.id);
        let prevRoomId = onlineUsers[id].roomId;
        let nextRoomId = data.roomId;
        socket.leave('room' + prevRoomId);
        socket.join('room' + nextRoomId);
        onlineUsers[id].roomId = data.roomId;
        updateUserList(prevRoomId, nextRoomId, id);
    });
    
});

사용자가 로그인을 했을때 default 로 입장되는 방은 Everyone 이고 이방의 roomId는 1 이므로 로그인 할때 socket.join('room1'); 을 이용해 Everyone 방에 입장하였다.

그리고 join room 이벤트가 발생하면 기존에 있던 방을 떠나고 새로운 방에 join 한다. 그리고 onlineUsers 에 있는 roomId 를 갱신해준다.

updateUserList(); 함수는 3번 에서 구현할 것이다.

지금까지 작성한 코드중에 이해되지 않은 코드가 있으면 댓글로 작성해주세요......

 

3. (s) 이제 방에 새로운 멤버가 들어왔으니 member list 업데이트 이벤트 발생 userlist

# /app.js
io.sockets.on('connection', function(socket){
----------
    function updateUserList(prev, next, id) {
        if (prev !== 0) {
            io.sockets.in('room' + prev).emit("userlist", getUsersByRoomId(prev));
            io.sockets.in('room' + prev).emit("lefted room", id);
            console.log("prev"+ prev);
        }
        if (next !== 0) {
            io.sockets.in('room' + next).emit("userlist", getUsersByRoomId(next));
            io.sockets.in('room' + next).emit("joined room", id);
            console.log("next"+ next);
        }
    }

    function getUsersByRoomId(roomId) {
        let userstemp = [];
        Object.keys(onlineUsers).forEach((el) => {
            if (onlineUsers[el].roomId === roomId) {
                userstemp.push({
                    socketId: onlineUsers[el].socketId,
                    name: el
                });
            }
        });
        return userstemp;
    }
}

자신이 떠난 방과 자신이 새로 들어온 방에만 이벤트를 보내 userlist 를 업데이트하는 코드이다.

값이 0인것을 검사한 이유는 로그인이나, 로그아웃, disconnect 와 같은 경우는 떠난 방, 들어온 방중 하나가 없다. 그래서 그 값을 0으로 처리해서 이 코드에서 검사를 해야 한다.

in({지정한 방이름}).emit("이벤트명", "보낼 데이터")

지정한 방에 연결된 socket 들에게만 이벤트가 발생한다. 위에서는 userlist 라는 이벤트를 각 방에 보내고 있다. 데이터는 getUsersByRoomId라는 함수를 이용해 현재 접속중인 user 들중 roomId가 일치하는 사람들의 배열을 보내고 있다.

Object.keys는 해당 Object의 키값을 배열로 만드는 것이고 foreach를 통해 roomId 검증을 진행하고 있다.

 

5. (s) 유저가 disconnect 할때나 로그아웃할 때도 userlist update 이벤트 발생

# /app.js
socket.on('logout', function () {
        if (!socket.id) return;
        let id = getUserBySocketId(socket.id);
        let roomId = onlineUsers[id].roomId;
        delete onlineUsers[getUserBySocketId(socket.id)];
        updateUserList(roomId, 0, id);
    });

    socket.on('disconnect', function () {
        if (!socket.id) return;
        let id = getUserBySocketId(socket.id);
        if(id === undefined || id === null){
            return;
        }
        let roomId = onlineUsers[id].roomId || 0;
        delete onlineUsers[getUserBySocketId(socket.id)];
        updateUserList(roomId, 0, id);
    });

disconnectlogout 에도 updateUserList() 를 호출하게 한다. 조금 특이 한점은 disconnect 에서 id 를 검증한다. logout 같은 경우는 로그인을 해야만 할 수 있는 것이여서 onlineUsers 를 따로 검사하지 않아도 되지만 disconnect 는 로그인을 하지낳고 나가는 경우 때문에 onlineUsers 의 유효성 검사를 했다.

 

이번에 작성한 전체 코드

# /app.js
var express = require('express'),
    port = process.env.PORT || 3000,
    app = express(),
    server = require('http').createServer(app),
    io = require('socket.io').listen(server),
    users = {
        'test': {
            id: 'test',
            pw: 'test'
        }
    },
    onlineUsers = {};

app.use(express.static('public'));

app.get('/', function (req, res) {
    res.redirect('/chat');
});

app.get('/chat', function (req, res) {
    res.sendfile(__dirname + '/chat.html');
});

server.listen(port, () => {
    console.log(`server open ${port}`);
});


io.sockets.on('connection', function (socket) {
    socket.on("join user", function (data, cb) {
        if (joinCheck(data)) {
            cb({
                result: false,
                data: "이미 존재하는 회원입니다."
            });
            return false;
        } else {
            users[data.id] = {
                id: data.id,
                pw: data.pw
            };
            cb({
                result: true,
                data: "회원가입에 성공하였습니다."
            });

        }
    });

    socket.on("login user", function (data, cb) {
        if (loginCheck(data)) {
            onlineUsers[data.id] = {
                roomId: 1,
                socketId: socket.id
            };
            socket.join('room1');
            cb({
                result: true,
                data: "로그인에 성공하였습니다."
            });
            updateUserList(0, 1, data.id);
        } else {
            cb({
                result: false,
                data: "등록된 회원이 없습니다. 회원가입을 진행해 주세요."
            });
            return false;
        }
    });

    socket.on('logout', function () {
        if (!socket.id) return;
        let id = getUserBySocketId(socket.id);
        let roomId = onlineUsers[id].roomId;
        delete onlineUsers[getUserBySocketId(socket.id)];
        updateUserList(roomId, 0, id);
    });

    socket.on('disconnect', function () {
        if (!socket.id) return;
        let id = getUserBySocketId(socket.id);
        if(id === undefined || id === null){
            return;
        }
        let roomId = onlineUsers[id].roomId || 0;
        delete onlineUsers[getUserBySocketId(socket.id)];
        updateUserList(roomId, 0, id);
    });

    socket.on('join room', function (data) {
        let id = getUserBySocketId(socket.id);
        let prevRoomId = onlineUsers[id].roomId;
        let nextRoomId = data.roomId;
        socket.leave('room' + prevRoomId);
        socket.join('room' + nextRoomId);
        onlineUsers[id].roomId = data.roomId;
        updateUserList(prevRoomId, nextRoomId, id);
    });

    function updateUserList(prev, next, id) {
        if (prev !== 0) {
            io.sockets.in('room' + prev).emit("userlist", getUsersByRoomId(prev));
            io.sockets.in('room' + prev).emit("lefted room", id);
        }
        if (next !== 0) {
            io.sockets.in('room' + next).emit("userlist", getUsersByRoomId(next));
            io.sockets.in('room' + next).emit("joined room", id);
        }
    }

    function loginCheck(data) {
        if (users.hasOwnProperty(data.id) && users[data.id].pw === data.pw) {
            return true;
        } else {
            return false;
        }
    }

    function joinCheck(data) {
        if (users.hasOwnProperty(data.id)) {
            return true;
        } else {
            return false;
        }
    }

    function getUserBySocketId(id) {
        return Object.keys(onlineUsers).find(key => onlineUsers[key].socketId === id);
    }

    function getUsersByRoomId(roomId) {
        let userstemp = [];
        Object.keys(onlineUsers).forEach((el) => {
            if (onlineUsers[el].roomId === roomId) {
                userstemp.push({
                    socketId: onlineUsers[el].socketId,
                    name: el
                });
            }
        });
        return userstemp;
    }
});

헷갈리 것 같아 준비했다. 위에 코드를 이해 했으면 그냥 복붙해도 상관없다.

 

다음 시간에

다음 시간에는 이제 서버로 부터 받은userlist를 화면에 바인딩하는 작업을 해보자.