[Node.js] 2022/05/16 : Node_Socket
**Node_Socket
1.Socket
=>통신을 할 수 있도록 해주는 NIC(Network Interface Card)를 추상화(프로그래밍에서 사용할 수 있도록 객체로 변환) 한 것
1)소켓 프로그래밍의 분류
=>고수준 소켓 프로그래밍: 직접 연결 과 해제를 하지 않는 방식으로 웹 프로그래밍이 대표적인 고수준 소켓 프로그래밍의 대표적인 방식인데 구현이 쉽지만 효율이 떨어집니다.
=>저수준 소켓 프로그래밍(소켓 프로그래밍): 직접 소켓을 생성해서 통신을 하는 방식으로 구현이 어렵지만 효율이 좋습니다.
2)프로토콜 종류
=>TCP: 연결형 통신
요청 -> 제공하는 쪽에서 메타 데이터 전송 -> 요청 -> 데이터 전송 -> 응답
=>UDP: 비연결형 통신
요청 -> 데이터 전송
2.Web Socket
1)HTTP, HTTPS
=>Client 와 Server 간 접속을 유지하지 않고 한 번에 한 방향으로만 통신이 가능한 Half-Duplex(반 이중 - 어느 한 순간에는 수신이나 송신만 가능)
=>Client 와 Server 간의 정보 유지를 위해서 Cookie 와 Session의 개념을 학습니다.
=>서로 간에 짧은 주기를 가지고 데이터를 자주 주고 받아야 하는 경우에 성능 저하를 피할 수 없음
=>접속을 유지할 수 없기 때문에 Client 의 요청 없이 Server 가 클라이언트에게 데이터를 전송할 수 없음
이 부분은 ajax pooling 기법으로 해결
2)HTML5에 새로 추가된 API
=>HTML5의 로컬 저장소: WebStorage, Web SQL, Indexed DB 같은 개념을 이용해서 웹 브라우저에 데이터를 저장하는 개념
=>HTML5의 Web Socket: 클라이언트 와 서버 간의 Full-Duplex(전 이중 - 동시에 주고 받는 것이 가능)
=>HTML5의 Web Push: 서버가 클라이언트의 요청이 없어도 데이터를 전송하는 기능, Notification 이나
SSE(Server Sent Event) 이라고 합니다.
3)웹 소켓
=>ws 프로토콜 사용
=>모든 브라우저가 웹 소켓을 지원하지는 않습니다.
IE 하위 버전에서는 지원하지 않습니다.
3.Node 에서의 Web Socket - websocket 모듈 이용
=>Socket.IO 모듈을 이용하는 경우가 많이 이용하지만 ws 모듈을 이용하기도 하고 websocket 모듈도 기능을 제공함
1)web socket 구현
=>프로젝트 생성
2)필요한 패키지 설치
=>websocket, express, morgan
=>개발용으로 nodemon
3)프로젝트에 index.html 파일을 생성하고 작성
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>웹 소켓</title>
</head>
<body>
소켓 연결 상태:<span id="status"></span>
<br />
메시지:<ul id="message"></ul>
</body>
</html>
4)프로젝트에 index.js 파일을 추가하고 작성
var WebSocketServer = require('websocket').server;
var http = require('http');
var fs = require('fs');
var server = http.createServer((req, res) => {
if(req.url == "/"){
res.writeHead(200, {'Content-Type':'text/html'});
res.end('Web Socket')
}else if(req.url == "/index"){
fs.readFile("index.html", (error, data) => {
res.writeHead(200, {'Content-Type':'text/html;charset=utf-8'});
res.end(data);
})
}
})
//서버 구동
server.listen(8000, function(){
console.log('Server is listening on port 8000');
})
5)프로젝트를 실행하고 브라우저에 localhost:8000, localhost:8000/index 요청을 확인
6)index.js 파일에 웹 소켓 서버 구현 코드를 추가
//웹 소켓 서버 구현
wsServer = new WebSocketServer({
httpServer:server,
autoAcceptConnections:false
});
//클라이언트에서 연결 요청이 오면
wsServer.on('request', (request) => {
//클라이언트 와 example-echo 라는 이름으로 연결
var connection = request.accept('example-echo', request.origin);
//연결된 클라이언트에서 메시지가 오면
connection.on('message', (message) => {
//텍스트 데이터라면
if(message.type === 'utf8'){
//메시지 출력
console.log('받은 메시지:' + message.utf8Data);
//받은 메시지를 클라이언트에게 전송
connection.sendUTF(message.utf8Data);
}
//일반 파일 데이터라면
else if(message.type == 'binary'){
connection.sendUTF(message.binaryData);
}
connection.on('close', (reasonCode, description) => {
console.log('Peer ' + connection.remoteAddress +
' disconnected.')
})
});
})
7)index.html 파일에 웹 소켓 사용을 위한 스크립트 코드를 추가
<script>
//브라우저의 웹 소켓 여부 확인
if('WebSocket' in window){
//DOM(Document Object Model) 찾아오기
var status = document.getElementById('status');
var message = document.getElementById('message');
//웹 소켓 연결
//IP는 서버의 IP적어야 하고 이름은 서버에서 만든 이름을 적어야 합니다.
var ws = new WebSocket('ws://127.0.0.1:8000', 'example-echo');
ws.addEventListener('open', (e) => {
status.innerHTML = '연결 성공';
for(var i = 0; i<10; i++){
//웹 소켓 서버에게 전송
ws.send('Hello ' + i);
}
});
ws.addEventListener('message', (evt) =>{
message.innerHTML +=
'<li>받은 메시지:' + evt.data + '</li>';
})
}
</script>
4.Node 에서의 Web Socket - ws 모듈 이용: express 모듈 과 함께 사용 가능
1)패키지를 추가 설치
cookie-parser, detenv, express, express-session, morgan, nunjucks, ws
2)프로젝트에 라우팅 모듈화를 위한 routes 디렉토리를 생성
3)routes 디렉토리에 index.js 파일을 생성하고 요청 처리 코드를 작성
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.render('websocket');
});
module.exports = router;
4)웹 소켓 로직을 위한 socket.js 파일을 생성하고 작성
const WebSocket = require('ws');
module.exports = (server) => {
//웹 소켓 서버 생성
const wss = new WebSocket.Server({server});
//클라이언트가 접속을 하면
wss.on('connection', (ws, req)=>{
//클라이언트의 IP 확인
const ip = req.headers['x-forwarded-for']
|| req.connection.remoteAddress;
console.log('새로운 클라이언트 접속:' + ip);
ws.on('message', (message) => {
console.log("클라이언트에게 받은 메시지:", message);
});
ws.on('close', () => {
console.log("클라이언트 접속 종료:", ip);
//타이머 종료
clearInterval(ws.interval);
});
//타이머를 이용해서 클라이언트에게 주기적으로 메시지를 전송
ws.interval = setInterval(() => {
if(ws.readyState === ws.OPEN){
ws.send('서버에서 클라이언트에게 메시지를 전송합니다.');
}
}, 3000)
})
};
5)프로젝트의 idnex.js 파일을 수정
//ws 모듈을 이용한 웹 소켓 구현 - 서버에서 일정한 주기를 가지고 메시지 전송
const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');
dotenv.config();
const webSocket = require('./socket');
//파일 이름을 생략하면 index.js 입니다.
const indexRouter = require('./routes')
const app = express();
app.set('port', 8001);
//뷰 템플릿(서버의 데이터를 출력할 수 있는 html 파일) 설정
app.set('view engine', 'html');
nunjucks.configure('views', {
express:app,
watch:true
});
app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({extended:false}))
app.use(cookieParser('websocket'));
app.use(session({
resave:false,
saveUninitialized:false,
secre:'websocket',
cookie:{
httpOnly:true,
secure:false
}
}))
// /로 시작하는 요청은 indexRouter 가 처리
app.use('/', indexRouter);
const server = app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기 중');
})
webSocket(server);
6)프로젝트에 views 디렉토리를 생성하고 websocket.html 파일을 만든 후 작성
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>웹 소켓</title>
</head>
<body>
<div>검사를 이용해서 console 과 network를 확인하세요</div>
</body>
<script>
const webSocket = new WebSocket("ws://localhost:8001");
webSocket.addEventListener("open", () => {
console.log("웹 소켓 서버와 연결");
});
webSocket.addEventListener('message', (evt) => {
console.log(evt.data);
webSocket.send("클라이언트가 서버에게 보내는 메시지");
})
</script>
</html>
5.node 에서 웹 소켓 구현 - socket.io 모듈
1)설치
npm install socket.io
2)프로젝트의 socket.js 파일을 수정 - 없으면 생성
const SocketIO = require('socket.io');
module.exports = (server) => {
//웹 소켓 서버 생성
const io = SocketIO(server, {path:'/socket.io'});
//클라이언트가 접속을 하면
io.on('connection', (socket)=>{
//클라이언트의 IP 확인
const req = socket.request;
const ip = req.headers['x-forwarded-for']
|| req.connection.remoteAddress;
console.log('새로운 클라이언트 접속:' + ip);
socket.on('disconnect', () => {
clearInterval(socket.interval);
});
socket.on('reply', (data) => {
console.log(data);
})
socket.interval = setInterval(()=>{
//emit 은 강제로 이벤트를 발생시키는 것입니다.
//news 라는 이벤트를 안녕이라는 파라미터로 발생
socket.emit('news', '안녕');
}, 3000)
})
};
3)websocket.html 파일 수정
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>웹 소켓</title>
</head>
<body>
<div>검사를 이용해서 console 과 network를 확인하세요</div>
</body>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io.connect('http://localhost:8001', {
path:'/socket.io',
transports:['websocket']
})
socket.on('news', function(data){
console.log(data);
socket.emit('reply', 'Hello socket.io');
})
</script>
</html>
6.채팅 구현
1)socket.js 파일을 수정
//socket.io 모듈 가져오기
const SocketIO = require('socket.io');
//서버를 생성해서 다른 곳에서 사용할 수 있도록 설정
module.exports = (server) => {
//웹 소켓 서버 생성
const io = SocketIO(server, {path:'/socket.io'});
//클라이언트가 접속을 하면
io.on('connection', (socket)=>{
//클라이언트의 IP 확인
const req = socket.request;
const ip = req.headers['x-forwarded-for']
|| req.connection.remoteAddress;
console.log('새로운 클라이언트 접속:' + ip);
//접속을 해제 했을 때 처리 - 타이머 종료
socket.on('disconnect', () => {
clearInterval(socket.interval);
});
//reply 이벤트가 발생했을 때 처리 - 사용자가 보내는 reply 이벤트
//원래 존재하는 이벤트가 아님
socket.on('reply', (data) => {
console.log(data);
})
//타이머 생성 - 3초 마다 강제로 news 라는 이벤트를 발생
socket.interval = setInterval(()=>{
//emit 은 강제로 이벤트를 발생시키는 것입니다.
//news 라는 이벤트를 안녕이라는 파라미터로 발생
socket.emit('news', '안녕');
}, 3000)
//클라이언트가 메시지를 전송하면
socket.on('message', (data) => {
//모든 클라이언트에게 메시지 전송
io.sockets.emit('message', data);
});
})
};
2)websocket.html 파일 수정
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>웹 소켓</title>
<!-- 모바일 웹 페이지 생성 시 옵션 설정-->
<meta name="viewport"
content="width=device-width, initial-scale=1" />
<!-- jquery mobile 설정-->
<link rel = "stylesheet"
href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css"/>
<!-- jquery mobile은 기본적으로 single page application 을 제작
내부 코드는 ajax로 동작-->
<script src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
<script
src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js">
</script>
<!--소켓 설정-->
<script src="/socket.io/socket.io.js"></script>
<script>
$(document).ready(function(){
//웹 소켓 생성
var socket = io.connect('http://localhost:8001');
//소켓 서버로부터 message 이벤트가 오면
socket.on('message', function(data){
//받은 메시지를 이용해서 출력할 내용을 생성
var output = '';
output += '<li>';
output += '<h3>' + data.name + '</h3>';
output += '<p>' + data.message + '</p>';
output += '<p>' + data.date + '</p>';
output += '</li>';
//메시지 출력
$(output).prependTo('#content');
$('#content').listview('refresh');
});
//버튼 눌렀을 때 메시지 전송
$('button').click(function(){
socket.emit('message', {
name:$('#name').val(),
message:$('#message').val(),
date:new Date().toUTCString()
});
$('#message').val('');
})
})
</script>
</head>
<body>
<div data-role = 'page'>
<div data-role = 'header'>
<h1>socket.io chatting</h1>
</div>
<div data-role='content'>
<h3>별명</h3>
<input id='name' />
<a data-role="button" href="#chatpage">채팅 시작</a>
</div>
</div>
<div data-role = 'page' id="chatpage">
<div data-role="header">
<h1>socket.io chatting</h1>
</div>
<div data-role="content">
<input id="message" />
<button>전송</button>
<ul id="content" data-role="listview" data-inset="true">
</ul>
</div>
</div>
</body>
</html>
7.전자 칠판 구현
1)프로젝트에 public 디렉토리를 생성 - 정적인 데이터 저장이 목적
2)websocket.html 파일 수정
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>웹 소켓</title>
<!-- jquery mobile은 기본적으로 single page application 을 제작
내부 코드는 ajax로 동작-->
<script src="http://code.jquery.com/jquery-1.8.2.min.js">
</script>
<script src="js/board.js"></script>
<link rel = 'stylesheet' href="stylesheet/style.css">
</head>
<body>
<canvas id="cv" width="860px" height="645px"></canvas>
</body>
</html>
3)public 디렉토리에 images 디렉토리르 생성하고 blackboard.jpg 파일을 추가
4)public 디렉토리에 stylesheet 디렉토리를 생성하고 style.css 파일을 추가한 후 작성
body{
margin:0px;
}
#cv{
width:860px;
height:645px;
background-image: url('../images/blackboard.jpg');
}
5)websocket.html 파일을 수정
=>캔버스 아래에 메뉴 와 버튼을 추가
<div class="menu"></div>
<button id="clear">전체 삭제</button>
7)style.css 파일의 내용을 수정
body{
margin:0px;
}
#cv{
width:860px;
height:645px;
background-image: url('../images/blackboard.jpg');
float:left;
}
.menu{
float:left;
}
button{
width:100px;
height:50px;
}
6)public 디렉토리에 js 디렉토리를 생성하고 board.js 파일을 추가한 후 작성
var ctx;
$(function(){
ctx = $('#cv').get(0).getContext('2d');
$('#cv').bind('mousedown', draw.start);
$('#cv').bind('mousemove', draw.move);
$('#cv').bind('mouseup', draw.end);
$('#clear').bind('click', draw.clear);
//기본 설정
shape.setShape()
})
var shape = {
color:'white',
width:3,
setShape : function(color, width){
if(color != null){
this.color = color;
}
if(width != null){
this.width = width;
}
ctx.strokeStyle = this.color;
ctx.lineWidth = this.width;
}
}
var draw = {
drawing:null,
start:function(e){
ctx.beginPath();
ctx.moveTo(e.pageX, e.pageY);
this.drawing = true;
},
move:function(e){
if(this.drawing){
ctx.lineTo(e.pageX, e.pageY);
ctx.stroke();
}
},
end:function(e){
this.drawing = false;
},
clear:function(){
ctx.clearRect(0, 0, cv.width, cv.height);
}
}
7)현재까지 작성한 후 실행해서 마우스로 선이 그려지고 버튼을 누르면 삭제되는지 확인
8)websocket.html 파일의 메뉴 영역에 색상 변경, 펜 두께, 펜 모양 메뉴를 추가
<div class="menu">
<button id="clear">전체 삭제</button>
<fieldset>
<legend>색상 변경</legend>
<select id="pen_color"></select>
</fieldset>
<fieldset>
<legend>두께</legend>
<select id="pen_width"></select>
</fieldset>
<fieldset id="pen_shape">
<legend>모양</legend>
</fieldset>
</div>
9)style.css 파일에 추가한 DOM 의 style 설정 코드 추가
#cv_pen{
width:100px;
height:50px;
float:left;
background-image: url('images/blackboard.jpg');
}
fieldset{
width:100px;
height:60px;
float:left;
}
#pen_shape{
position: absolute;
top:10px;
left:700px;
color:white;
}