저장을 습관화

NestJS - socket.emit, socket.on 연습 기록 본문

공부/node.js

NestJS - socket.emit, socket.on 연습 기록

ctrs 2023. 10. 19. 00:32

/views/index.hbs

{{! 템플릿 엔진 }}
{{! 서버 사이드에서 클라이언트 사이드(브라우저)로 보낼때 html로 렌더링 될 내용 }}
{{! https://docs.nestjs.com/techniques/mvc }}

<html>
  <head>
    <meta charset='utf-8' />
    <title>{{data.title}}</title>
    <link href='css/styles.css' rel='stylesheet' />
    {{! public부터 시작하는 절대 경로로 지정해야지, 상대 경로로는 적용되지 않는다. }}
    {{! main.ts에서 선언한 내용에 따라 진행되기 때문이다. }}
  </head>

  <body>
    <h1>Hello World!</h1>
    <div id='hello_stranger'></div>
    <div id='chatting_box'></div>
    <form id='chat_form'>
      <input placeholder='Chat...' />
      <button type='submit'>Chat</button>
    </form>

    {{! babel - 최신 자바스크립트 문법을 브라우저에서 사용가능하게 지원해줌 }}
    <script src='https://unpkg.com/@babel/standalone/babel.min.js'></script>

    {{! polyfill, 자바스크립트를 지원하지 않는 브라우저에서도 자바스크립트 메서드 등을 사용할 수 있게 해줌 }}
    <script
      src='https://polyfill.io/v3/polyfill.min.js?features=default%2Ces2015%2Ces2016%2Ces2017%2Ces2018%2Ces2019'
    ></script>

    {{! socket.io - 웹 클라이언트와 서버 간 실시간 양방향 통신을 지원해줌}}
    <script
      src='https://cdn.socket.io/3.1.3/socket.io.min.js'
      integrity='sha384-cPwlPLvBTa3sKAgddT6krw0cJat7egBga3DJepJyrLl4Q9/5WLra3rrnMcyTyOnh'
      crossorigin='anonymous'
    ></script>

    <script src='js/scripts.js'></script>
    {{! public부터 시작하는 절대 경로로 지정해야지, 상대 경로로는 적용되지 않는다. }}
    {{! main.ts에서 선언한 내용에 따라 진행되기 때문이다. }}
  </body>
</html>

 

/public/js/scripts.js

const socket = io('/'); // socket.io의 메소드를 사용하기 위한 준비
const getElementById = (id) => {
  return document.getElementById(id) || null;
};
// DOM에서 특정 id를 가진 HTML 요소를 찾는다.
// id를 가진 HTML 요소가 존재하지 않는다면(undefined의 경우) null를 반환한다.

// get DOM element
const helloStrangerElement = getElementById('hello_stranger');
const chattingBoxElement = getElementById('chatting_box');
const formElement = getElementById('chat_form');

const helloUser = () => {
  const username = prompt('What is your name?');
  socket.emit('new_user', username, (data) => {
    console.log(data);
  });
  // emit은 '보내는 행위'
  // 서버에서 클라이언트로, 클라이언트에서 서버로 보낼 때도 emit
  // prompt를 통해 입력받은 내용을 변수 username에 할당하고,
  // 이벤트 'new_user'를 찾아간다. 여기서는 /src/chats/chats.gateway.ts이다.
  console.log(username);
  socket.on('hello_user', (data) => {
    console.log(data);
  });
  // on은 '받는 행위
  // 서버가 보낸 데이터를 클라이언트가 받거나,
  // 클라이언트가 보낸 데이터를 서버가 받는 경우가 on
  // chats.gateway.ts의 'handleNewUser'메소드에서 socket.emit을 통해 보낸 데이터를 받아 이벤트를 수행한다.
};

const init = () => {
  helloUser();
};

init();

emit은 '보내는 행위'

서버에서 클라이언트로, 클라이언트에서 서버로 보낼 때 모두 emit

 

on은 '받는 행위'

서버가 보낸 데이터를 클라이언트가 받거나,

클라이언트가 보낸 데이터를 서버가 받는 경우 모두 on

 

 

/src/chats/chats.gateway.ts

import {
  SubscribeMessage,
  WebSocketGateway,
  MessageBody,
  ConnectedSocket,
} from '@nestjs/websockets';
import { Socket } from 'socket.io';

@WebSocketGateway()
export class ChatsGateway {
  @SubscribeMessage('new_user')
  // /public/script.js의 함수 helloUser에서 socket.emit 메소드로 보낸 데이터를 받는다.
  handleNewUser(
    @MessageBody() username: string,
    @ConnectedSocket() socket: Socket,
  ) {
    console.log(socket.id); // 소켓의 아이디
    // 모든 소켓은 개별적인 아이디를 가지며, 연결이 끊어질때마다 이 아이디는 사라진다. 
    // 새로 연결하면 새로운 아이디를 받는다.
    console.log(username);
    socket.emit('hello_user', 'Hello ' + username);
    // 다시 emit으로 'hello_user'이벤트를 향해 보내거나
    return 'this is return for emit!';
    // return을 통해 값을 반환할 수도 있다.
  }
}

 

양방향 통신 연결로 발생하는 모든 소켓은 개별적인 아이디를 가진다.

연결이 끊어지면 이 아이디는 사라지며,

같은 서버와 같은 클라이언트더라도 새롭게 연결하면 새로운 아이디를 가지는 소켓을 받는다.

 

 

실행 화면

- scripts.js

const helloUser = () => {
  const username = prompt('What is your name?');
  socket.emit('new_user', username, (data) => {
    console.log(data);
  });
  console.log(username);
  socket.on('hello_user', (data) => {
    console.log(data);
  });
};

함수 helloUser에서 발생시킨 프롬프트로 사용자의 이름을 묻고,

입력받은 내용을 socket.emit('new_user')을 'new_user'를 향해 보낸다.

 

- chats.gateway.ts

@WebSocketGateway()
export class ChatsGateway {
  @SubscribeMessage('new_user')
  handleNewUser(
    @MessageBody() username: string,
    @ConnectedSocket() socket: Socket,
  ) {
    console.log(socket.id); 
    console.log(username);
    socket.emit('hello_user', 'Hello ' + username);
    return 'this is return for emit!';
  }
}

console.log(socket.id)와 console.log(username)의 결과가 출력되고

socket.emit('new_user', username, (data) => {
    console.log(data);
  });
  console.log(username); // <- 이게 먼저 실행되었다.
  socket.on('hello_user', (data) => {
    console.log(data);
  });

console.log(username)의 결과인 ctrs_socket_test가 출력 되었고

 

socket.emit('hello_user', 'Hello ' + username의 결과로

scripts.js의 socket.on('hello_user')이벤트를 실행시켜

socket.on('hello_user', (data) => {
    console.log(data);

Hello ctrs_socket_test가 출력되었다.

 

socket.emit('hello_user', 'Hello ' + username);
    return 'this is return for emit!';

마지막으로 

return 'this is return for emit!'의 결과로 scripts.js의 socket.emit 메소드가 종료되며

this is return for emit!가 출력었다.

 

함수나 컨트롤러-서비스의 관계에서처럼

promise가 없다면 실행한 순서대로가 아닌 먼저 처리되는 순서대로 비동기처리된다.