채팅방이나 메신저 기능을 만들 때 적합한 socket.io를 이용하여 간단한 채팅 기능을 만들어 볼 것이다.
📁 디렉터리 구조는 server와 client 폴더를 각각 만들고
client에 react 설치,server에 npm init,npm i socket.io --save설치하고 작업하면 된다.
socket.io는 웹 소켓 연결을 통해 클라이언트와 서버간에 실시간 양방향 통신을 가능하게하는 JavaScript 라이브러리이다.
게임에서 보면 귓속말 (특정한 사람에게 전달), 확성기 (한 사용자로부터 받은 데이터를 전체 사용자들에게 전달) 등 다양한 chat 기능이 있다.
Socket.io Server API는 클라이언트에서 발생하는 이벤트를 개발자가 임의로 설정할 수 있는 특징이 있기 때문에 이벤트를 등록하여, 위와 같은 다양한 기능을 구현할 수 있는 것이다.
npm i socket.io --save
const express= require('express');
const socket= require('socket.io');
// Node.js 기본 내장 모듈 불러오기
const http= require('http');
// Node.js 기본 내장 모듈 불러오기
const fs= require('fs')
// express 객체 생성
const app= express();
// express http 서버 생성
const server= http.createServer(app)
// 생성된 서버를 socket.io에 바인딩
const io= socket(server);
Socket.io 메소드는 수신 (.on), 송신 (.emit)으로 나뉘어진다.
- socket.io 수신 (.on) 메소드
// ✨ 기본형 : 해당 이벤트가 발생할 경우 콜백함수 실행
socket.on('이벤트', function(message){
})
// 이벤트가 발생하면 모든 socket에게 콜백함수 실행
io.sockets.on('이벤트', function(message){
})
// connection 이벤트가 발생할 경우, 콜백함수 실행
socket.on('connection', function(socket){
console.log('새로운 유저가 입장했습니다.')
})
// disconnect 이벤트 발생할 경우, 콜백함수 실행
socket.on('disconnect', function(){
console.log('유저가 퇴장했습니다.');
})
- socket.io 송신 (.emit) 메소드
// ✨ 기본형 : 전송할 이벤트를 지정하고, 메세지 전송
socket.emit('전송할 이벤트', message)
// 나를 포함한 모든 socket에게 메세지 전송
io.sockets.emit('전송할 이벤트', message)
// 나를 제외한 모든 socket에게 메세지 전송
socket.broadcast.emit('전송할 이벤트', message)
// 특정 client에게만 전달
io.to(id).emit('전송할 이벤트', message)

socket.io는 클라이언트와 서버간에 WebSocket 연결을 설정하여 작동한다.
서버는 들어오는 연결을 확인하고 클라이언트가 방출하는 이벤트를 처리한다.
클라이언트는 서버에 연결하고 이벤트를 방출하거나 이벤트를 확인할 수 있다. 클라이언트가 이벤트를 방출하면 서버는 이벤트를 수신하여 클라이언트로 응답을 다시 보내거나 동일한 네임 스페이스 또는 방에 있는 다른 클라이언트로 이벤트를 브로드 캐스트 할 수 있다.
서버 측에서는 연결, 전송한 메세지 받기, 연결 종료 의 경우에 메세지를 받는 로직을 구현할 것이다.
socket.io를 사용하기 전, html로 채팅 화면을 보여줘야 하므로 서버에게 writeHead를 통해 이건 html 파일이야~ 하고 알려주자. 헤더를 작성했으면 write로 html 데이터를 보내주고, 모두 보냈으면 end로 완료되었음을 알린다.
/* Get 방식으로 / 경로에 접속하면 실행 됨 */
app.get('/', function(request, response) {
fs.readFile('./client/index.html', function(err, data){
if(err){
response.send('에러')
}else{
response.writeHead(200, {'Content-Type' : 'text/html'})
response.write(data);
response.end();
}
})
})
👩🏻💻 여기서 fs는 Node.js에서 기본적으로 제공하는 모듈로 파일을 처리한다. readFile() 함수는 지정된 파일을 읽어서 데이터를 가져온다.
다음으로 socket.io를 사용하는 부분이다.
첫 번째는 연결되었을 경우이다.
// 연결 경우
io.sockets.on('connection', function(socket){
socket.on('newUser', function(name){
// 1. 클라이언트로부터 받은 이름을 소켓에 저장해두기
socket.name = name;
// 2. 접속한 것을 알리기 위해 모든 소켓에게 이름 전송
io.sockets.emit('update', {type: 'connect', name: 'SERVER', message: name + '님이 접속하였습니다.'})
})
io.sockets은 접속한 모든 socket을, 콜백함수 안 socket은 접속한 해당 socket을 의미한다.
따라서 'connection' 이벤트가 발생하면, 모든 socket에게 새로운 socket이 접속했다는 메세지를 수신받는다. 이때, 메세지의 내용은 내부에 있는 socket.on('newUser',function(name){...}) 부분에서 알 수 있다.
새로운 socket이 접속했을 때, 'newUser' 이벤트가 발생하고 name을 받는 콜백함수를 실행하며 콜백함수에서는 두 가지 기능을 한다.
- 클라이언트로부터 받은 이름을 socket의 name에 저장
- socket name을 모든 socket에게 전송

이렇게 두 가지 기능을 하면, Gyul님이 접속하였습니다.라는 문구가 채팅방에 나타나게 된다.
다음으로, 사용자가 메세지를 전송할 때, 이를 채팅방에 뜨게 하는 부분이다.
// 전송한 메세지 받기
socket.on('message', function(data){
// 받은 데이터에 누가 보냈는지 이름을 추가
data.name = socket.name
// 보낸 사람을 제외한 나머지 유저에게 메시지 전송
socket.broadcast.emit('update', data);
})
'message' 이벤트가 발생했을 때, 메세지를 보낸 사람의 이름을 data.name에 넣어주고 메세지를 보낸 사람의 이름이 다른 사람의 채팅방에 뜨도록 한다.
즉, 내 채팅방에서는 내이름 안 보이고 다른 사람 이름은 보인다는 말 !


마지막으로, socket 연결이 종료되었을 경우이다.
채팅방(브라우저 새로고침 및 탭 닫기)을 나가는 경우 연결이 종료되었음을 알린다.
//연결이 종료된 경우
socket.on('disconnect', function(){
// 나가는 사람을 제외한 나머지 유저에게 메시지 전송
socket.broadcast.emit('update', {type: 'disconnect', name: 'SERVER', message: socket.name+'님이 나가셨습니다.'});
})

'disconnect' 이벤트가 발생했을 때, 나가는 사람(= 나)을 제외한 채팅방의 모든 사람들에게 Gyul님이 나가셨습니다.라고 알린다.
html 파일에는 css파일과 socket.io, index.js 세개의 script를 연결해주고 button 태그에 있는 onclick= "send()"는 index.js 파일에서 메세지를 전송하는 send함수이다.
<head>
<link rel="stylesheet" href="css/style.css">
<script src="/socket.io/socket.io.js"></script>
<script src="js/index.js"></script>
</head>
<body>
<div id="main">
<div id="chat">
<!-- 메세지 들어올 공간 -->
</div>
<div>
<input type="text" id="test" placeholder="메시지를 입력해주세요.">
<button onclick="send()">전송</button>
</div>
</div>
</body>
const socket = io()
/* 채팅방에 접속 했을 때 */
socket.on('connect', function() {
/* ''에 이름을 입력받고 */
const name = prompt('반갑습니다!', '')
/* 이름이 빈칸인 경우 */
if(!name) {
name = '익명'
}
/* 📌 새로운 유저가 왔다고 server에게 알림 -> server에서 on으로 수신 */
socket.emit('newUser', name)
})
사용자가 채팅방에 들어왔을 때 발생하는 순서이다.
socket.emit('newUser', name) 이 부분을 통해 발생시킬 이벤트 와 채팅방에 접속한 사람의 name을 server에 송신(.emit) 'newUser' 이벤트와 콜백함수를 실행 'oo님이 입장하셨습니다.' 메세지가 나타남즉, 대화로 보면 이렇다.
🙋🏻♀️ client : server야! 새로운 사람이 들어왔어. 채팅방에 알리자~
🙋🏻♂️ server : 알았어~ newUser 이벤트를 실행해서 메세지를 띄워줄게
/* 서버로부터 데이터 받은 경우 */
socket.on('update', function(data) {
const chat = document.getElementById('chat')
const message = document.createElement('div')
const node = document.createTextNode(`${data.name}: ${data.message}`)
const className = ''
// 타입에 따라 적용할 클래스를 다르게 지정
switch(data.type) {
case 'message':
className = 'other'
break
case 'connect':
className = 'connect'
break
case 'disconnect':
className = 'disconnect'
break
}
message.classList.add(className)
message.appendChild(node)
chat.appendChild(message)
})
server로부터 data를 받은 후, 연결, 전송한 메세지 받기, 연결 종료 의 경우마다 스타일을 다르게 분기처리 해준다. data.type이 connect는 연결, message는 전송한 메세지 받기, disconnect는 연결 종료일 때를 의미한다.
/* 메시지 전송 함수 */
function send() {
// 입력되어있는 데이터 가져오기
const message = document.getElementById('test').value
// 가져왔으니 데이터 빈칸으로 변경
document.getElementById('test').value = ''
// 내가 전송할 메시지 클라이언트에게 표시
const chat = document.getElementById('chat')
const msg = document.createElement('div')
const node = document.createTextNode(message)
msg.classList.add('me')
msg.appendChild(node)
chat.appendChild(msg)
// 서버로 message 이벤트 전달 + 데이터와 함께
socket.emit('message', {type: 'message', message: message})
}
메세지를 전송할 때는 input의 text value를 가져와 전송하고, 동시에 input을 빈칸으로 만든다.
또한, 내가 전송하는 메세지를 내 채팅방에서 볼 때는 message : message로 이름 없이 message의 내용만 나타나도록 한다.