Doit! - 채팅서버 만들기
Last updated
Last updated
노드에서 채팅 기능을 만들려면 **웹 소켓(Web Socket)**을 사용해야한다. 웹 소켓은 웹 서버로 소켓을 연결한 후 데이터를 주고받을 수 있도록 만든 HTML5표준이다. 이러한 웹 소켓은 HTTP 프로토콜로 소켓 연결을 하기 때문에 웹 브라우저가 이 기능을 지원하지 않으면 사용할 수 없다. socket.io모듈을 사용하면 웹 소켓을 지원하지 않는 웹 브라우저에서도 웹 소켓을 사용할 수 있다.
$ npm install socket.io --save
socket.io
를 사용하려면 cors
모듈도 설치되어 있어야한다. CORS(Corss-Origin Resource Sharing)를 사용하면 Ajax를 사용해 데이터를 가져올 때 현재 보고 있는 브라우저의 웹 문서를 제공한 웹 서버 이외에 다른 웹 서버에서는 접속할 수 없는 제약이 풀린다.
$ npm install cors --save
파일 or 폴더 | 설명 |
---|---|
//app.js
...
// socket.io 모듈 불러들이기
var socketio = require('socket.io');
// cors 사용 - 클라이언트에서 ajax로 요청하면 CORS 지원
var cors = require('cors');
...
//cors를 미들웨어로 사용하도록 등록
app.use(cors());
...
// socket.io 서버를 시작
var io = socketio.listen(server);
console.log("socket.io 요청을 받아들일 준비가 되었습니다.");
http모듈로 실행한 익스프레스 서버는 server변수에 저장되어 있으므로 그 아랫부분에서 socket.io모듈 객체의 listen()
메소드를 호출한다.
socket.io 서버를 웹 서버 위에서 동작하도록 설정하면 웹 소켓과 관련된 요청은 모두 socket.io에서 처리한다. socket.io객체는 io변수에 할당되었는데 이 객체에 들어 있는 sockets객체는 클라이언트가 접속하거나 데이터를 전송했을 때 이벤트를 발생시킨다.
...
//클라이언트가 연결했을 때의 이벤트 처리
io.sockets.on('connection',function(socket){
console.log('connection info : ',socket.request.connection._peername);
// 소켓 객체에 클라이언트 Host, Port 정보 속성으로 추가
socket.remoteAddress = socket.request.connection._peername.address;
socket.remotePort = socket.request.connection._peername.port;
});
on()
메소드로 connection 이벤트를 처리하는 콜백함수를 등록하면 콜백 함수 쪽으로 소켓 객체가 전달된다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>채팅 클라이언트 01</title>
<script src="jquery-3.1.1.min.js"></script>
<script src="socket.io.js"></script>
<script>
var host;
var port;
var socket;
// 문서 로딩 후 실행됨
$(function() {
$("#connectButton").bind('click', function(event) {
println('connectButton이 클릭되었습니다.');
host = $('#hostInput').val();
port = $('#portInput').val();
connectToServer();
});
});
// 서버에 연결하는 함수 정의
function connectToServer() {
var options = {'forceNew':true};
var url = 'http://' + host + ':' + port;
socket = io.connect(url, options);
socket.on('connect', function() {
println('웹소켓 서버에 연결되었습니다. : ' + url);
});
socket.on('disconnect', function() {
println('웹소켓 연결이 종료되었습니다.');
});
}
function println(data) {
console.log(data);
$('#result').append('<p>' + data + '</p>');
}
</script>
</head>
<body>
<h3>채팅 클라이언트 01</h3>
<br>
<div>
<input type="text" id="hostInput" value="localhost" />
<input type="text" id="portInput" value="3000" />
<input type="button" id="connectButton" value="연결하기" />
</div>
<hr/>
<p>결과 : </p>
<div id="result"></div>
</body>
</html>
socket.io는 메시지를 주고받을 때 이벤트 처리 방식을 사용한다.
이벤트의 이름은 마음대로 정할 수 있다. 즉, 사용자 정의 이벤트와 같다. Echo기능은 서버에 보낸 데이터를 그대로 다시 돌려받는 기능이다.
socket.on('connect', function() {
println('웹소켓 서버에 연결되었습니다. : ' + url);
socket.on('message',function(message){
console.log(JSON.stringify(message));
println('<p>수신 메시지 : '+message.sender+','+message.recepient+','+ message.command+','+message.type+','+message.data+'</p>');
});
});
sender : 보내는 사람의 아이디
recepient : 받는 사람의 아이디
command : 데이터의 종류
type : 전송될 데이터의 형태
data : 데이터
일대일 채팅은 상대방을 지정하여 메시지를 보내야 하므로 서버에 연결된 각 클라이언트마다 고유한 정보가 있어야한다. 클라이언트가 로그인할 때 사용하는 로그인 아이디를 사용해 클라이언트를 구별할 수 있도록 하는 것이 좋다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>채팅 클라이언트 03</title>
<script src="jquery-3.1.1.min.js"></script>
<script src="socket.io.js"></script>
<script>
var host;
var port;
var socket;
// 문서 로딩 후 실행됨
$(function() {
// 연결하기 버튼 클릭 처리
$("#connectButton").bind('click', function(event) {
println('connectButton이 클릭되었습니다.');
host = $('#hostInput').val();
port = $('#portInput').val();
connectToServer();
});
// 전송 버튼 클릭 시 처리
$("#sendButton").bind('click', function(event) {
var sender = $('#senderInput').val();
var recepient = $('#recepientInput').val();
var data = $('#dataInput').val();
var output = {sender:sender, recepient:recepient, command:'chat', type:'text', data:data};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('message', output);
});
// 로그인 버튼 클릭 시 처리
$("#loginButton").bind('click', function(event) {
var id = $('#idInput').val();
var password = $('#passwordInput').val();
var alias = $('#aliasInput').val();
var today = $('#todayInput').val();
var output = {id:id, password:password, alias:alias, today:today};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('login', output);
});
});
// 서버에 연결하는 함수 정의
function connectToServer() {
var options = {'forceNew':true};
var url = 'http://' + host + ':' + port;
socket = io.connect(url, options);
socket.on('connect', function() {
println('웹소켓 서버에 연결되었습니다. : ' + url);
socket.on('message', function(message) {
console.log(JSON.stringify(message));
println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.command + ', ' + message.data + '</p>');
});
socket.on('response', function(response) {
console.log(JSON.stringify(response));
println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
});
});
socket.on('disconnect', function() {
println('웹소켓 연결이 종료되었습니다.');
});
}
function println(data) {
console.log(data);
$('#result').append('<p>' + data + '</p>');
}
</script>
</head>
<body>
<h3>채팅 클라이언트 03 : 일대일 채팅하기</h3>
<br>
<div>
<input type="text" id="hostInput" value="localhost" />
<input type="text" id="portInput" value="3000" />
<input type="button" id="connectButton" value="연결하기" />
</div>
<br>
<div>
<input type="text" id="idInput" value="test01" />
<input type="password" id="passwordInput" value="123456" />
<input type="text" id="aliasInput" value="소녀시대" />
<input type="text" id="todayInput" value="좋은 날!" />
<input type="button" id="loginButton" value="로그인" />
<input type="button" id="logoutButton" value="로그아웃" />
</div>
<br>
<div>
<div><span>보내는사람 아이디 :</span> <input type="text" id="senderInput" value="test01" /></div>
<div><span>받는사람 아이디 :</span> <input type="text" id="recepientInput" value="ALL" /></div>
<div><span>메시지 데이터 :</span> <input type="text" id="dataInput" value="안녕!"/> </div>
<br>
<input type="button" id="sendButton" value="전송" />
</div>
<hr/>
<p>결과 : </p>
<div id="result"></div>
</body>
</html>
// Express 기본 모듈 불러오기
var express = require('express')
, http = require('http')
, path = require('path');
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
, cookieParser = require('cookie-parser')
, static = require('serve-static')
, errorHandler = require('errorhandler');
// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');
// Session 미들웨어 불러오기
var expressSession = require('express-session');
//===== Passport 사용 =====//
var passport = require('passport');
var flash = require('connect-flash');
// 모듈로 분리한 설정 파일 불러오기
var config = require('./config/config');
// 모듈로 분리한 데이터베이스 파일 불러오기
var database = require('./database/database');
// 모듈로 분리한 라우팅 파일 불러오기
var route_loader = require('./routes/route_loader');
// Socket.IO 사용
var socketio = require('socket.io');
// cors 사용 - 클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
var cors = require('cors');
// 익스프레스 객체 생성
var app = express();
//===== 뷰 엔진 설정 =====//
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
console.log('뷰 엔진이 ejs로 설정되었습니다.');
//===== 서버 변수 설정 및 static으로 public 폴더 설정 =====//
console.log('config.server_port : %d', config.server_port);
app.set('port', process.env.PORT || 3000);
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))
// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())
// public 폴더를 static으로 오픈
app.use('/public', static(path.join(__dirname, 'public')));
// cookie-parser 설정
app.use(cookieParser());
// 세션 설정
app.use(expressSession({
secret:'my key',
resave:true,
saveUninitialized:true
}));
//===== Passport 사용 설정 =====//
// Passport의 세션을 사용할 때는 그 전에 Express의 세션을 사용하는 코드가 있어야 함
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
//클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
app.use(cors());
//라우팅 정보를 읽어들여 라우팅 설정
var router = express.Router();
route_loader.init(app, router);
// 패스포트 설정
var configPassport = require('./config/passport');
configPassport(app, passport);
// 패스포트 라우팅 설정
var userPassport = require('./routes/user_passport');
userPassport(router, passport);
//===== 404 에러 페이지 처리 =====//
var errorHandler = expressErrorHandler({
static: {
'404': './public/404.html'
}
});
app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );
//===== 서버 시작 =====//
//확인되지 않은 예외 처리 - 서버 프로세스 종료하지 않고 유지함
process.on('uncaughtException', function (err) {
console.log('uncaughtException 발생함 : ' + err);
console.log('서버 프로세스 종료하지 않고 유지함.');
console.log(err.stack);
});
// 프로세스 종료 시에 데이터베이스 연결 해제
process.on('SIGTERM', function () {
console.log("프로세스가 종료됩니다.");
app.close();
});
app.on('close', function () {
console.log("Express 서버 객체가 종료됩니다.");
if (database.db) {
database.db.close();
}
});
// 시작된 서버 객체를 리턴받도록 합니다.
var server = http.createServer(app).listen(app.get('port'), function(){
console.log('서버가 시작되었습니다. 포트 : ' + app.get('port'));
// 데이터베이스 초기화
database.init(app, config);
});
//===== Socket.IO를 이용한 채팅 테스트 부분 =====//
// 로그인 아이디 매핑 (로그인 ID -> 소켓 ID)
var login_ids = {};
// socket.io 서버를 시작합니다.
var io = socketio.listen(server);
console.log('socket.io 요청을 받아들일 준비가 되었습니다.');
// 클라이언트가 연결했을 때의 이벤트 처리
io.sockets.on('connection', function(socket) {
console.log('connection info :', socket.request.connection._peername);
// 소켓 객체에 클라이언트 Host, Port 정보 속성으로 추가
socket.remoteAddress = socket.request.connection._peername.address;
socket.remotePort = socket.request.connection._peername.port;
// 'login' 이벤트를 받았을 때의 처리
socket.on('login', function(login) {
console.log('login 이벤트를 받았습니다.');
console.dir(login);
// 기존 클라이언트 ID가 없으면 클라이언트 ID를 맵에 추가
console.log('접속한 소켓의 ID : ' + socket.id);
login_ids[login.id] = socket.id;
socket.login_id = login.id;
console.log('접속한 클라이언트 ID 갯수 : %d', Object.keys(login_ids).length);
// 응답 메시지 전송
sendResponse(socket, 'login', '200', '로그인되었습니다.');
});
// 'message' 이벤트를 받았을 때의 처리
socket.on('message', function(message) {
console.log('message 이벤트를 받았습니다.');
console.dir(message);
if (message.recepient =='ALL') {
// 나를 포함한 모든 클라이언트에게 메시지 전달
console.dir('나를 포함한 모든 클라이언트에게 message 이벤트를 전송합니다.')
io.sockets.emit('message', message);
} else {
// 일대일 채팅 대상에게 메시지 전달
if (login_ids[message.recepient]) {
io.sockets.connected[login_ids[message.recepient]].emit('message', message);
// 응답 메시지 전송
sendResponse(socket, 'message', '200', '메시지를 전송했습니다.');
} else {
// 응답 메시지 전송
sendResponse(socket, 'login', '404', '상대방의 로그인 ID를 찾을 수 없습니다.');
}
}
});
});
// 응답 메시지 전송 메소드
function sendResponse(socket, command, code, message) {
var statusObj = {command: command, code: code, message: message};
socket.emit('response', statusObj);
}
대상 소켓을 찾기위해서는 io.sockets.connected[login_ids[messagae.recepient]]
를 사용해야한다.
배열의 요소는 delete
키워드나 배열의 splice()
메소드등을 이용해 여러가지 방법을 사용할 수 있다.
그룹 채팅은 방을 만들고 그 방에 초대된 사람끼리 동시에 메시지를 주고받는다.
socket.io모듈은 몇 명의 사용자를 하나으 ㅣ방에 모아 두고 방에 들어온 특정 클라이언트에만 메시지를 전송할 수 있는 기능을 제공한다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>채팅 클라이언트 04</title>
<script src="jquery-3.1.1.min.js"></script>
<script src="socket.io.js"></script>
<script>
var host;
var port;
var socket;
// 문서 로딩 후 실행됨
$(function() {
// 연결하기 버튼 클릭 처리
$("#connectButton").bind('click', function(event) {
println('connectButton이 클릭되었습니다.');
host = $('#hostInput').val();
port = $('#portInput').val();
connectToServer();
});
// 전송 버튼 클릭 시 처리
$("#sendButton").bind('click', function(event) {
var sender = $('#senderInput').val();
var recepient = $('#recepientInput').val();
var data = $('#dataInput').val();
var output = {sender:sender, recepient:recepient, command:'chat', type:'text', data:data};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('message', output);
});
// 로그인 버튼 클릭 시 처리
$("#loginButton").bind('click', function(event) {
var id = $('#idInput').val();
var password = $('#passwordInput').val();
var alias = $('#aliasInput').val();
var today = $('#todayInput').val();
var output = {id:id, password:password, alias:alias, today:today};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('login', output);
});
// 방만들기 버튼 클릭 시 처리
$("#createRoomButton").bind('click', function(event) {
var roomId = $('#roomIdInput').val();
var roomName = $('#roomNameInput').val();
var id = $('#idInput').val();
var output = {command:'create', roomId:roomId, roomName:roomName, roomOwner:id};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('room', output);
});
// 방이름바꾸기 버튼 클릭 시 처리
$("#updateRoomButton").bind('click', function(event) {
var roomId = $('#roomIdInput').val();
var roomName = $('#roomNameInput').val();
var id = $('#idInput').val();
var output = {command:'update', roomId:roomId, roomName:roomName, roomOwner:id};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('room', output);
});
// 방없애기 버튼 클릭 시 처리
$("#deleteRoomButton").bind('click', function(event) {
var roomId = $('#roomIdInput').val();
var output = {command:'delete', roomId:roomId};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('room', output);
});
});
// 서버에 연결하는 함수 정의
function connectToServer() {
var options = {'forceNew':true};
var url = 'http://' + host + ':' + port;
socket = io.connect(url, options);
socket.on('connect', function() {
println('웹소켓 서버에 연결되었습니다. : ' + url);
socket.on('message', function(message) {
console.log(JSON.stringify(message));
println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.command + ', ' + message.data + '</p>');
});
socket.on('response', function(response) {
console.log(JSON.stringify(response));
println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
});
// 그룹 채팅에서 방과 관련된 이벤트 처리
socket.on('room', function(data) {
console.log(JSON.stringify(data));
println('<p>방 이벤트 : ' + data.command + '</p>');
println('<p>방 리스트를 받았습니다.</p>');
if (data.command == 'list') { // 방 리스트
var roomCount = data.rooms.length;
$("#roomList").html('<p>방 리스트 ' + roomCount + '개</p>');
for (var i = 0; i < roomCount; i++) {
$("#roomList").append('<p>방 #' + i + ' : ' + data.rooms[i].id + ', ' + data.rooms[i].name + ', ' + data.rooms[i].owner + '</p>');
}
}
});
});
socket.on('disconnect', function() {
println('웹소켓 연결이 종료되었습니다.');
});
}
function println(data) {
console.log(data);
$('#result').append('<p>' + data + '</p>');
}
</script>
</head>
<body>
<h3>채팅 클라이언트 04 : 그룹 채팅하기</h3>
<br>
<div>
<input type="text" id="hostInput" value="localhost" />
<input type="text" id="portInput" value="3000" />
<input type="button" id="connectButton" value="연결하기" />
</div>
<br>
<div>
<input type="text" id="idInput" value="test01" />
<input type="password" id="passwordInput" value="123456" />
<input type="text" id="aliasInput" value="소녀시대" />
<input type="text" id="todayInput" value="좋은 날!" />
<input type="button" id="loginButton" value="로그인" />
<input type="button" id="logoutButton" value="로그아웃" />
</div>
<br>
<div>
<input type="text" id="roomIdInput" value="meeting01" />
<input type="text" id="roomNameInput" value="청춘들의대화" />
<input type="button" id="createRoomButton" value="방만들기" />
<input type="button" id="updateRoomButton" value="방이름바꾸기" />
<input type="button" id="deleteRoomButton" value="방없애기" />
</div>
<br>
<div id="roomList">
</div>
<br>
<div>
<div><span>보내는사람 아이디 :</span> <input type="text" id="senderInput" value="test01" /></div>
<div><span>받는사람 아이디 :</span> <input type="text" id="recepientInput" value="ALL" /></div>
<!-- command 선택 <select> 채팅, 그룹채팅 -->
<div><span>메시지 데이터 :</span> <input type="text" id="dataInput" value="안녕!"/> </div>
<br>
<input type="button" id="sendButton" value="전송" />
</div>
<hr/>
<p>결과 : </p>
<div id="result"></div>
</body>
</html>
클라이언트에서는 만든 방의 리스트만 전달받는다.
// Express 기본 모듈 불러오기
var express = require('express')
, http = require('http')
, path = require('path');
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
, cookieParser = require('cookie-parser')
, static = require('serve-static')
, errorHandler = require('errorhandler');
// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');
// Session 미들웨어 불러오기
var expressSession = require('express-session');
//===== Passport 사용 =====//
var passport = require('passport');
var flash = require('connect-flash');
// 모듈로 분리한 설정 파일 불러오기
var config = require('./config/config');
// 모듈로 분리한 데이터베이스 파일 불러오기
var database = require('./database/database');
// 모듈로 분리한 라우팅 파일 불러오기
var route_loader = require('./routes/route_loader');
// Socket.IO 사용
var socketio = require('socket.io');
// cors 사용 - 클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
var cors = require('cors');
// 익스프레스 객체 생성
var app = express();
//===== 뷰 엔진 설정 =====//
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
console.log('뷰 엔진이 ejs로 설정되었습니다.');
//===== 서버 변수 설정 및 static으로 public 폴더 설정 =====//
console.log('config.server_port : %d', config.server_port);
app.set('port', process.env.PORT || 3000);
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))
// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())
// public 폴더를 static으로 오픈
app.use('/public', static(path.join(__dirname, 'public')));
// cookie-parser 설정
app.use(cookieParser());
// 세션 설정
app.use(expressSession({
secret:'my key',
resave:true,
saveUninitialized:true
}));
//===== Passport 사용 설정 =====//
// Passport의 세션을 사용할 때는 그 전에 Express의 세션을 사용하는 코드가 있어야 함
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
//클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
app.use(cors());
//라우팅 정보를 읽어들여 라우팅 설정
var router = express.Router();
route_loader.init(app, router);
// 패스포트 설정
var configPassport = require('./config/passport');
configPassport(app, passport);
// 패스포트 라우팅 설정
var userPassport = require('./routes/user_passport');
userPassport(router, passport);
//===== 404 에러 페이지 처리 =====//
var errorHandler = expressErrorHandler({
static: {
'404': './public/404.html'
}
});
app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );
//===== 서버 시작 =====//
//확인되지 않은 예외 처리 - 서버 프로세스 종료하지 않고 유지함
process.on('uncaughtException', function (err) {
console.log('uncaughtException 발생함 : ' + err);
console.log('서버 프로세스 종료하지 않고 유지함.');
console.log(err.stack);
});
// 프로세스 종료 시에 데이터베이스 연결 해제
process.on('SIGTERM', function () {
console.log("프로세스가 종료됩니다.");
app.close();
});
app.on('close', function () {
console.log("Express 서버 객체가 종료됩니다.");
if (database.db) {
database.db.close();
}
});
// 시작된 서버 객체를 리턴받도록 합니다.
var server = http.createServer(app).listen(app.get('port'), function(){
console.log('서버가 시작되었습니다. 포트 : ' + app.get('port'));
// 데이터베이스 초기화
database.init(app, config);
});
//===== Socket.IO를 이용한 채팅 테스트 부분 =====//
// 로그인 아이디 매핑 (로그인 ID -> 소켓 ID)
var login_ids = {};
// socket.io 서버를 시작합니다.
var io = socketio.listen(server);
console.log('socket.io 요청을 받아들일 준비가 되었습니다.');
// 클라이언트가 연결했을 때의 이벤트 처리
io.sockets.on('connection', function(socket) {
console.log('connection info :', socket.request.connection._peername);
// 소켓 객체에 클라이언트 Host, Port 정보 속성으로 추가
socket.remoteAddress = socket.request.connection._peername.address;
socket.remotePort = socket.request.connection._peername.port;
// 'login' 이벤트를 받았을 때의 처리
socket.on('login', function(login) {
console.log('login 이벤트를 받았습니다.');
console.dir(login);
// 기존 클라이언트 ID가 없으면 클라이언트 ID를 맵에 추가
console.log('접속한 소켓의 ID : ' + socket.id);
login_ids[login.id] = socket.id;
socket.login_id = login.id;
console.log('접속한 클라이언트 ID 갯수 : %d', Object.keys(login_ids).length);
// 응답 메시지 전송
sendResponse(socket, 'login', '200', '로그인되었습니다.');
});
// 'message' 이벤트를 받았을 때의 처리
socket.on('message', function(message) {
console.log('message 이벤트를 받았습니다.');
console.dir(message);
if (message.recepient =='ALL') {
// 나를 포함한 모든 클라이언트에게 메시지 전달
console.dir('나를 포함한 모든 클라이언트에게 message 이벤트를 전송합니다.')
io.sockets.emit('message', message);
} else {
// 일대일 채팅 대상에게 메시지 전달
if (login_ids[message.recepient]) {
io.sockets.connected[login_ids[message.recepient]].emit('message', message);
// 응답 메시지 전송
sendResponse(socket, 'message', '200', '메시지를 전송했습니다.');
} else {
// 응답 메시지 전송
sendResponse(socket, 'login', '404', '상대방의 로그인 ID를 찾을 수 없습니다.');
}
}
});
// room 이벤트를 받았을 때의 처리
socket.on('room',function(room){
console.log("room 이벤트를 받았습니다.");
console.dir(room);
if(room.command == 'create'){
if(io.sockets.adapter.rooms[room.roomId]){//방이 이미 만들어져 있는경우
console.log("방이 이미 만들어져 있습니다.");
}else{
console.log("방을 새로 만듭니다.");
socket.join(room.roomId);
var curRoom = io.sockets.adapter.rooms[room.roomId];
curRoom.id = room.roomId;
curRoom.name = room.roomName;
curRoom.owner = room.roomOwner;
}
}else if(room.command == 'update'){
var curRoom = io.sockets.adapter.rooms[room.roomId];
curRoom.id = room.roomId;
curRoom.name = room.roomName;
curRoom.owner = room.roomOwner;
}else if (room.command == 'delete') {
socket.leave(room.roomId);
if(io.sockets.adapter.rooms[room.roomId]){//방이 만들어져 있는 경우
delete io.sockets.adapter.rooms[room.roomId];
}else{
console.log("방이 만들어져 있지 않습니다.");
}
}
var roomList = getRoomList();
var output = {command : 'list',rooms: roomList};
console.log("클라이언트로 보낼 데이터 : "+JSON.stringify(output));
io.sockets.emit('room',output);
});
});
function getRoomList() {
console.dir(io.sockets.adapter.rooms);
var roomList = [];
Object.keys(io.sockets.adapter.rooms).forEach(function(roomId) { // for each room
console.log('current room id : ' + roomId);
var outRoom = io.sockets.adapter.rooms[roomId];
// find default room using all attributes
var foundDefault = false;
var index = 0;
Object.keys(outRoom.sockets).forEach(function(key) {
console.log('#' + index + ' : ' + key + ', ' + outRoom.sockets[key]);
if (roomId == key) { // default room
foundDefault = true;
console.log('this is default room.');
}
index++;
});
if (!foundDefault) {
roomList.push(outRoom);
}
});
console.log('[ROOM LIST]');
console.dir(roomList);
return roomList;
}
// 응답 메시지 전송 메소드
function sendResponse(socket, command, code, message) {
var statusObj = {command: command, code: code, message: message};
socket.emit('response', statusObj);
}
socket.io 모듈에서 방정보를 관리하고 있다. 방 객체에 속성을 추가하는 것도 가능하다. 방 정보는 io.sockets.adapter.rooms
에 들어있다. getRoomList()
는 처음부터 만들어져 있던 방이다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>채팅 클라이언트 05</title>
<script src="jquery-3.1.1.min.js"></script>
<script src="socket.io.js"></script>
<script>
var host;
var port;
var socket;
// 문서 로딩 후 실행됨
$(function() {
// 연결하기 버튼 클릭 처리
$("#connectButton").bind('click', function(event) {
println('connectButton이 클릭되었습니다.');
host = $('#hostInput').val();
port = $('#portInput').val();
connectToServer();
});
// 전송 버튼 클릭 시 처리
$("#sendButton").bind('click', function(event) {
// chattype 구분
var chattype = $('#chattype option:selected').val();
var sender = $('#senderInput').val();
var recepient = $('#recepientInput').val();
var data = $('#dataInput').val();
var output = {sender:sender, recepient:recepient, command:chattype, type:'text', data:data};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('message', output);
});
// 로그인 버튼 클릭 시 처리
$("#loginButton").bind('click', function(event) {
var id = $('#idInput').val();
var password = $('#passwordInput').val();
var alias = $('#aliasInput').val();
var today = $('#todayInput').val();
var output = {id:id, password:password, alias:alias, today:today};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('login', output);
});
// 방만들기 버튼 클릭 시 처리
$("#createRoomButton").bind('click', function(event) {
var roomId = $('#roomIdInput').val();
var roomName = $('#roomNameInput').val();
var id = $('#idInput').val();
var output = {command:'create', roomId:roomId, roomName:roomName, roomOwner:id};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('room', output);
});
// 방이름바꾸기 버튼 클릭 시 처리
$("#updateRoomButton").bind('click', function(event) {
var roomId = $('#roomIdInput').val();
var roomName = $('#roomNameInput').val();
var id = $('#idInput').val();
var output = {command:'update', roomId:roomId, roomName:roomName, roomOwner:id};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('room', output);
});
// 방없애기 버튼 클릭 시 처리
$("#deleteRoomButton").bind('click', function(event) {
var roomId = $('#roomIdInput').val();
var output = {command:'delete', roomId:roomId};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('room', output);
});
// 방입장하기 버튼 클릭 시 처리
$("#joinRoomButton").bind('click', function(event) {
var roomId = $('#roomIdInput').val();
var output = {command:'join', roomId:roomId};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('room', output);
});
// 방나가기 버튼 클릭 시 처리
$("#leaveRoomButton").bind('click', function(event) {
var roomId = $('#roomIdInput').val();
var output = {command:'leave', roomId:roomId};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('room', output);
});
});
// 서버에 연결하는 함수 정의
function connectToServer() {
var options = {'forceNew':true};
var url = 'http://' + host + ':' + port;
socket = io.connect(url, options);
socket.on('connect', function() {
println('웹소켓 서버에 연결되었습니다. : ' + url);
socket.on('message', function(message) {
console.log(JSON.stringify(message));
println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.command + ', ' + message.data + '</p>');
});
socket.on('response', function(response) {
console.log(JSON.stringify(response));
println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
});
// 그룹 채팅에서 방과 관련된 이벤트 처리
socket.on('room', function(data) {
console.log(JSON.stringify(data));
println('<p>방 이벤트 : ' + data.command + '</p>');
println('<p>방 리스트를 받았습니다.</p>');
if (data.command == 'list') { // 방 리스트
var roomCount = data.rooms.length;
$("#roomList").html('<p>방 리스트 ' + roomCount + '개</p>');
for (var i = 0; i < roomCount; i++) {
$("#roomList").append('<p>방 #' + i + ' : ' + data.rooms[i].id + ', ' + data.rooms[i].name + ', ' + data.rooms[i].owner + '</p>');
}
}
});
});
socket.on('disconnect', function() {
println('웹소켓 연결이 종료되었습니다.');
});
}
function println(data) {
console.log(data);
$('#result').append('<p>' + data + '</p>');
}
</script>
</head>
<body>
<h3>채팅 클라이언트 05 : 그룹 채팅하기</h3>
<br>
<div>
<input type="text" id="hostInput" value="localhost" />
<input type="text" id="portInput" value="3000" />
<input type="button" id="connectButton" value="연결하기" />
</div>
<br>
<div>
<input type="text" id="idInput" value="test01" />
<input type="password" id="passwordInput" value="123456" />
<input type="text" id="aliasInput" value="소녀시대" />
<input type="text" id="todayInput" value="좋은 날!" />
<input type="button" id="loginButton" value="로그인" />
<input type="button" id="logoutButton" value="로그아웃" />
</div>
<br>
<div>
<input type="text" id="roomIdInput" value="meeting01" />
<input type="text" id="roomNameInput" value="청춘들의대화" />
<input type="button" id="createRoomButton" value="방만들기" />
<input type="button" id="updateRoomButton" value="방이름바꾸기" />
<input type="button" id="deleteRoomButton" value="방없애기" />
</div>
<br>
<div id="roomList">
</div>
<br>
<div>
<input type="button" id="joinRoomButton" value="방입장하기" />
<input type="button" id="leaveRoomButton" value="방나가기" />
</div>
<br>
<div>
<div>
<span>보내는사람 아이디 :</span>
<input type="text" id="senderInput" value="test01" />
</div>
<div>
<span>받는사람 아이디 :</span>
<input type="text" id="recepientInput" value="ALL" />
</div>
<!-- command 선택 <select> 채팅, 그룹채팅 -->
<select name="chattype" id="chattype">
<option value="chat">채팅</option>
<option value="groupchat" selected>그룹채팅</option>
</select>
<div>
<span>메시지 데이터 :</span>
<input type="text" id="dataInput" value="안녕!"/>
</div>
<br>
<input type="button" id="sendButton" value="전송" />
</div>
<hr/>
<p>결과 : </p>
<div id="result"></div>
</body>
</html>
일대일 채팅에서는 io.sockets.connected
객체에 들어 있는 소켓 객체들 중에서 대상이 되는 소켓 객체를 소켓 ID로 찾은 후 emit()
메소드를 호출하여 메시지를 전송했다. 이와 다르게 그룹 채팅에서는 io.sockets.in()
메소드를 사용해 대상이 되는 방에 들어 있는 소켓 객체들을 찾은 후 메시지를 전송한다.
// Express 기본 모듈 불러오기
var express = require('express')
, http = require('http')
, path = require('path');
// Express의 미들웨어 불러오기
var bodyParser = require('body-parser')
, cookieParser = require('cookie-parser')
, static = require('serve-static')
, errorHandler = require('errorhandler');
// 에러 핸들러 모듈 사용
var expressErrorHandler = require('express-error-handler');
// Session 미들웨어 불러오기
var expressSession = require('express-session');
//===== Passport 사용 =====//
var passport = require('passport');
var flash = require('connect-flash');
// 모듈로 분리한 설정 파일 불러오기
var config = require('./config/config');
// 모듈로 분리한 데이터베이스 파일 불러오기
var database = require('./database/database');
// 모듈로 분리한 라우팅 파일 불러오기
var route_loader = require('./routes/route_loader');
// Socket.IO 사용
var socketio = require('socket.io');
// cors 사용 - 클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
var cors = require('cors');
// 익스프레스 객체 생성
var app = express();
//===== 뷰 엔진 설정 =====//
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
console.log('뷰 엔진이 ejs로 설정되었습니다.');
//===== 서버 변수 설정 및 static으로 public 폴더 설정 =====//
console.log('config.server_port : %d', config.server_port);
app.set('port', process.env.PORT || 3000);
// body-parser를 이용해 application/x-www-form-urlencoded 파싱
app.use(bodyParser.urlencoded({ extended: false }))
// body-parser를 이용해 application/json 파싱
app.use(bodyParser.json())
// public 폴더를 static으로 오픈
app.use('/public', static(path.join(__dirname, 'public')));
// cookie-parser 설정
app.use(cookieParser());
// 세션 설정
app.use(expressSession({
secret:'my key',
resave:true,
saveUninitialized:true
}));
//===== Passport 사용 설정 =====//
// Passport의 세션을 사용할 때는 그 전에 Express의 세션을 사용하는 코드가 있어야 함
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
//클라이언트에서 ajax로 요청 시 CORS(다중 서버 접속) 지원
app.use(cors());
//라우팅 정보를 읽어들여 라우팅 설정
var router = express.Router();
route_loader.init(app, router);
// 패스포트 설정
var configPassport = require('./config/passport');
configPassport(app, passport);
// 패스포트 라우팅 설정
var userPassport = require('./routes/user_passport');
userPassport(router, passport);
//===== 404 에러 페이지 처리 =====//
var errorHandler = expressErrorHandler({
static: {
'404': './public/404.html'
}
});
app.use( expressErrorHandler.httpError(404) );
app.use( errorHandler );
//===== 서버 시작 =====//
//확인되지 않은 예외 처리 - 서버 프로세스 종료하지 않고 유지함
process.on('uncaughtException', function (err) {
console.log('uncaughtException 발생함 : ' + err);
console.log('서버 프로세스 종료하지 않고 유지함.');
console.log(err.stack);
});
// 프로세스 종료 시에 데이터베이스 연결 해제
process.on('SIGTERM', function () {
console.log("프로세스가 종료됩니다.");
app.close();
});
app.on('close', function () {
console.log("Express 서버 객체가 종료됩니다.");
if (database.db) {
database.db.close();
}
});
// 시작된 서버 객체를 리턴받도록 합니다.
var server = http.createServer(app).listen(app.get('port'), function(){
console.log('서버가 시작되었습니다. 포트 : ' + app.get('port'));
// 데이터베이스 초기화
database.init(app, config);
});
//===== Socket.IO를 이용한 채팅 테스트 부분 =====//
// 로그인 아이디 매핑 (로그인 ID -> 소켓 ID)
var login_ids = {};
// socket.io 서버를 시작합니다.
var io = socketio.listen(server);
console.log('socket.io 요청을 받아들일 준비가 되었습니다.');
// 클라이언트가 연결했을 때의 이벤트 처리
io.sockets.on('connection', function(socket) {
console.log('connection info :', socket.request.connection._peername);
// 소켓 객체에 클라이언트 Host, Port 정보 속성으로 추가
socket.remoteAddress = socket.request.connection._peername.address;
socket.remotePort = socket.request.connection._peername.port;
// 'login' 이벤트를 받았을 때의 처리
socket.on('login', function(login) {
console.log('login 이벤트를 받았습니다.');
console.dir(login);
// 기존 클라이언트 ID가 없으면 클라이언트 ID를 맵에 추가
console.log('접속한 소켓의 ID : ' + socket.id);
login_ids[login.id] = socket.id;
socket.login_id = login.id;
console.log('접속한 클라이언트 ID 갯수 : %d', Object.keys(login_ids).length);
// 응답 메시지 전송
sendResponse(socket, 'login', '200', '로그인되었습니다.');
});
// 'message' 이벤트를 받았을 때의 처리
socket.on('message', function(message) {
console.log('message 이벤트를 받았습니다.');
console.dir(message);
if (message.recepient =='ALL') {
// 나를 포함한 모든 클라이언트에게 메시지 전달
console.dir('나를 포함한 모든 클라이언트에게 message 이벤트를 전송합니다.')
io.sockets.emit('message', message);
} else {
// command 속성으로 일대일 채팅과 그룹채팅 구분
if (message.command == 'chat') {
// 일대일 채팅 대상에게 메시지 전달
if (login_ids[message.recepient]) {
io.sockets.connected[login_ids[message.recepient]].emit('message', message);
// 응답 메시지 전송
sendResponse(socket, 'message', '200', '메시지를 전송했습니다.');
} else {
// 응답 메시지 전송
sendResponse(socket, 'login', '404', '상대방의 로그인 ID를 찾을 수 없습니다.');
}
} else if (message.command == 'groupchat') {
// 방에 들어있는 모든 사용자에게 메시지 전달
io.sockets.in(message.recepient).emit('message', message);
// 응답 메시지 전송
sendResponse(socket, 'message', '200', '방 [' + message.recepient + ']의 모든 사용자들에게 메시지를 전송했습니다.');
}
}
});
// room 이벤트를 받았을 때의 처리
socket.on('room',function(room){
console.log("room 이벤트를 받았습니다.");
console.dir(room);
if(room.command == 'create'){
if(io.sockets.adapter.rooms[room.roomId]){//방이 이미 만들어져 있는경우
console.log("방이 이미 만들어져 있습니다.");
}else{
console.log("방을 새로 만듭니다.");
socket.join(room.roomId);
var curRoom = io.sockets.adapter.rooms[room.roomId];
curRoom.id = room.roomId;
curRoom.name = room.roomName;
curRoom.owner = room.roomOwner;
}
}else if(room.command == 'update'){
var curRoom = io.sockets.adapter.rooms[room.roomId];
curRoom.id = room.roomId;
curRoom.name = room.roomName;
curRoom.owner = room.roomOwner;
}else if (room.command == 'delete') {
socket.leave(room.roomId);
if(io.sockets.adapter.rooms[room.roomId]){//방이 만들어져 있는 경우
delete io.sockets.adapter.rooms[room.roomId];
}else{
console.log("방이 만들어져 있지 않습니다.");
}
}else if(room.command == 'join'){
socket.join(room.roomId);
sendResponse(socket,'room','200','방에 입장했습니다.');
}else if(room.command == 'leave'){
socket.leave(room.roomId);
sendResponse(socket,'room','200','방에서 나갔습니다.');
}
var roomList = getRoomList();
var output = {command : 'list',rooms: roomList};
console.log("클라이언트로 보낼 데이터 : "+JSON.stringify(output));
io.sockets.emit('room',output);
});
});
function getRoomList() {
console.dir(io.sockets.adapter.rooms);
var roomList = [];
Object.keys(io.sockets.adapter.rooms).forEach(function(roomId) { // for each room
console.log('current room id : ' + roomId);
var outRoom = io.sockets.adapter.rooms[roomId];
// find default room using all attributes
var foundDefault = false;
var index = 0;
Object.keys(outRoom.sockets).forEach(function(key) {
console.log('#' + index + ' : ' + key + ', ' + outRoom.sockets[key]);
if (roomId == key) { // default room
foundDefault = true;
console.log('this is default room.');
}
index++;
});
if (!foundDefault) {
roomList.push(outRoom);
}
});
console.log('[ROOM LIST]');
console.dir(roomList);
return roomList;
}
// 응답 메시지 전송 메소드
function sendResponse(socket, command, code, message) {
var statusObj = {command: command, code: code, message: message};
socket.emit('response', statusObj);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
<title>일대일 채팅</title>
<link href="./semantic.min.css" rel="stylesheet" >
<style>
* {
padding:0;
margin:0;
box-sizing:border-box;
}
html {
width:100%;
height:100%;
}
body {
width:100%;
height:100%;
color: #000;
background-color: #fff;
}
.container {
width:100%;
height:100%;
display:flex;
flex-flow:column wrap;
align-items:center;
justify-content:center;
}
#cardbox {
width:94%;
height:94%;
padding-left:0.4em;
padding-right:0.4em;
}
#iconImage {
display:inline;
}
#titleText {
font-size:1.4em;
font-weight:bold;
color:#777;
}
#contentsText {
color:#999;
}
#result {
height:10em;
overflow:auto;
}
</style>
<script src="jquery-3.1.1.min.js"></script>
<script src="socket.io.js"></script>
<script src="semantic.min.js"></script>
<script>
var host;
var port;
var socket;
// 문서 로딩 후 실행됨
$(function() {
// 연결하기 버튼 클릭 처리
$("#connectButton").bind('click', function(event) {
println('connectButton이 클릭되었습니다.');
host = $('#hostInput').val();
port = $('#portInput').val();
connectToServer();
});
// 전송 버튼 클릭 시 처리
$("#sendButton").bind('click', function(event) {
var sender = $('#senderInput').val();
var recepient = $('#recepientInput').val();
var data = $('#dataInput').val();
var output = {sender:sender, recepient:recepient, command:'chat', type:'text', data:data};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('message', output);
});
// 로그인 버튼 클릭 시 처리
$("#loginButton").bind('click', function(event) {
var id = $('#idInput').val();
var password = $('#passwordInput').val();
var alias = $('#aliasInput').val();
var today = $('#todayInput').val();
var output = {id:id, password:password, alias:alias, today:today};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('login', output);
});
// 결과 지우기 버튼 클릭 시 처리
$("#clearButton").bind('click', function(event) {
$("#result").html('');
});
});
// 서버에 연결하는 함수 정의
function connectToServer() {
var options = {'forceNew':true};
var url = 'http://' + host + ':' + port;
socket = io.connect(url, options);
socket.on('connect', function() {
println('웹소켓 서버에 연결되었습니다. : ' + url);
socket.on('message', function(message) {
console.log(JSON.stringify(message));
println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.command + ', ' + message.data + '</p>');
});
socket.on('response', function(response) {
console.log(JSON.stringify(response));
println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
});
});
socket.on('disconnect', function() {
println('웹소켓 연결이 종료되었습니다.');
});
}
function println(data) {
console.log(data);
$('#result').append('<p>' + data + '</p>');
}
</script>
</head>
<body>
<div class="container">
<div id="cardbox" class="ui blue fluid card">
<div class="content">
<div class="left floated author">
<img id="iconImage" class="ui avatar image" src="./images/author.png">
</div>
<div>
<div id="titleText" class="header">일대일 채팅</div>
<div id="contentsText" class="description">
연결 및 로그인 후 메시지를 보내세요.
</div>
</div>
</div>
<br>
<!-- 연결하기 -->
<div>
<div class="ui input">
<input type="text" id="hostInput" value="localhost" />
</div>
<div class="ui input">
<input type="text" id="portInput" value="3000" />
</div>
<br><br>
<input class="ui primary button" type="button" id="connectButton" value="연결하기" />
</div>
<br>
<!-- 로그인/로그아웃 -->
<div>
<div class="ui input">
<input type="text" id="idInput" value="test01" />
</div>
<div class="ui input">
<input type="password" id="passwordInput" value="123456" />
</div>
<div class="ui input">
<input type="text" id="aliasInput" value="소녀시대" />
</div>
<div class="ui input">
<input type="text" id="todayInput" value="좋은 날!" />
</div>
<br><br>
<input class="ui primary button" type="button" id="loginButton" value="로그인" />
<input class="ui primary button" type="button" id="logoutButton" value="로그아웃" />
</div>
<br>
<!-- 전송하기 -->
<div>
<div class="description">
<span>보내는사람 아이디 :</span>
<div class="ui input">
<input type="text" id="senderInput" value="test01" />
</div>
</div>
<div class="description">
<span>받는사람 아이디 :</span>
<div class="ui input">
<input type="text" id="recepientInput" value="ALL" />
</div>
</div>
<div class="description">
<span>메시지 데이터 :</span>
<div class="ui input">
<input type="text" id="dataInput" value="안녕!"/>
</div>
</div>
<br>
<input class="ui primary button" type="button" id="sendButton" value="전송" />
<input class="ui primary button" type="button" id="clearButton" value="결과 지우기" />
</div>
<br>
<!-- 결과 표시 -->
<h4 class="ui horizontal divider header">메시지</h4>
<div class="ui segment" id="result">
</div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
<title>일대일 채팅</title>
<link href="./semantic.min.css" rel="stylesheet" >
<style>
* {
padding:0;
margin:0;
box-sizing:border-box;
}
html {
width:100%;
height:100%;
}
body {
width:100%;
height:100%;
color: #000;
background-color: #fff;
}
.container {
width:100%;
height:100%;
display:flex;
flex-flow:column wrap;
align-items:center;
justify-content:center;
}
#cardbox {
width:94%;
height:94%;
padding-left:0.4em;
padding-right:0.4em;
}
#iconImage {
display:inline;
}
#titleText {
font-size:1.4em;
font-weight:bold;
color:#777;
}
#contentsText {
color:#999;
}
#result {
height:14em;
overflow:auto;
}
.discussion {
list-style: none;
background: #ededed;
margin: 0;
padding: 0 0 50px 0;
}
.discussion li {
padding: 0.5em;
overflow: hidden;
display: flex;
}
.discussion .avatar {
width: 40px;
position: relative;
}
.discussion .avatar img {
display: block;
width: 100%;
}
.other .avatar:after {
content: "";
position: absolute;
top: 0;
right: 0;
width: 0;
height: 0;
border: 5px solid white;
border-left-color: transparent;
border-bottom-color: transparent;
}
.self {
justify-content: flex-end;
align-items: flex-end;
}
.self .messages {
order: 1;
border-bottom-right-radius: 0;
}
.self .avatar {
order: 2;
}
.self .avatar:after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 0;
border: 5px solid white;
border-right-color: transparent;
border-top-color: transparent;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
}
.messages {
background: white;
padding: 10px;
border-radius: 2px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.messages p {
font-size: 0.8em;
margin: 0 0 0.2em 0;
}
.messages time {
font-size: 0.7em;
color: #ccc;
}
</style>
<script src="jquery-3.1.1.min.js"></script>
<script src="socket.io.js"></script>
<script src="semantic.min.js"></script>
<script>
var host;
var port;
var socket;
// 문서 로딩 후 실행됨
$(function() {
// 연결하기 버튼 클릭 처리
$("#connectButton").bind('click', function(event) {
println('connectButton이 클릭되었습니다.');
host = $('#hostInput').val();
port = $('#portInput').val();
connectToServer();
});
// 전송 버튼 클릭 시 처리
$("#sendButton").bind('click', function(event) {
var sender = $('#senderInput').val();
var recepient = $('#recepientInput').val();
var data = $('#dataInput').val();
var output = {sender:sender, recepient:recepient, command:'chat', type:'text', data:data};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('message', output);
addToDiscussion('self', data);
});
// 로그인 버튼 클릭 시 처리
$("#loginButton").bind('click', function(event) {
var id = $('#idInput').val();
var password = $('#passwordInput').val();
var alias = $('#aliasInput').val();
var today = $('#todayInput').val();
var output = {id:id, password:password, alias:alias, today:today};
console.log('서버로 보낼 데이터 : ' + JSON.stringify(output));
if (socket == undefined) {
alert('서버에 연결되어 있지 않습니다. 먼저 서버에 연결하세요.');
return;
}
socket.emit('login', output);
});
// 결과 지우기 버튼 클릭 시 처리
$("#clearButton").bind('click', function(event) {
$("#result").html('');
});
});
// 서버에 연결하는 함수 정의
function connectToServer() {
var options = {'forceNew':true};
var url = 'http://' + host + ':' + port;
socket = io.connect(url, options);
socket.on('connect', function() {
println('웹소켓 서버에 연결되었습니다. : ' + url);
socket.on('message', function(message) {
console.log(JSON.stringify(message));
println('<p>수신 메시지 : ' + message.sender + ', ' + message.recepient + ', ' + message.command + ', ' + message.data + '</p>');
addToDiscussion('other', message.data);
});
socket.on('response', function(response) {
console.log(JSON.stringify(response));
println('응답 메시지를 받았습니다. : ' + response.command + ', ' + response.code + ', ' + response.message);
});
});
socket.on('disconnect', function() {
println('웹소켓 연결이 종료되었습니다.');
});
}
function println(data) {
console.log(data);
//$('#result').append('<p>' + data + '</p>');
}
function addToDiscussion(writer, msg) {
println("addToDiscussion 호출됨 : " + writer + ", " + msg);
var img = '/public/images/user1.png';
if (writer == 'other') {
img = '/public/images/user2.png';
}
var contents = "<li class='" + writer + "'>"
+ " <div class='avatar'>"
+ " <img src='" + img + "' />"
+ " </div>"
+ " <div class='messages'>"
+ " <p>" + msg + "</p>"
+ " <time datetime='2016-02-10 18:30'>18시 30분</time>"
+ " </div>"
+ "</li>";
println("추가할 HTML : " + contents);
$(".discussion").prepend(contents);
}
</script>
</head>
<body>
<div class="container">
<div id="cardbox" class="ui blue fluid card">
<div class="content">
<div class="left floated author">
<img id="iconImage" class="ui avatar image" src="./images/author.png">
</div>
<div>
<div id="titleText" class="header">일대일 채팅</div>
<div id="contentsText" class="description">
연결 및 로그인 후 메시지를 보내세요.
</div>
</div>
</div>
<br>
<!-- 연결하기 -->
<div>
<div class="ui input">
<input type="text" id="hostInput" value="localhost" />
</div>
<div class="ui input">
<input type="text" id="portInput" value="3000" />
</div>
<br><br>
<input class="ui primary button" type="button" id="connectButton" value="연결하기" />
</div>
<br>
<!-- 로그인/로그아웃 -->
<div>
<div class="ui input">
<input type="text" id="idInput" value="test01" />
</div>
<div class="ui input">
<input type="password" id="passwordInput" value="123456" />
</div>
<div class="ui input">
<input type="text" id="aliasInput" value="소녀시대" />
</div>
<div class="ui input">
<input type="text" id="todayInput" value="좋은 날!" />
</div>
<br><br>
<input class="ui primary button" type="button" id="loginButton" value="로그인" />
<input class="ui primary button" type="button" id="logoutButton" value="로그아웃" />
</div>
<br>
<!-- 전송하기 -->
<div>
<div class="description">
<span>보내는사람 아이디 :</span>
<div class="ui input">
<input type="text" id="senderInput" value="test01" />
</div>
</div>
<div class="description">
<span>받는사람 아이디 :</span>
<div class="ui input">
<input type="text" id="recepientInput" value="ALL" />
</div>
</div>
<div class="description">
<span>메시지 데이터 :</span>
<div class="ui input">
<input type="text" id="dataInput" value="안녕!"/>
</div>
</div>
<br>
<input class="ui primary button" type="button" id="sendButton" value="전송" />
<input class="ui primary button" type="button" id="clearButton" value="결과 지우기" />
</div>
<br>
<!-- 결과 표시 -->
<h4 class="ui horizontal divider header">메시지</h4>
<div class="ui segment" id="result">
<ol class="discussion">
<li class="other">
<div class="avatar">
<img src="/public/images/user2.png" />
</div>
<div class="messages">
<p>어디쯤이야? 다들 기다리고 있어.</p>
<time datetime="2016-02-10 18:10">18시 10분</time>
</div>
</li>
<li class="self">
<div class="avatar">
<img src="/public/images/user1.png" />
</div>
<div class="messages">
<p>차가 막히네. 조금 늦을 듯.</p>
<time datetime="2016-02-10 18:00">18시 00분</time>
</div>
</li>
<li class="other">
<div class="avatar">
<img src="/public/images/user2.png" />
</div>
<div class="messages">
<p>강남역에 있는 카페에 자리 잡았어.</p>
<time datetime="2016-02-10 17:40">17시 40분</time>
</div>
</li>
</ol>
</div>
</div>
</div>
</body>
</html>
채팅 메시지 표시를 위해 만든 CSS는 https://css-tricks.com/replicating-google-hangouts-chat/참조
메소드 이름 | 설명 |
---|---|
메소드 | 설명 |
---|---|
메소드 | 설명 |
---|---|
메소드 | 설명 |
---|---|
app.js
기본 코드가 들어 있는 메인 파일
/config/config.js
설정 정보가 들어 있는 파일 스키마 모듈이나 라우팅 모듈을 만들어 추가했다면 이 파일에 설정 정보를 추가
/database
데이터베이스의 스키마 모듈 파일들을 만들어 넣는 폴더
/routes
라우팅 함수들을 모듈 파일로 만들어 넣는 폴더
/views
뷰 템플릿 파일들을 만들어 넣는 폴더
attach(httpServer,options)
웹 서버 인스턴스가 socket.io를 처리한다.
listen(httpServer,options)
웹 서버 인스턴스가 socket.io를 처리한다.
on(event,callback)
이벤트 수신 형태로 메시지를 수신했을 때 처리할 콜백 함수를 등록한다. 콜백 함수의 파라미터로 수신한 객체가 전달된다.
emit(event,object)
이벤트 송신 형태로 메시지를 송신한다.
io.sockets.emit(event,object)
나를 포함한 모든 클라이언트에 전송
socket.broadcast.emit(event,object)
나를 제외한 모든 클라이언트에 전송
join(roomName)
방에 입장한다. 방이 없으면 방을 새로 만든다.
leave(roomName)
방에서 나온다.