HTTP 는 Client - Server 간의 접속을 유지하지 않음
HTTP 는 Client 와 상태 유지 문제 발생
Cookie 와 Session 을 이용해서 상태 유지
Cookie : 클라이언트 브라우저의 데이터를 저장한 후 서버에 요청을 보낼 때 마다 전송되는 객체
Session : 서버에 쿠키의 키와 사용자(브라우저)를 구분해서 데이터를 저장하는 기술
HTTP 의 성능 저하 문제
HTTP 는 기본적으로 Half Duplex 방식을 사용하기 때문에 Full Duplex 보다는 성능이 떨어진다.
HTTP 는 헤더와 본체를 같이 전송하는 방식이기 때문에 본체의 내용이 적다면 헤더로 인한 오버헤드가 많이 발생
HTTP 는 Client 의 request 가 없으면 Server 가 Response 할 수 없음
성능 저하 문제를 해결하기 위해 Web Socket 이 등장
Web 에서 TCP/IP 방식의 소켓 통신을 가능하게 한 API
네트워크 과부하를 줄일 수 있고 애플리케이션의 반응성도 높일 수 있음
HTTP 는 800byte 에서 수kbyte 의 헤더를 갖지만 Web Socket 은
수 byte 정도의 헤더만을 가진다
HTTP 가 적합하지 않은 트래픽이 많고 지연 시간이 낮아야 하는 환경에서 주로 사용
대표적인 예
Web RTC 의 기반 기술
SSE (Server Sent Events) : Web Push
$ npm init
명령어 실행하고 옵션을 설정node runtime 프로젝트를 다른 곳으로 가져갈 때 node_module(의존성 라이브러리가 설치되는 디렉토리임)를 삭제하고 가져 간 후 디렉토리를 가져온 다음 npn install
명령만 다시 수행하면 이전 프로젝트 와 동일한 환경이 만들어진다.
패키지를 설치
$ npm install cookie-parser dotenv express express-session morgan nunjucks ws websocket
$ npm install --save-dev nodemon
--save-dev nodemon 패키지
서버 개발을 할 때 소스코드를 변경하면 바로 적용이 되도록 설정해두기 위한 패키지
{
"name": "nodeproject1",
"version": "1.0.0",
"description": "",
"main": "App.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"cookie-parser": "^1.4.6",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"express-session": "^1.17.3",
"morgan": "^1.10.0",
"nunjucks": "^3.2.3",
"websocket": "^1.0.34",
"ws": "^8.12.1"
},
"devDependencies": {
"nodemon": "^2.0.20"
}
}
"scripts":{}
안에 "start":"nodemon app
추가"main": "App.js"
수정<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Socket Example</title>
</head>
<body>
Status:<span id = 'status'></span>
<br/>
Message:<ul id = 'messages'></ul>
</body>
<script>
let status = document.getElementById('status');
let messages = document.getElementById('messages');
//웹 소켓 접속
let ws = new WebSocket('ws://127.0.0.1:8000','example-echo');
//웹 소켓에 접속 성공했을 때 처리
ws.addEventListener('open', (e)=>{
status.innerHTML = '연결에 성공하셨어요!';
// 서버에게 메시지 전송
for(let i=0; i<10; i=i+1){
ws.send('hello world' + i);
}
})
//웹 소켓 서버로부터 메시지가 온 경우
ws.addEventListener('message', (e)=>{
messages.innerHTML = messages.innerHTML + '<li>메시지:' + e.data + '</li>'
});
//웹 소켓 연결해제
ws.addEventListener('close',(e)=>{
status.innerHTML = '접속해제 완료';
//직접 연결 해제 - ws.close();
})
</script>
</html>
backlog를 이용해서 서버에 접속할 수 있는 클라이언트의 숫자를 설정하는데 토큰을 주고 받는 방식으로 동작
backlog를 10으로 설정하면 클라이언트에게 접속할 수 있는 토큰 10을 발급하는 것과 유사한데 클라이언트가 접속을 해제한다고 알려주지 않으면 토큰의 개수는 복원되지 않는다
클라이언트 프로그램을 만들 때 예상치 않는 상황으로 종료되는 경우 세션을 파기하거나 연결을 해제하는 코드를 만들어 주는 것이 좋다
//웹 소켓 서버를 위한 모듈
let WebSocketServer = require('websocket').server;
//http 웹 서버를 위한 모듈
let http = require('http');
//파일을 읽고 쓰기 위한 모듈
let fs = require('fs');
//서버 설정
let server = http.createServer(function(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', function(error, data){
res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
res.end(data);
})
}
});
//서버 구동
server.listen(8000, function(){
console.log('Server is Listen on Port 8000');
})
//소켓 서버 생성
let wsServer = new WebSocketServer({
httpServer:server,
autoAcceptConnections:false
});
wsServer.on('request', function(request){
//서버이름과 동일한 요청만 연결
let connection = request.accept('example-echo',request.origin);
//연결된 소켓과 이벤트 처리
connection.on('message', function(message){
if(message.type ==='utf8'){
console.log('메시지:' + message.utf8Data);
//echo : 클라이언트가 보낸 메시지를 그대로 다시 전송
connection.sendUTF(message.utf8Data);
//전송된 클라언트 IP 확인
console.log(connection.remoteAddress);
}else if(message.type === 'binary'){
connection.sendBytes(message.binaryData);
}
})
})
실행 : $ npm start
브라우저에서 localhost:8000 으로 접속
클라이언트가 요청한 URL 에 따라 다른 처리를 수행하도록 하는 것
Routing 하는 부분을 Python 의 django 에서는 urls 와 views를 이용해서 처리하는데 그 이외의 프레임워크에서는 Controller 라고 하는 경우가 많다
node 런타임에서는 디렉토리를 Import 하면 디렉토리 안의 index.js를 import 하는 것이다.
node 런타임에서 배포를 할 때는 배포하고자 하는 라이브러리나 컴포넌트를 하나의 디렉토리에 저장하고 index.js를 만들어서 배포
import React from 'react';
리액트 디렉토리에 index.js 파일이 export default 한 내용을 React라는 이름으로 받아서 사용하겠다는 의미
//웹 서버를 만들기 위한 node 의 모듈
const express = require('express');
const router = express.Router();
//기본 요청이 왔을 때 websocket.html 을 출력
router.get('/',(req,res)=>{
res.render('websocket');
});
module.exports = router;
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.socket.remoteAddress;
console.log('접속한 클라이언트:', ip);
//클라이언트가 서버에게 메시지를 전송한 경우
ws.on('message', (message) => {
console.log(message);
});
ws.on('close', ()=>{
console.log('클라이언트 접속 해제');
//타이머 해제
clearInterval(ws.interval);
});
//타이머를 이용해서 클라이언트에게 주기적으로 메시지 전송
ws.interval = setInterval(()=>{
if(ws.readyState === ws.OPEN){
ws.send('서버에서 일방적으로 메시지 보내기');
}
}, 3000);
})
}
COOKIE_SECRET = websocket
애플리케이션이 실행되는 과정
개발에 필요한 내용을 만들다 보면 변하지 않는 고정된 문자열이 있는 경우가 있는데 이러한 문자열 가운데는 개발 모드에서 운용모드로 변경될 때 수정하는 경우가 있다.
소스 코드가 변경이 되면 Compile 을 다시해야하고 Build 역시 다시 해야하지만, 소스 코드 이외의 변경은 ReRun 만 하면 되는 경우가 많다
Compile 이나 Build를 다시하게 되면 얘기치 않은 오류가 발생할 수 있다
//웹서버 프레임 워크 추출
const express = require('express');
//경로 사용하기 위한 노드의 기본 모듈
const path = require('path');
//로그 기록을 위한 모듈
const morgan = require('morgan');
//서버에서 클라이언트 출력을 만들 때 상태 유지를 위한 모듈
const cookieparser = require('cookie-parser');
const session = require('express-session');
//노드의 템플릿 엔진(서버에서 만든 데이터를 HTML 에 출력하기 위한 엔진
//- 서버사이드 렌더링에 필요) 중 하나
const nunjucks = require('nunjucks');
//.env 파일의 설정 내용을 읽어내기 위한 모듈
const dotenv = require('dotenv');
dotenv.config();
const webSocket = require('./socket');
const indexRouter = require('./routes');
//웹서버 생성
const app = express();
app.set('port', process.env.PORT || 8001);
//템플릿 엔진 설정
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(process.env.COOKIE_SECRET));
클라이언트의 요청을 Controller 가 처리하기 전이나 처리한 후에 동작을 수행하는 것
middleware가 없어도 Controller 에서 모든 처리를 할 수 있다.
가장 많이 사용되는 미들웨어는 로그 기록 하는 부분과 인증
데이터 유효성 검사도 미들웨어의 대상이 된다.
AoP(Aspect of Programming - 관점 지향 프로그래밍)
프로그래밍 구성을 Common Concern(공통 관심 사항)과 Bussiness Logic(업무 관련 로직) 으로 분리를 하고 별도로 작성하는 프로그래밍 기법
공통 관심 사항은 도메인 지식이 없어도 작성이 가능한 부분이고 Bussiness Logic 은 도메인 지식이 없으면 작성이 불가능한 부분
CQRS
데이터를 구분
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
웹 소켓을 이용한 푸쉬(검사 창에서 확인)
</body>
<script>
//웹 소켓 연결
const webSocket = new WebSocket('ws://localhost:8001');
//연결된 경우 이벤트 처리
webSocket.addEventListener('open', ()=>{
console.log('서버와 웹 소켓으로 연결함');
});
//서버로부터 메시지가 도착한 경우
webSocket.addEventListener('message', (event) =>{
console.log(event.data);
})
</script>
</html>
socket.io 설치
$ npm install socket.io
socket.js 를 수정
const SocketIO = require('socket.io');
module.exports = (server) => {
const io = SocketIO(server, {path:'./socket.io'});
io.on('connection', (socket)=> {
const req = socket.request;
const ip = req.headers['x-fowarded-for'] || req.connection.remoteAddress;
console.log('접속한 클라이언트의 IP:' + ip);
socket.on('disconnect', ()=>{
clearInterval(socket.interval);
});
//클라이언트에서 응답이 왔을 때
socket.toString('reply', (data)=>{
console.log(data);
});
let ar = ['일','이','삼','사','오'];
let i =0;
socket.interval = setInterval(()=>{
i = i +1;
socket.emit('news', ar[i % ar.length]);
}, 3000)
})
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>push using websocket</title>
<div id="content"></div>
</head>
<body>
웹 소켓을 이용한 푸쉬(검사 창에서 확인)
</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){
document.getElementById('content').innerHTML = data;
console.log(data);
})
</script>
</html>
socket.on('message', function(data){
io.sockets.emit('message', data);
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>web Chatting</title>
<link rel = "stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css"/>
<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>
<div id="content"></div>
</head>
<body>
<div data-role = "page">
<div data-role = "header">
<h1>Web Socket 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>채팅</h1>
</div>
<div data-role="content">
<input id = "message"/>
<button>전송</button>
<ul id = "content" data-role="listview" data-insert="true"></ul>
</div>
</div>
</body>
<script>
//웹 소켓 연결
const socket = io.connect('http://localhost:8001',{
path:'/socket.io',
transports:['websocket'],
});
socket.on('news', function(data){
document.getElementById('content').innerHTML = data;
console.log(data);
})
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>web Chatting</title>
<link rel = "stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css"/>
<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>
</head>
<body>
<div data-role = "page">
<div data-role = "header">
<h1>Web Socket 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>채팅</h1>
</div>
<div data-role="content">
<input id = "message"/>
<button>전송</button>
<ul id = "content" data-role="listview" data-insert="true"></ul>
</div>
</div>
</body>
<script>
window.addEventListener('load', () =>{
let socket = io.connect('http://localhost:8001');
//소켓에 message 이벤트가 발생했을 때
socket.on('message', function(data){
//출력 내용 만들기
let output = '';
output = output + '<li>';
output = output + '<h3>' + data.name + '</h3>';
output = output + '<p>' + data.message + '</p>';
output = output + '<p>' + data.date + '</p>';
output = output + '</li>';
let content = document.getElementById('content');
content.innerHTML = output + content.innerHTML;
});
//버튼을 누를 때 입력한 내용을 전송
$('button').click(function(){
socket.emit('message', {
name:$('#name').val(),
message:$('#message').val(),
date: new Date().toUTCString()
});
$('#message').val('');
})
})
</script>
</html>
canvas api : HTML5 의 API로 HTML 화면에 그림을 그리거나 이미지를 출력하고 동영상 이미지 등을 캡처 할 수 있는 API
websocket.html 파일을 수정
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>전자 캔버스</title>
<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>
body{
margin:0px;
}
#cv{
width: 860px;
height: 645px;
background-image: url('../images/blackboard.jpg');
}
window.addEventListener('load',(e)=>{
// canvas 태그를 찾아오기
let canvas = document.getElementById('cv');
// Canvas 의 Context(그리기 위한 기본 정보를 저장한 객체)를 찾아 오기
let ctx = canvas.getContext('2d');
//ctx.fillStyle="#FF0000";
//ctx.fillRect="0,0,200,200";
ctx.strokeStyle="#FF0000";
ctx.lineWidth=5;
ctx.beginPath();
ctx.arc(200,200,100,0,2*Math.PI);
ctx.stroke();
})
window.addEventListener('load',(e)=>{
// canvas 태그를 찾아오기
let canvas = document.getElementById('cv');
// Canvas 의 Context(그리기 위한 기본 정보를 저장한 객체)를 찾아 오기
let ctx = canvas.getContext('2d');
//ctx.fillStyle="#FF0000";
//ctx.fillRect="0,0,200,200";
/*ctx.strokeStyle="#FF0000";
ctx.lineWidth=5;
ctx.beginPath();
ctx.arc(200,200,100,0,2*Math.PI);
ctx.stroke();
*/
//선그리기
ctx.strokeStyle="#FF0000";
ctx.lineWidth=5;
ctx.beginPath();
ctx.moveTo(100,100);
ctx.lineTo(200,200);
ctx.stroke();
})
window.addEventListener('load',(e)=>{
// canvas 태그를 찾아오기
let canvas = document.getElementById('cv');
// Canvas 의 Context(그리기 위한 기본 정보를 저장한 객체)를 찾아 오기
let ctx = canvas.getContext('2d');
//ctx.fillStyle="#FF0000";
//ctx.fillRect="0,0,200,200";
/*ctx.strokeStyle="#FF0000";
ctx.lineWidth=5;
ctx.beginPath();
ctx.arc(200,200,100,0,2*Math.PI);
ctx.stroke();
*/
//마우스 이벤트와 결합한 선 그리기
ctx.strokeStyle="#FF0000";
ctx.lineWidth=5;
ctx.beginPath();
//그리기 모드 변수
let drawing = false;
//마우스를 눌렀을 때
canvas.addEventListener('mousedown',(e)=>{
drawing = true;
//현재 마우스 좌표로 move
ctx.moveTo(e.pageX,e.pageY);
});
// 마우스를 이동할 때 선그리기
canvas.addEventListener('mousemove', (e)=>{
if(drawing){
ctx.lineTo(e.pageX,e.pageY);
ctx.stroke();
}
});
// 마우스에서 손을 땔 때 그리기 모드를 해제
canvas.addEventListener('mouseup',(e)=>{
drawing=false;
})
})
//Context 객체를 저장할 변수
//여러 곳에서 사용하기 위해서 외부에 선언
let ctx;
window.addEventListener('load',(e)=>{
let canvas = document.getElementById('cv')
ctx = canvas.getContext('2d');
ctx.strokeStyle = 'white';
ctx.lineWidth = 5;
ctx.beginPath();
let drawing = false;
canvas.addEventListener('mousedown',draw.start);
canvas.addEventListener('mousemove',draw.move);
canvas.addEventListener('mouseup',draw.end);
shape.setShape();
})
//옵션 설정하는 객체
let 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;
}
}
//선을 그려주는 함수를 소유한 객체
let 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;
},
}
<div class="menu">
<button id="clear">전체 삭제</button>
</div>
body{
margin:0px;
}
#cv{
width: 860px;
height: 645px;
background-image: url('../images/blackboard.jpg');
float: left;
}
.menu{
float: left;
width: 100px;
}
button,select{
width: 100px;
height: 50px;
float: left;
}
//Context 객체를 저장할 변수
//여러 곳에서 사용하기 위해서 외부에 선언
let ctx;
window.addEventListener('load',(e)=>{
let canvas = document.getElementById('cv')
ctx = canvas.getContext('2d');
ctx.strokeStyle = 'white';
ctx.lineWidth = 5;
ctx.beginPath();
let drawing = false;
canvas.addEventListener('mousedown',draw.start);
canvas.addEventListener('mousemove',draw.move);
canvas.addEventListener('mouseup',draw.end);
//삭제 버튼 이벤트 연결
let clear = document.getElementById('clear');
clear.addEventListener('click', draw.clear);
shape.setShape();
})
//옵션 설정하는 객체
let 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;
}
}
//선을 그려주는 함수를 소유한 객체
let 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:(e)=>{
//캔버스의 영역에 해당하는 부분의 그림을 삭제
ctx.clearRect(0,0,860,645);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>전자 칠판</title>
<script src="/socket.io/socket.io.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>
<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>
</body>
</html>
body{
margin:0px;
}
#cv{
width: 860px;
height: 645px;
background-image: url('../images/blackboard.jpg');
float: left;
}
.menu{
float: left;
width: 100px;
}
button,select{
width: 100px;
height: 50px;
float: left;
}
fieldset{
width: 100px;
height: 60px;
float: left;
}
#pen_shape{
position:absolute;
top: 10px;
left: 700px;
color: white;
}
//Context 객체를 저장할 변수
//여러 곳에서 사용하기 위해서 외부에 선언
let ctx;
window.addEventListener('load',(e)=>{
let canvas = document.getElementById('cv')
ctx = canvas.getContext('2d');
ctx.strokeStyle = 'white';
ctx.lineWidth = 5;
ctx.beginPath();
let drawing = false;
canvas.addEventListener('mousedown',draw.start);
canvas.addEventListener('mousemove',draw.move);
canvas.addEventListener('mouseup',draw.end);
//삭제 버튼 이벤트 연결
let clear = document.getElementById('clear');
clear.addEventListener('click', draw.clear);
shape.setShape();
//색상 배열
let color_map = [
{'value':'white','name':'히얀색'},
{'value':'white','name':'빨간색'},
{'value':'white','name':'주황색'},
{'value':'white','name':'노란색'},
{'value':'white','name':'파란색'},
{'value':'white','name':'녹색'},
{'value':'white','name':'검정색'},
]
let pen_color = document.getElementById('pen_color');
let pen_width = document.getElementById('pen_width');
let pen_shape = document.getElementById('pen_shape');
//색상 추가
for(let key in color_map){
let newOption = new Option(color_map[key].name, color_map[key].value);
pen_color.append(newOption);
}
//두께 추가
for(let i=1;i<=15; i = i+1){
let newOption = new Option(i,1);
pen_width.append(newOption);
}
pen_color.addEventListener('change', shape.change);
pen_width.addEventListener('change',shape.change);
pen_shape.addEventListener('change',shape.change);
})
//옵션 설정하는 객체
let shape = {
color:'white',
width:3,
change:function(){
// select 의 선택된 값을 찾아오기
let color = pen_color.options[pen_color.selectedIndex].value;
let width = pen_width.options[pen_width.selectedIndex].value;
shape.setShape(color,width);
},
setShape:function(color,width){
if(color != null){
this.color = color;
}
if(width != null){
this.width = width;
}
ctx.strokeStyle=this.color;
ctx.lineWidth=this.width;
//선택한 선 모양을 출력
ctx.clearRect(703,0,860,90);
ctx.beginPath();
ctx.moveTo(710,55);
ctx.lineTo(820,55);
ctx.stroke();
}
}
//선을 그려주는 함수를 소유한 객체
let 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:(e)=>{
//캔버스의 영역에 해당하는 부분의 그림을 삭제
ctx.clearRect(0,0,860,645);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>전자 칠판</title>
<script src="/socket.io/socket.io.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>
<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>
</body>
</html>
//Context 객체를 저장할 변수
//여러 곳에서 사용하기 위해서 외부에 선언
let ctx;
//웹 소켓을 저장하기 위한 변수
let socket;
window.addEventListener('load', (e) => {
//웹 소켓 연결 - 여기서는 로컬을 설정
//실제 프로젝트에서는 서버의 IP와 포트번호로 설정
socket = io.connect('http://'+ window.location.host);
//서버로부터 전송되어온 메시지를 처리
socket.on('linesend_toclient', (data) => {
//넘어온 데이터를 가지고 그림을 그려주는 메서드를 호출
draw.drawFromServer(data);
})
//캔버스 DOM 찾아오기
let canvas = document.getElementById('cv');
ctx = canvas.getContext('2d');
//초기 설정
ctx.strokeStyle = 'white';
ctx.lineWidth = 5;
ctx.beginPath();
let drawing = false;
//이벤트 처리
canvas.addEventListener('mousedown', draw.start);
canvas.addEventListener('mousemove', draw.move);
canvas.addEventListener('mouseup', draw.end);
//삭제 버튼 이벤트 연결
let clear = document.getElementById('clear');
clear.addEventListener('click', draw.clear);
shape.setShape();
//색상 배열
let color_map = [
{'value':'white', 'name':'하얀색'},
{'value':'red', 'name':'빨간색'},
{'value':'orange', 'name':'주황색'},
{'value':'yellow', 'name':'노란색'},
{'value':'blue', 'name':'파란색'},
{'value':'green', 'name':'녹색'},
{'value':'black', 'name':'검정색'},
]
let pen_color = document.getElementById("pen_color");
let pen_width = document.getElementById("pen_width");
let pen_shape = document.getElementById("pen_shape");
//색상 추가
for(let key in color_map){
let newOption = new Option(color_map[key].name, color_map[key].value);
pen_color.append(newOption);
}
//두께 추가
for(let i=1; i<=15; i=i+1){
let newOption = new Option(i,i);
pen_width.append(newOption);
}
pen_color.addEventListener('change', shape.change);
pen_width.addEventListener('change', shape.change);
pen_shape.addEventListener('change', shape.change);
})
//옵션 설정하는 객체
let shape = {
color:'white',
width:3,
change:function(){
//select의 선택된 값을 찾아오기
let color = pen_color.options[pen_color.selectedIndex].value;
let width = pen_width.options[pen_width.selectedIndex].value;
shape.setShape(color, width);
},
setShape:function(color, width){
if(color != null){
this.color = color;
}
if(width != null){
this.width=width;
}
ctx.strokeStyle=this.color;
ctx.lineWidth=this.width;
//선택한 선 모양을 출력
ctx.clearRect(703, 0, 860, 90);
ctx.beginPath();
ctx.moveTo(710, 55);
ctx.lineTo(820, 55);
ctx.stroke();
}
}
//선을 그려주는 함수를 소유한 객체
let draw = {
drawing:null,
start:function(e){
ctx.beginPath();
ctx.moveTo(e.pageX, e.pageY);
this.drawing = true;
msg.line.send('start', e.pageX, e.pageY);
},
move:function(e){
if(this.drawing){
ctx.lineTo(e.pageX, e.pageY);
ctx.stroke();
msg.line.send('move', e.pageX, e.pageY);
}
},
end:function(e){
this.drawing = false;
msg.line.send('end');
},
clear:(e) => {
//캔버스의 영역에 해당하는 부분의 그림 삭제
ctx.clearRect(0,0,860,645);
msg.line.send('clear');
},
drawFromServer:(data)=>{
if(data.type === 'start'){
ctx.beginPath();
ctx.moveTo(data.x, data.y);
ctx.strokeStyle = data.color;
ctx.lineWidth = data.width;
}else if(data.type === 'move'){
ctx.lineTo(data.x, data.y);
ctx.stroke();
}else if(data.type === 'end'){
}else if(data.type === 'clear'){
ctx.clearRect(0, 0, cv.width, cv.height);
shape.setShape();
}
}
}
//웹 소켓 통신을 위한 객체
let msg = {
line:{
send:function(type, x, y){
console.log(type, x, y);
socket.emit('linesend', {
'type':type,
'x':x,
'y':y,
'color':shape.color,
'width':shape.width
})
}
}
}