본문 바로가기

Node.js

Socket io, node js 를 이용한 채팅 사이트 만들기 예시


chat.js - 서버측 js파일. nodejs로 실행시킨다.

enter.ejs - 유저가 접속할 때 닉네임을 설정하도록 하는 페이지

index.ejs - 유저가 닉네임을 설정한 후 제공받게 되는 채팅 페이지


chat.js 설명

- '/'경로에서 enter.ejs페이지를 제공하고닉네임을 입력받음.

  입력받으면 '/chat'경로로 POST요청이 들어오고 index.ejs를 제공함.


로컬 변수

whoIsTyping - 현재 타이핑하는 유저들의 배열

whoIsOn - 현재 접속중인 유저들의 배열


이벤트 리스닝

서버기준

.emit은 서버가 이벤트를 발생시키는 것이고

.on은 서버가 클라이언트의 이벤트를 핸들하는 것임.


 - 목록

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

socket.emit('selfData', {nickName:nickName}); - 유저에게 자신의 닉네임이 담긴 정보를 제공

io.emit('login', whoIsOn); - 모든 유저에게 현재접속자들의 배열을 인자로하는 이벤트 발생

socket.on('setNickName', function(nickName){ ... }) - 누군가 닉네임을 바꿨을 때

io.emit('setNickName', {past:pastNickName, current:nickName, whoIsOn:whoIsOn}); - 누군가 닉네임을 바꿨을 때 그 정보를 전송

socket.on('say', function(msg){ ... })누군가 채팅을 했을 때

socket.broadcast.emit('chat message', nickName+'  :  '+msg); - 누군가 채팅을 했을 때 그것을 화자 외에게 전달

socket.emit('mySaying', 'ME  :  '+msg);  - 화자에게 내용 다시 보냄

socket.on('typing', function(){ .. }) - 누군가 타이핑을 시작했음을 의미

io.emit('typing', whoIsTyping); - 누군가 타이핑 중임을 알림

socket.on('quitTyping', function(){ ... }) - 누군가 타이핑을 그만 두었음을 의미

io.emit('endTyping'); - 아무도 타이핑하는 사람이 없을 때 

socket.on('disconnect', function(){ ... }) - 누군가 접속이 끊어졌을 때


chat.js

var app = require('express')();
var http = require('http').createServer(app);
var io = require('socket.io')(http);
var ejs=require('ejs');
var bodyParser=require('body-parser');
var nowNickName="";

app.use(bodyParser.urlencoded({
    extended: true
}));

app.engine('html', ejs.renderFile);
app.get('/', function(req, res){
	res.render(__dirname+'/enter.ejs');
  
	console.log('in / GET');
});

app.get('/chat', function(req, res){
	res.send('plz connect through "/"');
});

app.post('/chat', function(req,res){
	console.log('in /chat POST');

	nowNickName=req.body.nickName;
	console.log('new user : '+nowNickName);
	
	res.render(__dirname+'/index.ejs');
	
});

var whoIsTyping=[];
var whoIsOn=[];

io.on('connection', function(socket){
	
	var nickName=nowNickName||socket.id;
	whoIsOn.push(nickName);
	socket.emit('selfData', {nickName:nickName});
	
	//someone who has this nickName has logged in
	//original :
	//io.emit('login', nickName);
	io.emit('login', whoIsOn);
	//basically after login, execute refreshUsers	
	//io.emit('refreshUsers', whoIsOn);
	
	if(whoIsTyping.length!=0){
		io.emit('typing', whoIsTyping);
	}
	
	socket.on('setNickName', function(_nickName){
		var pastNickName=nickName;	//past nickname
		nickName=_nickName;
		if(whoIsTyping.indexOf(pastNickName)!=-1){
			//if he was typing
			console.log('setNickName debug1');
			whoIsTyping.splice(whoIsTyping.indexOf(pastNickName),1,nickName);
			io.emit('typing', whoIsTyping);
		}
		
		if(whoIsOn.indexOf(pastNickName)!=-1){
			console.log('setNickName debug2');
			whoIsOn.splice(whoIsOn.indexOf(pastNickName), 1, nickName);
		}
		io.emit('setNickName', {past:pastNickName, current:nickName, whoIsOn:whoIsOn});
		console.log(socket.id+'  to  '+nickName);
	});
	
	socket.on('say', function(msg){
		console.log('message: ' + msg);
		//chat message to the others
		//mySaying to the speaker
		socket.broadcast.emit('chat message', nickName+'  :  '+msg);
		socket.emit('mySaying', 'ME  :  '+msg);	
  	});
	
	
	socket.on('typing', function(){
		if(!whoIsTyping.includes(nickName)){
			whoIsTyping.push(nickName);
			console.log('who is typing now');
			console.log(whoIsTyping);
			io.emit('typing', whoIsTyping);	
		}
	});
	
	socket.on('quitTyping', function(){
		if(whoIsTyping.length==0){
			//if it's empty
			console.log('emit endTyping');
			io.emit('endTyping');
		}
		else{
			//if someone else is typing
			var index=whoIsTyping.indexOf(nickName);
			console.log(index);
			if(index!=-1){
				whoIsTyping.splice(index, 1);
				if(whoIsTyping.length==0){
					
					console.log('emit endTyping');
					io.emit('endTyping');
				}
				
				else{
					io.emit('typing', whoIsTyping);
					console.log('emit quitTyping');
					console.log('whoIsTyping after quit');
					console.log(whoIsTyping);
				}
				
			}
			
			
		}
	});
	
	
	//disconnect is in socket
 	socket.on('disconnect', function(){
    	console.log(nickName+' : DISCONNECTED');
		
		whoIsOn.splice(whoIsOn.indexOf(nickName), 1);
		io.emit('logout', {nickNameArr:whoIsOn, disconnected:nickName});
		
		if(whoIsTyping.length==0){
			//if it's empty
			io.emit('endTyping');
		}
		else{
			//if someone was typing
			var index=whoIsTyping.indexOf(nickName);
			if(index!=-1){
				whoIsTyping.splice(index, 1);
				
				//if no one is typing now
				if(whoIsTyping.length==0){
					io.emit('endTyping');
				}
				
				//if someone else is still typing
				else{
					io.emit('typing', whoIsTyping);
					console.log('emit popTyping');
					console.log(whoIsTyping);		
				}
			
			}
			
			
		}
  	});
	
});

http.listen(80, function(){
  console.log('listening on *:80');
});



enter.ejs (enter.html 이나 마찬가지)

<html>
	<head>
		<title>
		Entering SuChat
		</title>
		<style>
			html, body{width:99%; height:99%}
			form input { border: 0; padding: 10px; width: 40%; margin:auto; margin-right: .5%;
				font-size:3rem;
			background-color:gray; opacity:false}
			form button { width: 12%; background: rgb(130, 224, 255); border: none; padding: 10px; 
				font-size:3rem;}
		</style>
		
		<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.3.1.min.js"></script>
	
	</head>
	
	<body>
		<form action="chat" method="POST">
			<input id='nickName' name='nickName'>
			<button>
				ENTER
			</button>
		</form>
	</body>
</html>


index.ejs (index.html이나 마찬가지)

<!doctype html>
<html>
  <head>
    <title>chat_practice</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      html, body { height:99%; font: 1rem Helvetica, Arial; font-weight:50}
		form { background: #000; padding: 3px; width: 100%; position: relative;}
		#nickNameForm{
			
		}
		
		#typeForm{
			bottom:0;
		}
		#whoIsInBox{
			overflow-y:scroll;
		}
      form input { border: 0; padding: 10px; width: 90%; margin-bottom:1%;
		  margin-right: .5%; font-size:2rem}
      form button { background: rgb(130, 224, 255);
		  border: none; padding: 10px; font-size:2rem}
      #messages { list-style-type: none; margin: 0; padding: 0; height:100%; overflow-y:scroll; position:relative;}
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(even) { background: #eee; }
		ul{
			font-size:1.5rem
		}
    </style>
	  <script src="/socket.io/socket.io.js"></script>
	  <script src="https://code.jquery.com/jquery-1.11.1.js"></script>
		<script>
		  	
			//when the document is ready.
  			$(function () {
				//about DOM
				$('#m').focus();
				
				//socket io
				var socket = io();
				var typingNotice=' is typing..';
				var fontColor='black';
				var nickName='';
				var whoIsTyping=[];
				
				$('#typeForm').submit(function(){
					//submit only if it's not empty
					if($('#m').val()!=""){
						socket.emit('say', $('#m').val());
						//say event means someone transmitted chat
						$('#m').val('');
						socket.emit('quitTyping')
					}
					return false;
				});
				
				$('#nickNameForm').submit(function(){
					nickName=$('#nickName').val();
					$('#nickName').attr('placeholder', 'NickName : '+nickName);
					socket.emit('setNickName', nickName)
					$('#nickName').val("");	
					$('#m').focus();
					return false;
				})
				
				socket.on('selfData', function(obj){
					console.log('getting initial data from server');
					nickName=obj.nickName;
					$('#nickName').attr('placeholder', 'NickName : '+nickName);
					//set #nickNameForm placeholder
					
				});
				
				socket.on('setNickName', function(obj){
					var past=obj.past;
					var current=obj.current;
					var whoIsOn=obj.whoIsOn;
					
					var msg=`====== ${past} changed nickname to ${current} ======`;
					$('#messages').append($('<li>').text(msg));
					
					editUsers(whoIsOn);
					
				})
				
				socket.on('chat message', function(msg){
				  $('#messages').append($('<li>').text(msg));
				});
				
				socket.on('login', function(nickNameArr){
					var newbie=nickNameArr[nickNameArr.length-1];
					editUsers(nickNameArr);
					$('#messages').append($('<li>').text('======'+newbie+"  LOG IN======"));
				})
				socket.on('typing', function(nickNameArr){
					var tempMsg="";
					whoIsTyping=nickNameArr;
					for(person in nickNameArr){
						tempMsg+=nickNameArr[person]+', '
					}
					tempMsg=tempMsg.substring(0, tempMsg.length-2);
					$('#m').attr('placeholder', tempMsg+typingNotice)
				});
				
				socket.on('mySaying', function(msg){
					
					$('#messages').append($('<li>').text(msg));
				});
			
				socket.on('endTyping', function(){
					console.log('endTyping');
					whoIsTyping=[];
					$('#m').attr('placeholder', "");
				})
				
				socket.on('logout', function(received){
					var nickNameArr=received.whoIsOn;
					var disconnected=received.disconnected;
					$('#messages').append($('<li>').text(`====== ${disconnected} has disconnected ======`));
					editUsers(nickNameArr);
				})
				
				function editUsers(nickNameArr){
					
					$('#whoIsInBox ul').children().each((index, item)=>{
						$(item).remove();
					});
					for(person in nickNameArr){
						$('#whoIsInBox ul').append($('<li>').text(nickNameArr[person]));
					}
				}
				
				
				$('#m').keyup(function(event){
					
					if($('#m').val()!="" && !whoIsTyping.includes(nickName)){
						socket.emit('typing');
						console.log('emit typing');
					}
					
					else if($('#m').val()=="" && whoIsTyping.includes(nickName)){

						socket.emit('quitTyping');
						console.log('emit quitTyping');	
						
					}
				});
				
				
		  	});
</script>
      
  </head>
  <body>
	  <div style="height:70%">
		<ul id="messages">
	  		<li id='chatTitle' style='font-size:4rem; font-weight:bold; color:powderblue'>Socket io CHAT</li>
	  	</ul>	  
	  </div>
    
	  <div id='whoIsInBox'>
		  <h1>
			  Who Is In Now?
		  </h1>
		  <ul id='whoIsIn'>
			  <li>Test In</li>
		  </ul>
		  
	  </div>
	  <form action="" id='nickNameForm'>
		  <input id="nickName" placeholder="Plz input a nickname first!" autocomplete="off" /><button>Send</button>
	  </form>
    <form action="" id='typeForm'>
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
  </body>
</html>