IT Study/Nodejs 채팅서버 튜토리얼

[Nodejs] 채팅구현하기 1 - SocketIO (처음부터 끝까지)

ComputerScientist 2020. 11. 24. 18:15

Nodejs + socketio 를 활용하여 채팅을 구현해보자.

 

이 튜토리얼의 최종 목표 화면이다

 

필수 설치 목록

 


사용하게 될 스펙 및 중요 모듈

  • Back-end : nodejs, express, socket.io,
  • Front-end : jquery, bootstrap(css), 

순서 (대기실 구성하기 - 일반 채팅)

  1. Express를 활용하여 웹서버 띄우기
  2. 프론트 그리기
  3. SocketIO를 를 붙여서 서버와 클라이언트(브라우저) 연결

다음편 순서 (채팅'방' 구현하기)


 

 

 

 

1. Express를 활용하여 웹서버 띄우기

원하는 폴더를 생성하고, 그 폴더에서 

(~/working_folder)

$ npm init

을 실행한다.

 

 

 

npm init 을 하고나서 주로 enter로 다 넘기는데, entry point 설정은 app.js 로 설정한다. (개인마다 다른데, 보통 app.js를 많이 한다)

// ~/working_folder/package.json

{
  "name": "mann-chat-web",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

npm init 을 한 뒤 위와 같이 package.json 파일이 생성된다.

(package.json 은 npm을 활용하여 소스코드를 관리할 때 생성되는 파일로, 각종 repository 및 명령어를 저장하는 등의 역할을 한다.)

 

package.json을 확인 한 후에, 다음으로 할 게 app.js (혹은 index.js)를 작성하는 것이다.

app.js 를 작성하기 전에, 이 튜토리얼에서는 express를 활용하여 서버를 구성할 것이므로,

// (~/working_folder)

$ npm intall --save express

위와 같이 express 모듈을 설치한다. 

 

 

 

express 모듈을 설치한 뒤, app.js를 작성한다.

// ~/working_folder/app.js

const app = require('express')();
const http = require('http').createServer(app);

app.get('/', (req, res) => {
    res.send("Hello world");
});

http.listen(3000, () => {
    console.log('Connected at 3000');
});

위처럼 app.js를 작성하자.

  • nodejs 위 express 모듈을 활용하여 웹서버를 띄우는 코드이며,
  • port는 3000포트를 사용하게 될 서버이며,
  • localhost:3000 에 접속하면 "Hello world"를 보내게 되어있음을 알 수 있다.

아직 socket.io 모듈을 활성화 시키지 않았다.

 

 

 

그럼 이제 app.js 로 실행하여 서버를 띄워보자

// (~/working_folder)

$ node app.js

해당 스크립트를 실행 후, 브라우저에서 localhost:3000 으로 접속하면, Hello World 메시지가 확인된다.

 

 

 

 

 

 

 

2. 프론트 그리기

다음으로는 채팅창을 그려보자. (index.html)

(bootstrap css 에 대해서는 크게 설명하진 않겠다. )

<!-- ~/working_folder/index.html -->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Socket Tester</title>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <style>
    #messages { list-style-type: none; margin: 0; padding: 0; }
    #messages li { padding: 5px 10px; }
    #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <div class="row">

      <!-- 대기실 -->
      <div class="col-lg-4">
        <div class="card">
          <div class="card-header">
            대기실
          </div>
          <div class="card-body">
            <form action="">
              <div class="input-group mb-3">
                <input type="text" class="form-control" id="m" autocomplete="off" />
                <div class="input-group-append">
                  <button id="msg-send" class="btn btn-primary" placeholder="message">Send</button>
                </div>
              </div>
            </form>
          </div>
          <div class="card-footer">
            <ul id="messages"></ul>
          </div>
        </div>
      </div>


      <!-- 방선택 -->
      <div class="col-lg-8"></div>
    </div>
    
    <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
  </body>
</html>

스타일에 대해 간략하게 얘기를 하자면,

  • 부트스트랩 스타일링을 사용하여 작성하였고,
  • 부트스트랩을 제외한 나머지 스타일링은 <style></style>에 담을 예정이다.
  • 카드 컴포넌트를 활용해 채팅의 기본 UI를 구성하며,
  • 대기실은 4/12(col-lg-4) 비율로 채팅방의 화면은 8/12(col-lg-8) 비율로 그려질 것이다. 

 

이제 작성한 html 파일을 서버위에 올려보자.

 

기존에 작성했던, Get 라우터 중, "Hello wolrd" 를 전송하던 코드 블럭이 있다. 이젠 이 코드블럭에 "Hello world" 를 전송하는게 아닌 html파일을 전송하게 작성한다.

 

app.js 파일을 수정해보자.

// ~/working_folder/app.js

// 기존의 소스코드
app.get('/', (req, res) => {
    res.send("Hello world");
});

// html 을 전송하는 소스코드
app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
});

 

수정이 끝났다면, 다시 

$ node app.js

를 실행하자. 그리고 localhost:3000 에 접속하면, 채팅방 ui 가 그려진 것을 확인할 수 있을 것이다.

 

 

 

 

 

 

 

3. SocketIO를 를 붙여서 서버와 클라이언트(브라우저) 연결

 

개인간의 채팅방은 제외하고, 우선 대기실에서 채팅을 할 수 있는 시스템을 구성해보자.

여기서 나오는 개념이 "io.emit" 이다.

 

io.emit 이란 현재 소켓 서버에 접속되어 있는 모든 사용자에게 메시지를 보내는 일을 한다.

 

소켓을 보내기/받기 하기 위해서는 먼저 서버와 클라이언트에서 설정해줘야할 것들이 있다.

우선 서버쪽을 먼저 보자.

 

socketio를 사용하기 위해선 socket.io 모듈이 필요하다.

socket.io 를 현재 package에 설치하도록하자.

$ npm install --save socket.io

 

서버

그리고 app.js 파일에 모듈을 선언하고, socketio 를 연동한다.

// app.js

//기존 코드
const app = require('express')();
const http = require('http').createServer(app);

// socket.io 모듈 사용 선언
const io = require('socket.io')(http);

..
...
.....



// 여기서 socket 은 개인 사용자 자신을 나타낸다 ( 사용자 개개인의 request source 라고 생각해도 될 듯 하다)
io.on('connection', (socket)=>{
    socket.on('request_message', (msg) => {
        io.emit('response_message', msg);
    });

    socket.on('disconnect', async () => {
        console.log('user disconnected');
    });
});


..
...
.....

 

 

 

클라이언트 쪽은 어떻게 될까?

클라이언트

socket.io 를 사용하기 위해선 javascript 내에서 socket.io.js 를 선언(로드)해주어야한다.

...
...

<!-- socket.io.js 를 로드한다 -->
<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>

...
...

<script>

// socket 을 init 해준다
const socket = io();

</script>

(jqeury 이후에 로드해도 상관없다)

 

 

 

 

이제 클라이언트에서 서버에 메시지를 보내고, 받은 메시지를 대기실에 그려보자.

직접 코딩을 하기 이전에, 어떤 흐름인지 먼저 이해하고 넘어가자.

 

 


꼭 이해하고 넘어가자 !

  1. 클라이언트에서 request_message 프로토콜로 서버에 message를 보낸다.
    (클라이언트에서 메시지 송출)
  2. 서버에서 request_message프로토콜 request를 확인한다.
    (
    서버에서 메시지 확인)
  3. 서버에서 response_message 프로토콜을 모든 클라이언트에게 방출(emit)한다.
    (
    서버에서 메시지 송출)
  4. 클라이언트에서 response_message 프로토콜 response를 확인한다.
    (
    클라이언트에서 메시지 확인)
  5. 클라이언트에서 response_message 프로토콜 데이터를 활용하여 client UI 에 메시지를 그린다.
    (
    클라이언트에서 결과 그리기)

 

참고 : 사실 socket.io 에는 request/response에 대한 개념이 크게 없다. 양방향 통신이므로, 어느쪽에서도 request 를 할 수 있다.

다만 request 라고 이름 지은 이유는, 패킷의 방향을 글로 이해하기 쉽게 하기 위함이다.


 

 

 

 

1. 클라이언트에서 메시지 송출

<script>

// 클라이언트에서 reuqest_message 프로토콜로 id='m' 의 input 값을 보낸다.
$('#msg-send').click(() => {
	socket.emit('request_message', $('#m').val());
	$('#m').val('');
	return false; 
});

</script>

jQuery를 사용하여, #msg-send 버튼이 클릭되었을 때, socket에 방출 명령어를 입력한다.

정해놓은 protocol 은 request_message 이며, #m input 의 value 값을 data 로 보낸다. 

 

 

 

2. 서버에서 메시지 확인 & 서버에서 메시지 송출

// app.js 

// request_message 프로토콜 listener
socket.on('request_message', (msg) => {
	// response_message로 접속중인 모든 사용자에게 msg 를 담은 정보를 방출한다.
	io.emit('response_message', msg);
});

 

서버에서 request_message 프로토콜으로 온, packet을 확인하고,

response_message 라는 프로토콜으로 메시지를 모든 유저(io.emit) 에게 방출한다.

 

 

3. 클라이언트 메시지 확인 & 클라이언트에서 결과 그리기

<script>

// 클라이언트에서 response_message 프로토콜 listener
socket.on('response_message', (res) => {
	$('#messages').prepend($('<li>').text(res));
});

</script>

서버에서 전송된 response_message 프로토콜로 온 packet을 확인하고,

#messages 에 넘오온 패킷 data를 prepend(앞에 추가) 한다.

 

 

 

 

 

이제 모든 준비가 끝났다. 

$ node app.js

를 실행하고, 브라우저에 localhost:3000으로 들어가서 채팅이 잘 되는지 확인해보도록하자.

 

 

 

 

 

 

 

 

 

 

혹시나 하는 마음에, 전체코드는 아래에 써두도록 하겠다.

 

+ (root folder)
|- app.js
|- index.html
|- package.json

 

package.json

{
  "name": "lvup-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^5.0.0",
    "express": "^4.17.1",
    "express-validator": "^6.6.1",
    "socket.io": "^2.3.0",
    "ws": "^7.3.1"
  }
}

 

 

 

app.js

const app = require('express')();
const http = require('http').createServer(app);
const io = require('socket.io')(http);

app.get('/', (req, res) => {
    res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket)=>{
    socket.on('request_message', (msg) => {
        // response_message로 접속중인 모든 사용자에게 msg 를 담은 정보를 방출한다.
        io.emit('response_message', msg);
    });

    socket.on('disconnect', async () => {
        console.log('user disconnected');
    });
});


// TEST CODE GOES HERE
(async function(){
})();



http.listen(3000, () => {
    console.log('Connected at 3000');
});

 

 

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Socket Tester</title>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <style>
    #messages { list-style-type: none; margin: 0; padding: 0; }
    #messages li { padding: 5px 10px; }
    #messages li:nth-child(odd) { background: #eee; }

    </style>
  </head>
  <body>
    <div class="row">

      <!-- 대기실 -->
      <div class="col-lg-4">
        <div class="card">
          <div class="card-header">
            대기실
          </div>
          <div class="card-body">
            <form action="">
              <div class="input-group mb-3">
                <input type="text" class="form-control" id="m" autocomplete="off" />
                <div class="input-group-append">
                  <button id="msg-send" class="btn btn-primary" placeholder="message">Send</button>
                </div>
              </div>
            </form>
          </div>
          <div class="card-footer">
            <ul id="messages"></ul>
          </div>
        </div>
      </div>


      <!-- 방선택 -->
      <div class="col-lg-8"></div>
    </div>
    
    <script src="/socket.io/socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
    <script>
      $(() => {
        /** Socket Starts **/
        const socket = io();
        
        // 클라이언트에서 reuqest_message 프로토콜로 id='m' 의 input 값을 보낸다.
        $('#msg-send').click(() => {
          socket.emit('request_message', $('#m').val());
          $('#m').val('');
          return false; 
        });

        socket.on('response_message', (res) => {
          $('#messages').prepend($('<li>').text(res));
        });

      });
    </script>
  </body>
</html>