본문 바로가기
개발/Node.js

[Node.js] socket.io로 채팅 서버 만들기 2 - 메시지 보내기

by 윤호 2021. 2. 1.

socket.io로 채팅 서버 만들기 1에 이어서 채팅 서버를 통해 클라이언트 간 메시지를 주고 받아 보겠습니다.

목차

  • 채팅서버 작동 방식
  • 1 : ALL 채팅 메시지 보내기
  • 1 : 1 채팅 메시지 보내기

채팅서버 작동 방식

저번 글에서 설명한 작동방식에 대해 추가적으로 설명 하겠습니다.

 

socket.io를 사용한 채팅서버의 작동

socket.io는 서버와 클라이언트 양쪽에서 모두 따로 사용하며 서버용 socket.io와 클라이언트용 socket.io는 서로 다릅니다. 채팅 메시지를 주고받는 과정을 간단히 말하면 이벤트로 클라이언트 socket.io가 서버 socket.io에 메시지를 보내고, 메시지를 받은 서버는 적절한 클라이언트에게 받은 메시지를 보냅니다.

 

즉, 메시지를 보내는 과정은 이벤트를 정의하고 호출하는 방법으로 이루어집니다.

 

서버에서 socket.emit() / io.emit()을 하면 클라이언트에서 이벤트가 처리되고, 클라이언트에서 socket.emit()을 하면 서버에서 이벤트가 처리됩니다. 이 부분이 굉장히 헷갈렸습니다. on메소드로 이벤트 정의는 클라이언트에서 했는데 클라이언트에선 해당 이벤트를 호출할 수 없습니다.

1 : ALL 채팅 메시지 보내기

 

채팅 메시지를 보내는 과정은 다음과 같습니다.

1.  클라이언트가 messageS 이벤트를 호출하여 서버에 message를 전달한다.

2.  서버에서 messageS 이벤트를 처리하고 messageC를 호출하여 모든 클라이언트에게 message를 전달한다.

3.  클라이언트에서 messageC 이벤트를 처리하여 message를 브라우저에 출력한다.

 

클라이언트(messageS 호출) -> 서버(messageS 처리) -> 서버(messageC 호출) -> 클라이언트(messageC 처리)

 

이를 코드로 구현해 보겠습니다.

서버 사이드 - app.js

io.sockets.emit()은 연결된 모든 클라이언트의 이벤트를 호출합니다.

이를 통해 모든 클라이언트에게 메시지를 보낼 수 있습니다.

 

참고로 socket.emit()은 해당 클라이언트 소켓의 이벤트만 호출합니다.

 

클라이언트한테 메시지를 받았을 때 처리하는 이벤트 messageS를 정의합니다.

// 클라이언트가 연결했을 때의 이벤트 처리
io.on("connection", (socket) => {
    console.log('connection info : ', socket.request.connection._peername);

    // 클라이언트로 부터 'messageS' 이벤트를 받았을 때의 처리
    socket.on('messageS', (message) => {
        console.dir(message);
        console.log('message 이벤트를 받았습니다.');

        if(message.recepient === 'ALL'){
            // 나를 포함한 모든 클라이언트에게 메시지 전달
            console.dir('나를 포함한 모든 클라이언트에게 message 이벤트를 전송합니다.');
            io.sockets.emit('messageC', message);
        }
    });
});

 

 

app.js 모든 코드

더보기
var express = require('express')
    , http = require('http')
    , path = require('path');

var static = require('serve-static');

var app = express();

app.set('port', process.env.PORT || 3000);
app.use('/public', static(path.join(__dirname, 'public')));

//==== 서버 실행 ====//
const httpServer = http.createServer(app).listen(app.get('port'), function(){
    console.log('서버가 시작되었습니다. 포트 : ', app.get('port'));
});

//==== 채팅 서버 ====//
const io = require("socket.io")(httpServer);
console.log('socket.io 요청을 받아들일 준비가 되었습니다.');

// 클라이언트가 연결했을 때의 이벤트 처리
io.on("connection", (socket) => {
    console.log('connection info : ', socket.request.connection._peername);

    // step2. 클라이언트 소켓에서 해당 이벤트를 호출
    // 'message' 이벤트를 받았을 때의 처리
    socket.on('messageS', (message) => {
        console.dir(message);
        console.log('message 이벤트를 받았습니다.');

        if(message.recepient === 'ALL'){
            // 나를 포함한 모든 클라이언트에게 메시지 전달
            console.dir('나를 포함한 모든 클라이언트에게 message 이벤트를 전송합니다.');
            // step3. 연결된 모든 클라이언트 소켓에 messageC 이벤트를 호출
            io.sockets.emit('messageC', message);
        }
    });
});

클라이언트 사이드 - chat.html

메시지 전송 버튼 sendButton을 추가하고 기능을 정의합니다.

<script>
    $('#sendButton').bind('click', (event) => {
        var sender = $('#senderInput').val();
        var output = {sender: sender, recepient: 'ALL', data: '모두에게 안녕'};
        console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
        if(socket == undefined){
            alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
            return;
        }
        // 서버의 messageS 이벤트를 호출
        socket.emit('messageS', output);
    });
</script>

 

서버로 부터 메시지를 받았을 때 처리하는 이벤트 messageC를 정의합니다.

<script>
    socket.on('connect', () => {
        println('웹소켓 서버에 연결되었습니다. : ' + url);
        console.log('socket.id : ' + socket.id);

        // 서버로 부터 messageC 이벤트를 받았을 때 처리
        socket.on('messageC', (message) => {
            console.log(JSON.stringify(message));

            println('<p>수신 메시지 : ' + message.sender + ', '
            	+ message.recepient + ', ' + message.data + '</p>');
        });
    });
</script>

 

chat.html 모든 코드

더보기
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>1 : ALL채팅 클라이언트</title>

    <script src="jquery-3.1.1.min.js"></script>
    <script src="socket.io/socket.io.js"></script>

    <script>
        var host;
        var port;
        var socket;

        // 문서 로딩 후 실행됨
        $(function() {

            $("#connectButton").bind('click', (event) => {
                println('connectButton이 클릭되었습니다.');

                host = $('#hostInput').val();
                port = $('#portInput').val();

                connectToServer();
            });

            $('#sendButton').bind('click', (event) => {
                var sender = $('#senderInput').val();

                var output = {sender: sender, recepient: 'ALL', data: '모두에게 안녕'};
                console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                if(socket == undefined){
                    alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                    return;
                }

                // step1. 서버 소켓에 message 이벤트를 호출
                socket.emit('messageS', output);
            });

        });

        // 서버에 연결하는 함수 정의
        function connectToServer() {

            var options = {'forceNew':true}; // 기존의 연결을 재사용 할 수 있도록
            var url = 'http://' + host + ':' + port;
            socket = io(url, options); // io는 socket.io.js 에서 선언된 전역변수임

            socket.on('connect', () => {
                println('웹소켓 서버에 연결되었습니다. : ' + url);
                console.log('socket.id : ' + socket.id);

                // step4. 서버 소켓에 의해 messageC 이벤트가 호출되고 화면에 메시지가 나옴
                socket.on('messageC', (message) => {
                    console.log(JSON.stringify(message));

                    println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.data + '</p>');
                });
            });

            socket.on('disconnect', () => {
                println('웹소켓 연결이 종료되었습니다.');
                console.log('socket.id : ' + socket.id);
            });

        }

        function println(data) {
            console.log(data);
            $('#result').append('<p>' + data + '</p>');
        }
    </script>
</head>
<body>
    <h3>채팅 클라이언트 02</h3>
    <br>
    <div>
        <input type="text" id="hostInput" value="localhost" />
        <input type="text" id="portInput" value="3000" />

        <input type="button" id="connectButton" value="연결하기" />
    </div>
    <div>
        <div><span>보내는사람 아이디 :</span> <input type="text" id="senderInput" value="test01" /></div>
        <br>
        <input type="button" id="sendButton" value="전송" />
    </div>

    <hr/>
    <p>결과 : </p>
    <div id="result"></div>

</body>
</html>

1 : 1 채팅 메시지 보내기

 

메시지를 보내는 과정은 앞의 내용과 비슷합니다.

소켓 id를 저장하고 소켓 id로 특정 클라이언트 소켓을 지정하는 과정이 추가됩니다.

 

1. 서버 소켓에서 클라이언트 소켓과 연결 될 때 해당 소켓의 id를 유저(로그인) id와 매핑하여 저장한다.

2. 서버에선 특정 소켓 id를 가져와 특정 클라이언트에게 메시지를 보낼 수 있다.

 

socket.id

연결된 클라이언트 소켓의 id 속성을 참조하여 소켓 id를 가져올 수 있습니다.

 

io.sockets.connected[소켓 id].emit('message', message);

connected 메소드를 추가하여 특정 클라이언트 소켓의 이벤트를 호출할 수 있습니다.

(connected 메소드가 없으면 모든 클라이언트 소켓의 이벤트를 호출합니다.)

 

코드는 다음과 같습니다.

app.js

더보기
var express = require('express')
    , http = require('http')
    , path = require('path');

var static = require('serve-static');

var app = express();

app.set('port', process.env.PORT || 3000);
app.use('/public', static(path.join(__dirname, 'public')));

//==== 서버 실행 ====//
const httpServer = http.createServer(app).listen(app.get('port'), function(){
    console.log('서버가 시작되었습니다. 포트 : ', app.get('port'));
});

//==== 채팅 서버 ====//
const io = require("socket.io")(httpServer);
console.log('socket.io 요청을 받아들일 준비가 되었습니다.');

// 로그인 아이디 매핑 (로그인 ID -> 소켓 ID)
var login_ids = {};

// 클라이언트가 연결했을 때의 이벤트 처리
io.on("connection", (socket) => {
    console.log('connection info : ', socket.request.connection._peername);

    // 'login' 이벤트를 받았을 때의 처리
    socket.on('login', function(login) {
        console.log('login 이벤트를 받았습니다.');
        console.dir(login);

        // 기존 클라이언트 ID가 없으면 클라이언트 ID를 맵에 추가
        console.log('접속한 소켓의 ID : ' + socket.id);
        login_ids[login.id] = socket.id;
        socket.login_id = login.id;

        console.log('접속한 클라이언트 ID 갯수 : %d', Object.keys(login_ids).length);

        // 응답 메시지 전송
        sendResponse(socket, 'login', '200', '로그인되었습니다.');
    });

    // step2. 클라이언트 소켓에서 해당 이벤트를 호출
    // 'message' 이벤트를 받았을 때의 처리
    socket.on('messageS', (message) => {
        console.dir(message);
        console.log('message 이벤트를 받았습니다.');

        if(message.recepient === 'ALL'){
            // 나를 포함한 모든 클라이언트에게 메시지 전달
            console.dir('나를 포함한 모든 클라이언트에게 message 이벤트를 전송합니다.');
            // step3. 연결된 모든 클라이언트 소켓에 messageC 이벤트를 호출
            io.sockets.emit('messageC', message);
        } else {
            // 일대일 채팅 대상에게 메시지 전달
            if (login_ids[message.recepient]) {
                // 클라이언트에 메시지 전송
                io.sockets.connected[login_ids[message.recepient]].emit('messageC', message);
                console.log('전달할 클라이언트 소켓 id : ' + login_ids[message.recepient])
                // 응답 메시지 전송
                sendResponse(socket, 'message', '200', '메시지를 전송했습니다.');
            } else {
                // 응답 메시지 전송
                sendResponse(socket, 'login', '404', '상대방의 로그인 ID를 찾을 수 없습니다.');
            }
        }
    });
});

// 응답 메시지 전송 메소드
function sendResponse(socket, command, code, message) {
    var statusObj = {command: command, code: code, message: message};
    socket.emit('response', statusObj);
}

 

chat.html

더보기
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>1 : 1채팅 클라이언트</title>

    <script src="jquery-3.1.1.min.js"></script>
    <script src="socket.io/socket.io.js"></script>

    <script>
        var host;
        var port;
        var socket;
        var id

        // 문서 로딩 후 실행됨
        $(function() {

            // 연결 버튼 클릭 시 처리
            $("#connectButton").bind('click', (event) => {
                println('connectButton이 클릭되었습니다.');

                host = $('#hostInput').val();
                port = $('#portInput').val();

                connectToServer();
            });

            // 로그인 버튼 클릭 시 처리
            $("#loginButton").bind('click', function(event) {
                id = $('#idInput').val();

                var output = {id:id};
                console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                if (socket == undefined) {
                    alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                    return;
                }

                socket.emit('login', output);
                socket.emit('me');
            });

            // 전송 버튼 클릭 시 처리
            $('#sendButton').bind('click', (event) => {
                var sender = id
                var recepient = $('#recepientInput').val();
                var data = $('#dataInput').val();

                var output = {sender: sender, recepient: recepient, command: 'chat', type: 'text', data: data};
                console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));

                if(socket == undefined){
                    alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
                    return;
                }

                // step1. 서버 소켓에 message 이벤트를 호출
                socket.emit('messageS', output);
            });

        });

        // 서버에 연결하는 함수 정의
        function connectToServer() {

            var options = {'forceNew':true}; // 기존의 연결을 재사용 할 수 있도록
            var url = 'http://' + host + ':' + port;
            socket = io(url, options); // io는 socket.io.js 에서 선언된 전역변수임

            socket.on('connect', () => {
                println('웹소켓 서버에 연결되었습니다. : ' + url);
                console.log('socket.id : ' + socket.id);

                // step4. 서버 소켓에 의해 messageC 이벤트가 호출되고 화면에 메시지가 나옴
                socket.on('messageC', (message) => {
                    console.log(JSON.stringify(message));

                    println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', '
                        + message.command + ', ' + message.type + ', ' + message.data + '</p>');
                });

                socket.on('response', function(response) {
                    console.log(JSON.stringify(response));
                    println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
                });

            });

            socket.on('disconnect', () => {
                println('웹소켓 연결이 종료되었습니다.');
                console.log('socket.id : ' + socket.id);
            });

        }

        function println(data) {
            console.log(data);
            $('#result').append('<p>' + data + '</p>');
        }
    </script>
</head>
<body>
    <h3>채팅 클라이언트 01</h3>
    <br>
    <div>
        <input type="text" id="hostInput" value="localhost" />
        <input type="text" id="portInput" value="3000" />

        <input type="button" id="connectButton" value="연결하기" />
    </div>
    <div>
        <input type="text" id="idInput" value="test01" />

        <input type="button" id="loginButton" value="로그인" />
        <input type="button" id="logoutButton" value="로그아웃" />
    </div>
    <br>
    <div>
        <div><span>받는사람 아이디 :</span> <input type="text" id="recepientInput" value="ALL" /></div>
        <div><span>메시지 데이터 :</span> <input type="text" id="dataInput" value="안녕!"/> </div>
        <br>
        <input type="button" id="sendButton" value="전송" />
    </div>

    <hr/>
    <p>결과 : </p>
    <div id="result"></div>

</body>
</html>

 

Reference

댓글