[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/