본문 바로가기
Today I Learned/SeSAC 웹 2기

Node.js Socket.io 라이브러리(EJS) 💬💬 | 15주차(2)

by suyeonnie 2025. 2. 8.

 

📂 오늘 배운 내용

socket.io

  • 사용자 지정 이벤트
  • Room 개념과 함수
  • 시작하기 (서버, 클라이언트)

JS문법


Socket.io 라이브러리

  • websocket 기반으로 만들어진 라이브러리라서 cdn 가져올 예정
  • websocket과의 차이점
    • 사용자 지정 이벤트가 있다는 거!
    • 클라이언트, 서버 메서드 차이가 없음
    • websocket은 클라이언트에서 이벤트 감지하려면 addEventListenr, 서버는 on
    • 웹 소켓은 데이터 전달할 때 JSON 데이터 변환이 필요했지만, socket.io는 그런 처리가 필요없음 👍
  • 특징 1) 양방향 2) 자동 재연결

 

기본 이벤트 : connection, disconnect, disconnecting, error

  • connection : 클라이언트-서버 연결 시 발생. 기본 메서드라서 emit 없이 작성 가능
  • disconnecting : 클라이언트가 연결을 해제하려는 경우 발생

 

사용자 지정 이벤트

  • emit : 이벤트 만들고 데이터를 보냄
  • on : 이벤트 받는 쪽

내가 만들고 싶은 이름('hello')으로 이벤트를 만들 수 있음! 이벤트를 만들고 보내는 건 서버/클라이언트 상관없다.

위 이미지에서는 클라이언트가 emit()으로 메시지를 보낼 때, 마지막 인자로 콜백함수를 전달하고 있음 (서버에서 실행하고 응답 가능)

 

 

Socket.IO의 Room

클라이언트

  • socket : 하나의 클라이언트
  • io : 여러개의 socket = 여러개의 클라이언트
  • broadcast: 나를 제외한 클라이언트

Room 개념

  • 기본적으로 모든 클라이언트는 `socket.id`를 가진 고유한 연결을 갖는다
  • `socket.join(room)` 호출 시 해당 클라이언트가 특정 방에 속하게 됨
  • 방에 속한 클라이언트들끼리 `io.to(room).emit()`을 이용해 같은 방에 있는 사람들에게만 메시지 전송 가능

 

함수

// 1. 클라이언트 특정 방에 추가
socket.join('roomA')
console.log(socket.rooms); // Set {'소켓ID', 'roomA'}

// 2. 클라이언트 특정 방에서 제거
socket.leave('roomA')

// 3. 특정 방에 있는 모든 클라이언트에게 이벤트, 데이터 발송
io.to('roomA').emit('message','hello, RoomA!')

// 4. 특정 방에 있는 모든 클라이언트에게 이벤트, 데이터 발송 (송신자 클라이언트 제외)
socket.to('roomA').emit('message', 'hello, RoomA!')
socket.broadcast.to('roomA').emit('message', 'A new user has joined the room.')

// 5. 송신자 제외 서버에 연결된 모든 클라이언트에게 이벤트, 데이터 발송
sockt.broadcast.emit('message', 'A new user has joined the room.')

// 6. 송신자 포함 모든 클라이언트에게 이벤트, 데이터 발송
io.socket.emit('message', 'hello, RoomA!')

// 7.특정 방에 속한 클라이언트들의 정보 확인
const roomInfo = io.sockets.adapter.rooms.get(room)
console.log(roomInfo) //방에 속한 클라이언트 ID 정보 출력

 

  • 서버에서 활용
  • `get` : 이 방에 누가 있나~
  • `io.sockets.adpater.rooms` : 현재 존재하는 모든 방과 방에 속한 소켓 정보 관리하는 객체
    • roomInfo에는 해당 방에 연결된 클라이언트 소켓 ID들이 저장된 Set 객체(중복이 없는 리스트)가 들어간다.

예제

io.on('connection', (socket) => {
	console.log(`클라이언트 연결됨: ${socket.id}`);
    
    // 특정 방(room1)에 클라이언트 입장
    socket.join('room1');
   	console.log(`${socket.id}가 room1에 입장`);
    
    // 현재 room1 방에 속한 클라이언트 정보 가져오기
    const roomInfo = io.sockets.rooms.get('room1');
    console.log('현재 room1에 속한 클라이언트:', roomInfo);
});

 

예제 코드의 실행 결과 예시 :

클라이언트 연결됨: WtGHJdXcNk0X7N6LAAAA
WtGHJdXcNk0X7N6LAAAA가 room1에 입장
현재 room1에 속한 클라이언트: Set { 'WtGHJdXcNk0X7N6LAAAA' }

 

 

시작하기

서버

설치

npm i socket.io

 

예제

const express = require('express');
const http = require('http'); // node.js 기본 http모듈
const app = express();
const server = http.createServer(app); //HTTP 서버 생성 (Express앱 기반)
const io = require('socket.io')(server); //socket.io 서버 생성(HTTP서버와 연결)
const PORT = 8080;

/* middleware */
// ...

/* api 라우팅 */
// ...

/* socket */
io.on('connection', (socket)=>{
	//...
})

/* 서버 실행 */
server.listen(PORT, () => {
  console.log(`http://localhost:${PORT}`);
});

 

socket.io의 소켓은 HTTP모듈로 생성된 서버에서만 동작한다.

`const io = require('socket.io')(server);` : HTTP 기반으로 만들어진 서버를 인자로 전달해서 io 소켓 만들어주기 (소켓 연결 설정)

 

클라이언트

CDN

<!-- socket.io CDN -->
<script src="/socket.io/socket.io.js"></script>

 

 

`io()` 함수 : 소켓 생성하고, 서버와의 연결을 설정

<script>
	const socket = io();
    
    //....
    
</script>

 

 


 

예제 on과 emit 사용해보기

 

좌) 클라이언트 우) 서버

1~2 클라이언트 > 서버

3 서버 > 클라이언트

 

서버 실행 결과 예시

 


 

 

실시간 채팅방을 구현하면서 사용했던 JS문법을 정리하고자한다.

 

먼저 닉네임 중복 검사 기능을 구현하기 위해 소켓 연결 전 클라이언트에서 받은 사용자 닉네임 저장 정보를 담을 수 있는 빈 객체를 만들었다. 여기에 저장된 정보와 클라이언트의 Input창에 입력된 이름을 비교하기 위해 객체의 메서드를 사용했다.

 

JS Object.keys , Object.values

Object.keys(obj) : 객체의 속성(key)을 배열로 반환한다.

const object1 = {
  a: 'somestring',
  b: 42,
  c: false,
};

console.log(Object.keys(object1));
// Expected output: Array ["a", "b", "c"]

 

 

Object.values(obj) : 객체의 값들을 배열로 반환한다.

const object1 = {
  a: 'somestring',
  b: 42,
  c: false,
};

console.log(Object.values(object1));
// Expected output: Array ["somestring", 42, false]

 

 

더하기 할당 연산자(+=) 활용

 

더하기 할당 연산자를 활용해서 닉네임 목록 갱신 기능을 구현했다. 더하기 할당은 오른쪽 피연산자의 값을 변수에 더한 결과를 다시 변수에 할당한다. 두 피연산자의 타입이 연산자의 동작을 결정하며, 덧셈 또는 문자열 연결이 가능하다.

 

더하기 할당 (+=) 기본 예제

let a = 0;
let b = '소켓';

console.log((a += 3)); // 3

console.log((b += ' 어렵다!!'));// "소켓 어렵다!!"

 

활용 : 닉네임 목록 갱신 기능

[app.js]

// 닉네임2. 닉네임 중복 검사, 입장 실패/성공에 따른 처리
  socket.on('checkNick', (nickname) => {
    // nickname = nickInput.value
    // nickInfo[socket.id] = nickname;
    console.log(nickInfo); //{ LzSvKL19UkErW1c8AAAD: '돌고래' }
    console.log(Object.values(nickInfo).indexOf(nickname));

    // 닉네임 중복 검사
    if (Object.values(nickInfo).indexOf(nickname) > -1) {
      socket.emit('error', '이미 존재하는 닉네임입니다.');
    } else {
      // 현재 클라이언트 닉네임 정보 nickInfo에 저장
      nickInfo[socket.id] = nickname;
    
      // ...
      
      // 입장 성공 시 나를 포함한 전체 client에게 전체 닉네임 정보 전달 (사용자 목록 갱신)
      io.emit('updateNicks', nickInfo);
    }
  });

 

[talk-dm.ejs]

socket.on('updateNicks', (nickInfo) => {
    let option = `<option value='all'>전체</option>`;
    console.log('nickInfo', nickInfo);
    console.log('option', option); //option 태그 그 자체 (문자열)
    console.log('option type', typeof option); //string

    for (let key in nickInfo) {
      // console.log('key', key);
      // console.log('value', nickInfo[key]);

      option += `<option value='${key}'>${nickInfo[key]}</option>`;
    }

    select.innerHTML = option; //닉네임 목록에 추가해주기
  });

 

여기서는 option이 문자열이므로 콘솔에 사용자의 닉네임 값이 모두 출력된다. 이 닉네임 값의 목록에 하나의 문자열을 또 추가해서 연결해기 위해 더하기 할당 연산자를 사용했다.

 

즉, 문자열을 누적하여 <option> 태그를 동적으로 생성하고 있다.

 


🌐  참고 / 읽을거리

서울시 청년취업사관학교(SeSAC) x 코딩온 웹 개발자 풀스택 과정

32.socket.io

- client : 기본 개념 (1) on과 emit 사용해보기 (2) 채팅 만들기

- client2 : 실습1 buttons(hello, study, bye)

- rooms : 실습2 방이 있는 채팅

 

32.socket.io-practice [EJS 채팅방 만들기]

- talk : 입장 공지, 기본 채팅 기능 (DM, 닉네임 표시 기능 x)

- talk-dm :  닉네임 입력 후 채팅방 입장(DM, 닉네임 표기 기능 O)

 

 

Socket.io Adapter

[zoom클론코딩] 방 이름과 유저 수를 띄워보자!