본문 바로가기

카테고리 없음

[Vue] VueJs + NodeJs + Socket.IO 를 사용하여 간단한 채팅 구현

 

VueJs + NodeJs + Socket.IO 를 사용하여 간단한 채팅 구현 


 

이번에는 간단한 채팅앱을 구현해보았다.

기본적으로 VueJs + NodeJs 를 해보았다는 가정하에 진행하겠다.

 

 

1. 웹소켓이란?

 

우리는 일반적으로 웹상에서 서버와 데이터를 주고 받을때 HTTP 를 주로 사용합니다. 

하지만 HTTP 는 요청이 있어야 이에 대한 응답을 보내주기 때문에 실시간 을 보장할 수 없었고,  폴링(polling) 방식 또는 롱폴링(polling) 방식을 사용하였습니다. 하지만 이러한 방식도 결과적으로는 실시간을 보장할 수 없을뿐더러 불필요한 HTTP 요청/응답 이 늘어나고 그만큼 서버 및 네트워크의 부하가 커졌습니다.  이에 따라 발전한게 웹소켓(WebSocket) 이며 한번의 요청(Request) 로 서버와 네트워크가 실시간으로 데이터를 주고받을 수 있으며, 불필요한 HTTP 요청/응답이 없기 때문에 서버 및 네트워크의 부하가 줄어들게 됩니다. 

 

 

2. 프로젝트 세팅

 

전체소스는 아래 github에 있습니다.

https://github.com/parkbeomun/vue-node-websocket

 

프로젝트는 서버와 클라이언트를 분리하였습니다.

 

vue-node-socket-sample/backend

vue-node-socket-sample/frontend

 

먼저 작업할 workspace 로 이동한 후 폴더를 생성합니다.

> mkdir vue-node-socket-sample

 

2-1. backend 세팅

 

이제 폴더가 생성되었다면 backend 폴더를 생성합니다. 

> mkdir backend

 

backend 폴더 내부에서 npm init 명령어로 package.json 를 생성합니다.

> cd backend 
> npm init

 

마지막으로 server.js 를 생성합니다. 

server.js 는 node 어플리케이션의 시작점입니다. 

 

mac os 
> touch server.js

windows os
> type NUL > server.js

 

이제 아래와 같이 server.js 코드를 작성합니다.

 

backend/server.js 

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

//setting cors 
app.all('/*', function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    next();
});

app.get('/', function(req, res) {
    res.sendFile('Hellow Chating App Server');
});

//connection event handler
io.on('connection' , function(socket) {
    console.log('Connect from Client: '+socket)

    socket.on('chat', function(data){
        console.log('message from Client: '+data.message)

        var rtnMessage = {
            message: data.message
        };

        // 클라이언트에게 메시지를 전송한다
        socket.broadcast.emit('chat', rtnMessage);
    });


})

server.listen(3001, function() {
    console.log('socket io server listening on port 3001')
})

 

var io = require('socket.io')(server);

:  해당 서버를 소켓 서버임을 설정합니다.

 

io.on('connection' , function(socket) {})

: connection 을 수립하고 callback 인자로 socket 를 받습니다.

 

socket.on('chat', function(data){})

: 클리이언트로 부터 'chat' 이라는 이벤트명을 사용해 메세지를 전달받습니다. 

 

socket.broadcast.emit('chat', rtnMessage);

:클라이언트로 'chat' 이라는 이벤트명을 사용해 메세지를 전달합니다.  .broadcase. 를 추가하면 자신을 제외한 나머지 클라이언트에게만 메세지를 전달합니다.  

 

2.2 frontend 세팅

 

다시 루트폴더로 이동합니다.

이번엔 vue cli 를 사용하여 프로젝트를 생성합니다.

> vue create frontend

 

콘솔에 뭔가 선택하라는 메세지가 나올텐데 일단은 default 로 설정합니다.

vue 프로젝트가 생성되었다면 dependencies 를 추가하겠습니다.

> npm install --save vue-material vue-socket.io

 

vue-meterial : vue application 의 디자인을 예쁘게 하기 위해 사용

vue-socket.io : 서버와 socket 을 연결하기 위해 사용

 

이제 클라이언트 코드를 작성합니다. 

 

frontend/src/main.js

import Vue from 'vue'
import App from './App.vue'
import VueMaterial from 'vue-material'
import 'vue-material/dist/vue-material.css'
import 'vue-material/dist/theme/black-green-light.css'
import Directives from '../plugin/directives'

import io from 'socket.io-client';
const socket = io('http://localhost:3000');

Vue.prototype.$socket = socket;

Vue.use(VueMaterial)
Vue.use(Directives)

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

 

초기 세팅에서 달라진점은 

VueMeterial, Directives, io, socket 관련된 코드가 추가되었습니다.

 

여기서 신경써야 할점은 

import io from 'socket.io-client';
const socket = io('http://localhost:3001');

Vue.prototype.$socket = socket;

 

위 부분입니다. 

첫번째라인은 서버와 소켓연결을 위해 사용한 모듈을 import 했으며

두번째라인은 연결하고자 하는 서버의 url 을 입력합니다. 현재는 로컬에서 작업하기때문에 로컬서버인 localhost:3000 이 사용되었습니다.

마지막라인은 socket 을 vue 인스턴스 변수로 등록하여 컴포넌트에서 사용할 수 있도록 하였습니다.

 

 

frontend/src/component/Char.vue

<template>
    <div class="page-container">
        <md-app>
            <md-app-toolbar class="md-primary">
                <div class="md-toolbar-row">
                    <span class="md-title">My Chat App</span>
                </div>
            </md-app-toolbar>
            <md-app-content>
                <md-field>
                    <label>Message</label>
                    <md-textarea v-model="textarea" disabled v-auto-scroll-bottom></md-textarea>
                </md-field>
                <md-field>
                    <label>Your Message</label>
                    <md-input v-model="message"></md-input>
                    <md-button class="md-primary md-raised" @click="sendMessage()">Submit</md-button>
                </md-field>
            </md-app-content>
        </md-app>
    </div>
</template>


<script>


    export default {
        name: 'Chat',
        created() {
            this.$socket.on('chat', (data)=> {
                this.textarea += data.message + "\n"
            })
        },
        data() {
            return {
                textarea: "",
                message: '',
            }

        },
        methods: {
            sendMessage () {

                this.$socket.emit('chat',{
                    message: this.message
                });

                this.textarea += this.message + "\n"
                this.message = ''
            }
        }
    }
</script>

<style>
    .md-app {
        height: 800px;
        border: 1px solid rgba(#000, .12);
    }

    .md-textarea {
        height: 300px;
    }
</style>

 

초기 DOM 이 생성된 후 한번 호출되는 created 함수에서는 socket 이벤트를 등록합니다.

서버에서 emit 을 통해 클라이언트로 메세지를 전달하였으므로, 클라이언트에서 같은 이벤트명을 사용하여

서버로부터 데이터를 받도록 합니다. 

 

        created() {
            this.$socket.on('chat', (data)=> {
                this.textarea += data.message + "\n"
            })
        },

 

Submit 버튼의 onclick 이벤트가 발생하면  서버로 데이터를 전송하고 현재 입력중인 데이터를 지웁니다.

 

            sendMessage () {

                this.$socket.emit('chat',{
                    message: this.message
                });

                this.textarea += this.message + "\n"
                this.message = ''
            }

 

위와 같이 클라이언트에서 emit 를 통해 데이터를 전송하면 

서버에서 동일한 이벤트명으로 클라이언트에서 데이터를 전달받습니다.

 

이제 스크롤 설정을 하겠습니다.

DOM 이 변경되면 스크롤을 하단으로 이동시킵니다.

 

frontend/plugin/directives.js

module.exports = (Vue) => {

    Vue.directive('auto-scroll-bottom', {
        update: (el) => {
            el.scrollTop = el.scrollHeight
        }
    })
}

 

마지막으로 App.vue 에서 Chat.vue 를 바로 보여주도록 수정합니다.

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <Chat/>
  </div>
</template>

<script>
import Chat from './components/Chat.vue'

export default {
  name: 'app',
  components: {
    Chat
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

 

3. 확인

 

backend 

> node server.js

 

frontend 

> npm run serve

 

브라우저를 2개 띄우고  localhost:8080 으로 접속합니다. 

한쪽 브라우저에서 입력시 나머지 브라우저에서 데이터를 받습니다.

 

 

 

참조사이트

https://poiemaweb.com/nodejs-socketio
https://blog.anoff.io/2018-04-18-node-vue-websockets/