개발 공부한 것들/SeSSAC 웹 풀스텍 과정 회고록

[SeSSAC] 웹 풀스택 영등포 5기 과정_socket

geograp 2023. 11. 17. 14:22

 

이제 프로젝트가 끝남과 동시에 또 수업을 시작하였습니다.

숨 돌릴 틈이 진짜 없는 느낌 ㅠㅡㅠ,,,

 

이번 시간에는 소켓에 대해 배웠습니다.

 


0. 소켓(socket)이란?

 

웹소켓은 HTTP와 달리 양방향 실시간 데이터 소통을 하기 위해 생겨난 기술입니다.

TCP/IP의 위에서 동작하는 HTTP와 달리 조금 더 밑에서 동작한다고 합니다.

 

소켓은 떨어져 있는 두 호스트를 연결해 주는 도구로써 인터페이스 역할을 한다고 합니다.

소켓을 통해 데이터를 주고 받는 것은 양방향 데이터 통신 채널을 만드는 것 같습니다.

 

- 소켓 통신 흐름도

소켓 통신의 흐름

 

 

하기의 블로그에 꽤나 쉽고 자세하게 나와있어서 읽어보시는 것을 추천드립니다.

https://helloworld-88.tistory.com/215

 

[기본] 소켓(SOCKET)통신 이란?

소켓통신 소켓(SOCKET)이란?소켓(Socket)은 프로세스가 드넓은 네트워크 세계로 데이터를 내보내거나 혹은 그 세계로부터 데이터를 받기 위한 실제적인 창구 역할을 한다. 그러므로 프로세스가 데

helloworld-88.tistory.com

 


 

1. web socket

 

웹 소켓 (줄여서 ws라고 부르는 듯) 은 앞서 알아본 소켓과는 다른 개념입니다.

저는 그거나 그거나인 줄 알았는데 아니더라고요,,,

 

소켓은 앞서 알아보았듯이 어떠한 두 개의 호스트를 연결하고 데이터를 주고받는

네트워크 프로그래밍의 일반적인 용어입니다.

 

반면 웹 소켓HTML5에서 도입된 웹의 표준 기술입니다.

웹 브라우저와 웹 서버가 실시간 양방향 통신 (핸드셰이크)를 통신 위한 특별한 프로토콜이라고 합니다.

빠르게 작동하고, 통신할 때 아주 적은 데이터를 이용하는 것이 장점입니다.

 

 

- 웹 소캣 이벤트

웹 소켓의 이벤트가 4가지 있습니다.

 

1-1. OPEN

socket.addEventListener('open', function (event) {
  console.log('웹 소켓이 열렸습니다.');
});

 

클라이언트가 웹 소켓 서버에 성공적으로 연결되어 열렸을 때 발생하는 이벤트입니다.

 

 

1-2. MESSAGE

socket.addEventListener('message', function (event) {
  console.log('서버에서 메시지 수신:', event.data);
});

 

서버에서 클라이언트로 메시지를 보낼 때 발생하는 이벤트입니다,

 

 

1-3. ERROR

socket.addEventListener('error', function (event) {
  console.error('웹 소켓 오류 발생:', event);
});

 

웹 소켓 연결 중 오류가 있을 대 발생하는 이벤트입니다.

 

 

1-4. CLOSE

socket.addEventListener('close', function (event) {
  console.log('웹 소켓이 닫혔습니다.');
});

 

웹 소켓 연결이 종료되었을 때 발생하는 이벤트입니다.

 

 

 

반면에, 웹 소켓 서버에서도 웹 소켓과 연결해서 이벤트를 처리해야 하는데

서버는 아무런 준비 없이 웹 소켓을 실행할 수 없습니다.

 

그래서 WS라는 모듈을 설치해서 클라이언트와 웹 소켓으로 주고받을 수 있습니다.

npm install ws

 

 

그리고 이벤트를 등록하고 처리하는 함수인 on을 통해 클라이언트와 소켓을 연결할 수 있습니다.

const ws = require('ws');

const server = app.listen(PORT, () => {
    console.log(`http://localhost:${PORT}`);
})

const wsServer = new ws.Server({server}); // 웹 소켓 서버 접속

const sockets= []; // 빈 배열

//  on : 이벤트 등록 함수
wsServer.on('connection', (socket) => {
    sockets.push(socket); // 배열에 접속한 클라이언트 객체 추가

    // 클라이언트 메세지 수신
    socket.on('message', (message) => {
      console.log(`클라이언트로 부터 받은 메세지 :: ${message}`);

      // 웹 소켓 서버에 접속한 모든 클라이언트(브라우저 탭)에게 메세지 전송
      // = 브로드캐스팅(여러 컴퓨터한테 데이터 전송)
        sockets.forEach((socket) => {
          socket.send(`${message}`);        
        });
    });

    socket.on('close', () => {
      console.log('[클라이언트 연결 종료]')
    })
})

 

 


2. socket.io

 

socket.io는 자바스크립트의 라이브러리로 기본적으로 웹 소켓을 위해 구축되었습니다.

저희 같은 개발자가 웹 소켓을 좀 더 쉽고 간편하게 쓰도록 도와주는 라이브러리입니다.

 

 

여기에도 웹 소켓과 같은 기본 이벤트가 있습니다.

 

1-1. CONNECTION

io.on('connection', function (socket) {
  console.log('새로운 클라이언트가 연결되었습니다.');
});

 

클라이언트가 서버에 연결되었을 때 발생하는 이벤트입니다.

 

 

1-2. DISCONNECT

socket.on('disconnect', function () {
  console.log('클라이언트가 연결을 종료했습니다.');
});

 

클라이언트가 소켓에 대한 연결을 해제했을 때 발생하는 이벤트입니다.

 

1-3. ERROR

socket.on('error', function (error) {
  console.error('에러 발생:', error);
});

 

연결 중 오류가 발생했을 때 발생하는 이벤트입니다.

 

1-4. CUSTOM

// 서버에서 사용자 정의 이벤트 발생
socket.emit('customEvent', { message: '사용자 정의 이벤트 발생' });

// 클라이언트에서 사용자 정의 이벤트 수신
socket.on('customEvent', function (data) {
  console.log('사용자 정의 이벤트 수신:', data.message);
});

 

저희가 원하는 이벤트 이름으로 설정하고 사용할 수 있습니다.

 

여기서 헷갈렸던 건데, emit이 보내는 쪽이고 on이 받는 쪽입니다!!!

 

 

 

저희는 이러한 내용을 바탕으로 간단한 단체 채팅방을 구현했었는데요

 

먼저, 카카오톡처럼 단체 채팅에서 입장과 퇴장을 나타내는 소켓을 구현해 보겠습니다.

 

입장 후

 

client 측 코드

// 입장할 때 !!
    <div class="entry-box">
        <input
            type="text"
            id="nickname"
            placeholder="닉네임"
            autofocus
            onkeypress="if(window.event.keyCode==13){join()}"
            />
        <button type="button" onclick="join();">입장</button>
    </div><br />
    
    <script>
    // 채팅방 입장 시 setNick을 통해 닉네임을 서버측에 보냄 (닉네임 중복 검사를 위해)
        function join() {
                socket.emit('setNick', document.querySelector('#nickname').value); }
        
        socket.on('notice', (msg) => {
            document.querySelector('.chat-list').insertAdjacentHTML('beforeend', 
            `<div style="justify-items: center; text-align: center; text-color: gray; font-size: 10px;">${msg}</div>`)
        });
    </script>

 

첫 번째 입장 창을 거쳐 join 함수를 통해 서버 소켓으로 'setNick'이라는 커스텀이벤트로 전달하고 있습니다.

 

 

 

server 측 코드

 socket.on('setNick', (nick) => {
        console.log(`닉네임 설정 완료 :: ${nick}님 입장`);
        console.log('접속 유저 목록 :: ', nickArray);
        
        // 프론트에서 입력한 nick이 nickObs 객체에 존재하는지 검사
        // 이미 존재: error 이벤트 + '이미 존재하는 닉네임 입니다'
        // 새 닉네임: notice 이벤트 + '00님이 입장하셨습니다.'
        if (Object.values(nickArray).indexOf(nick) > -1 ) {
            socket.emit('error', `이미 존재하는 닉네임입니다.`);
        } else {
            // 새로운 닉네임
            nickArray[socket.id] = nick;
            socket.emit('entrySuccess', nick); // 해당 소켓 데이터 전송
            io.emit('notice', `${nick}님이 입장하셨습니다.`);
            io.emit('updateNicks', nickArray); // 전체 사용자 닉네임 모음 객체 전달
            updateList();
        }
    })

 

server에서는 setNick을 받고 있고 콜백함수로 nick을 받아서 

 

이미 존재하는 닉네임인지 확인하고, 만약 새로운 닉네임이라면

입장해서 'notice'라는 커스텀 이벤트로 메시지와 함께 클라이언트로 보내고 있습니다.

 

 

 

client 측 코드

socket.on('notice', (msg) => {
    document.querySelector('.chat-list').insertAdjacentHTML('beforeend', 
    `<div 
    	style="justify-items: center; text-align: center; 
        text-color: gray; font-size: 10px;">
        	${msg}
     </div>`)
});

 

그럼 socket.on의 'notice'를 통해서 chat-list에 div를 삽입해서 '${nick}님이 입장하셨습니다.'라는

문구가 사용자에게 보이게 됩니다.

 

퇴장도 마찬가지로 client 측의 notice를 이용하여 msg만 바꿔서 이용합니다.

이번엔 custom 이벤트가 아니라 socket.io의 기본 이벤트인

disconnect 이벤트를 사용해서 퇴장을 공지합니다.

 

server 측 코드

    socket.on('disconnect', () => {
        console.log(`접속 끊김 :: ${socket.id}`);
        io.emit('notice', `${nickArray[socket.id]}님이 퇴장하셨습니다.`);
    })

 

이제 메시지 기능을 어떻게 구현할 수 있는지 알아보겠습니다.

 

 

 

저는 html에서 my msg라는 부분과 other msg라는 부분이 나눠져 있습니다.

my msg는 내가 보내서 채팅이 오른쪽에 있고, other msg는 남이 보낸 메시지로 채팅이 왼쪽에 위치합니다.

 

채팅방 구현

 

 

client 측 코드

        function send() {
            socket.emit('send', {
                nick: myNick, 
                msg: document.querySelector('.input-msg').value});
            document.querySelector('.input-msg').value = '';
        }

    
    socket.on('sendMsg', (data) => {
            if (data.nick === myNick){
                document.querySelector('.chat-list').insertAdjacentHTML('beforeend',
                    `<div class="my-chat">
                    <div class="my-chat-msg">${data.msg}</div>
                    <div class="my-pic">${data.nick}</div>
                </div>`);
            } else {
                document.querySelector('.chat-list').insertAdjacentHTML('beforeend',
                    `<div class="other-chat">
                        <div class="other-pic">${data.nick}</div>
                    <div class="other-chat-msg"> ${data.msg}</div>
                    </div>`);
            }
    })

 

chat-list라는 div가 있어서, 만약 내 닉네임과 일치한다면,

my-chat이라는 div를 추가해서 오른쪽에 div를 생성해서 붙이는 방식을 사용합니다.

 

 

반대로 만약 내 닉네임과 일치하지 않는다면

other-chat이라는 div를 추가해서 왼쪽에 생성하여 붙이는 방식을 사용합니다.

 

 

server 측 코드

    socket.on('send', (data) => {
        io.emit('sendMsg', data);
    })

 

저희는 단체메시지 방이고, 개인 DM 기능은 하지 않아서 socket의 send로 닉네임과 메시지를 받은 다음

io (전체에 소켓을 보냄)으로 emit을 통해 client 측에 보냅니다.

 

그럼 my 메시지인지 other 메세지인지 판별해서 메세지가 잘 뜨고

메세지 도중에 입장하거나 퇴장한 사람이 있다면 계속 div를 붙이는 방식이기 때문에

잘 나오게 됩니다.

 

 

이제 귓속말 기능도 넣고, css 도 디벨롭 해 볼 예정입니다 :)

 

감사합니다.