Node.js로 채팅 구현하기

권규리·2024년 3월 7일

🧱Node.js

목록 보기
13/15

👩🏻‍💻 여는 말

채팅방이나 메신저 기능을 만들 때 적합한 socket.io를 이용하여 간단한 채팅 기능을 만들어 볼 것이다.

📁 디렉터리 구조는 server와 client 폴더를 각각 만들고 client에 react 설치, server에 npm init , npm i socket.io --save 설치하고 작업하면 된다.


(1) socket.io란? 🚀

socket.io는 웹 소켓 연결을 통해 클라이언트와 서버간에 실시간 양방향 통신을 가능하게하는 JavaScript 라이브러리이다.

게임에서 보면 귓속말 (특정한 사람에게 전달), 확성기 (한 사용자로부터 받은 데이터를 전체 사용자들에게 전달) 등 다양한 chat 기능이 있다.

Socket.io Server API는 클라이언트에서 발생하는 이벤트를 개발자가 임의로 설정할 수 있는 특징이 있기 때문에 이벤트를 등록하여, 위와 같은 다양한 기능을 구현할 수 있는 것이다.

1. socket.io 설치

npm i socket.io --save

2. socket.io 사용 전, server index.js 세팅

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);

3. socket.io 사용 방법

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)

(2) socket.io 작동방식

socket.io는 클라이언트와 서버간에 WebSocket 연결을 설정하여 작동한다.

서버는 들어오는 연결을 확인하고 클라이언트가 방출하는 이벤트를 처리한다.
클라이언트는 서버에 연결하고 이벤트를 방출하거나 이벤트를 확인할 수 있다. 클라이언트가 이벤트를 방출하면 서버는 이벤트를 수신하여 클라이언트로 응답을 다시 보내거나 동일한 네임 스페이스 또는 방에 있는 다른 클라이언트로 이벤트를 브로드 캐스트 할 수 있다.


(3) socket.io를 사용하여 채팅방 구현 💬

1. server > index.js

서버 측에서는 연결, 전송한 메세지 받기, 연결 종료 의 경우에 메세지를 받는 로직을 구현할 것이다.

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에 넣어주고 메세지를 보낸 사람의 이름이 다른 사람의 채팅방에 뜨도록 한다.

즉, 내 채팅방에서는 내이름 안 보이고 다른 사람 이름은 보인다는 말 !

💬 Gyul의 채팅방

💬 익명의 채팅방


마지막으로, socket 연결이 종료되었을 경우이다.
채팅방(브라우저 새로고침 및 탭 닫기)을 나가는 경우 연결이 종료되었음을 알린다.

//연결이 종료된 경우

socket.on('disconnect', function(){

  // 나가는 사람을 제외한 나머지 유저에게 메시지 전송
  socket.broadcast.emit('update', {type: 'disconnect', name: 'SERVER', message: socket.name+'님이 나가셨습니다.'});
})


'disconnect' 이벤트가 발생했을 때, 나가는 사람(= 나)을 제외한 채팅방의 모든 사람들에게 Gyul님이 나가셨습니다.라고 알린다.


(4) html 및 js 구성

1. client > index.html

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>

2. client > index.js

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)
  • server가 이를 수신(.on)하여 '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.typeconnect는 연결, 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의 내용만 나타나도록 한다.


profile
기록장 📝

0개의 댓글