본문 바로가기
프로그래밍/nodejs

[220317] websocket (웹소켓) 구현하기 / 채팅창 만들기

by 한코코 2022. 3. 18.

목록

1.   websocket 사용환경 만들기

2.   요청/응답 코드 작성하기

3.   websocket의 이벤트

4.   string 형식으로 데이터를 주고받는 websocket

5. 채팅창 완성하기


외부 라이브러리 ws 설치하기

$npm install ws

 

라이브러리 가져와서 세팅하기

const webSocket = require('ws')

 


 

백엔드에서 요청을 받아주는 코드 작성하기

handshake를 만들고 Connection을 이루는곳까지 코드로 구현했다.

//socket.js
const webSocket = require('ws')

module.exports = (server) =>{
    // 클래스 문법이니까 new로 선언
    const wss = new webSocket.Server({server})
    
    //응답이 되는지 확인할 이벤트!
    //이 코드가 없어도 통신은 일어난다. 시각적으로 확인하려고 만든 임의의 코드
    //브라우저의 개발자도구에서 네트워크 환경을 확인이 가능하다
    wss.on('connection',(ws,req)=>{
    	console.log(ws,req)
        console.log('접속이 되었니?')
    })
}

 

http서버와 ws서버를 동시에 돌리게 해주는 webSocket!

만들어 놓은 webSocket을 가져온 코드.

이 코드만 있으면 요청/응답이 이미 이루어진 상태임. (핸드셰이크 완료)

//server.js
const webSocket = require('./socket')

 

 

만약 http서버와 ws서버를 각각 다른 포트로 돌리고 싶을 경우

const wss1 = new webSocket({port:3006}) //웹소켓은 3006포트로 돌림

 

 

클라이언트에서 요청을 보내는 코드 작성하기

프로토콜이 http인 브라우저에서 프로토콜이 ws인 웹소켓과 통신하는 코드를 작성해야한다.

이때 WebSocket의 내장객체를 사용해서 작성한다.

웹소켓의 프로토콜은 http가 아닌 ws인걸 주의하자.

 

핸드셰이크를 만드는 onopen 이벤트

<!-- index.html -->
<script type="text/javascript">
    // WebSocket 내장객체 사용하기
    // 웹소켓은 프로토콜이 http가 아닌 wd인걸 기억하자
    // 백엔드에 있는 ws 프로토콜의 localhost 주소에 있는 3005번 포트에 신호를 주는것 
    const webSocket = new WebSocket("ws://localhost:3005")
    webSocket.onopen=()=>{
    	console.log('웹소켓 Connection 성공(Handshake)')
    }
</script>

 


 

웹소켓 통신을 도와주는 이벤트들

wss.on('connection',(ws,req)=>{}
webSocket.onopen=()=>{}

형광펜 효과가 들어간게 웹소켓 속 이벤트들이다.

웹소켓이 어떠한 활동을 할 수 있도록 도와주며, 이미 시스템적으로 역할이 정해져있어서 임의로 변경할 수 없다.

해당 활동이 일어났을때 메소드 안의 코드들을 실행한다.

프로그래밍 하는건 사람이므로, 출력된 결과를 보고 해석할 일이 많기 때문에 사용한다.

 

 

클라이언트에게 일어난 이벤트를 듣는 on 이벤트

ws : 각각 연결한 사람의 socket에 대한 내용

wss.on('connection',(ws,req)=>{
    console.log(ws)

    ws.on('close',(ws,req)=>{
        console.log('고객이 도망갔다!')
    })
})

연결이 끊게해주는 close 이벤트가 일어난 시점을 잡아내는 open 이벤트

 

 

접속자의 ip주소 찍기

wss.on('connection',(ws,req)=>{
	console.log(req.connection.remoteAddress)
})

 

 

 

클라이언트에게 메세지를 말하는 send 이벤트

메세지를 보내는 onmessage

// socket.js
wss.on('connection',(ws,req)=>{
    ws.send('클라이언트야 받고있니')
})


// index.js
webSocket.onmessage = (event) => {
	console.log('event data : ',event.data)
}

참고 블로그에서 더 많은 이벤트들과 메소드를 확인할 수 있다.

https://developer.mozilla.org/ko/docs/Web/API/WebSocket

 

두 이벤트는 클라이언트나 서버 둘 다 사용이 가능하며,

on이 있다면 듣기, send가 있다면 말하기 기능이라고 생각하면 편하다.

 

 


 

응용 : 서버가 클라이언트에게 내용을 던지기

<script type="text/javascript">
    const webSocket = new WebSocket("ws://localhost:3005")
    webSocket.onopen = () => {
        console.log('웹소켓 Connection 성공(Handshake)')
    }
	
    //send로 보낸 텍스트 꺼내쓰기
    webSocket.onmessage = (event) => {
        console.log('이거 출력!!',event.data)
    }
</script>

꺼내서 콘솔로 출력도 가능하다

서버가 클라이언트에게 내용을 요청(send)를 보냈지만 클라이언트는 응답을 주지 않았다.

왜냐하면 최초에 handshake를 통해서 쌍방간에 메세지를 주고받겠다는 상호협의를 끝낸 상황이기 때문이다.

단, string 형태에 제한되어서 주고 받기가 가능하다.

 

 

 

데이터 형식을 정말 string으로 주고받는지 확인하기

string 형태로 데이터를 주고받기 때문에 JSON.string()을 사용해 데이터를 전달한다.

//socket.js
const webSocket = require('ws')
module.exports = (server) =>{
    const wss = new webSocket.Server({server})

    wss.on('connection',(ws,req)=>{
        let layout={
            event:'init',
            msg:'클라이언트야 이거 받아'
        }

        ws.send(JSON.stringify(layout))
        //{"event":"init","msg":"클라이언트야 이거 받아"}로 변환된다
    })
}

 

 

string값을 object로 받는 message

//index.html
webSocket.onmessage = (event) => {
    const message = JSON.parse(event.data)
    //{"event":"init","msg":"클라이언트야 이거 받아"}
    //위 내용을 객체로 변환해서 받는 message

    console.log(typeof event.data)
}

 

 


통신하기 위해 string으로 변환해서 데이터를 넘겨주는 것은 알았다.

하지만 데이터를 받을 경우에는 원본 데이터의 형태를 알아야 원상복구가 가능하다.

string으로 변환하기 전의 원본 형태는 어떻게 알 수 있을까.

 

 

string으로 변하기 전, 원본 형태는 event에 넣기

event가 각각 다른 객체를 만들었다.

//server.js
const webSocket = require('ws')
module.exports = (server) =>{
    const wss = new webSocket.Server({server})

    wss.on('connection',(ws,req)=>{
        let layout={
            event:'init',
            msg:'클라이언트야 이거 받아'
        }
        
        let example={
            event:'ingoo',
            msg:'hello socket'
        }

        ws.send(JSON.stringify(layout))
        ws.send(JSON.stringify(example))
    })
}

 

 

event가 'init'일 경우 콘솔에 출력하고,

event가 'ingoo'일 경우 브라우저 화면에 출력하도록 만들었다.

// index.html
webSocket.onmessage = (event) => {
    const message = JSON.parse(event.data)
    const {event:evt, msg} = message; 

    console.log(evt,msg);
    if(evt === 'init'){
    	console.log(msg)
    } else if(evt==='ingoo'){
    	document.write(msg)
    }
}

콘솔창
브라우저창

 

 


 

5. 채팅창 완성하기

 

body안에 내용을 추가하는 body.innerHTML

내용 자체를 통으로 바꿔버리는 write

write를 사용하면 자바스크립트 내용이 날아가버려서 서버와의 연결이 끊어지는 것.

// index.html
document.write(msg)
document.body.innerHTML = msg

 

메세지 객체를 이렇게 날리면서 구분할 수 있는 기준은 event

 

 

 

 

브라우저에서 back으로 메세지를 날려보기

보낼 데이터를 객체로 만들어서 string으로 보내기

// socket.js
let return_msg = {
    event : 'return',
    msg:'돌려달라ㅏㅏ'
}

ws.send(JSON.stringfy(return_msg))

 

 

string으로 데이터를 받아서 백엔드로 보내기

// indes.html
webSocket.onmessage = (event) => {
    const message = JSON.parse(event.data)
    const {event:evt, msg} = message; 

    console.log(evt,msg);

    if(evt === 'init'){
    	console.log(msg)
    }else if(evt==='ingoo'){
        // document.write(msg)
        document.body.innerHTML = msg
    }else if(evt==='return'){
    	webSocket.send('hohohohoho')
    }
}

 

 

 

 

백엔드로 보낸 데이터를 받는 코드 작성하기

백엔드로 받은 데이터를 그냥 출력하려고하면 Buffer 값이 출력된다.

Buffer 값을 해석하는 코드를 작성해주었다.

// socket.js
ws.on("message",(data)=>{
	console.log(data.toString('utf-8'))
})
// index.html
document.addEventListener('DOMContentLoaded',()=>{
      const form = document.querySelector('form');
      form.addEventListener('submit',(e)=>{
        e.preventDefault();
        const {input} = e.target;
  
        console.log(input.value);
      })
    })


  </script>
</head>
<body>
  <form action="/" method="get">
    <input type="text" name="input" id="input">
    <input type="submit" value="입력">
  </form>
</body>
</html>

 

 

 

응용해서 입력하면 목록이 생겨나게 만들기

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>웹소켓</title>
  <script type="text/javascript">
    const webSocket = new WebSocket('ws://localhost:3005');
    webSocket.onopen=()=>{
      console.log('웹소켓 Connection 성공(핸드셰이크)')
    }

    webSocket.onmessage = (event) => {
      const message = JSON.parse(event.data)
      const {event:evt, msg} = message; 

      console.log(evt,msg);

      if(evt === 'init'){
        console.log(msg);
      }else if(evt==='ingoo'){
        // document.write(msg)
        // document.body.innerHTML = msg;
      }else if(evt==='return'){
        webSocket.send('hohohohoho');
      }
    }

    document.addEventListener('DOMContentLoaded',()=>{
      const form = document.querySelector('form');

      form.addEventListener('submit',(e)=>{
        e.preventDefault();
        const {input} = e.target;
        webSocket.send(input.value);
      })
      
      webSocket.onmessage = (event) => {
          const chat = document.querySelector('#chat');
          let liElement = document.createElement("li");
          liElement.innerHTML = event.data;
          chat.appendChild(liElement);
          console.log(event.data)
        }
    })


  </script>
</head>
<body>
  <form action="/" method="get">
    <input type="text" name="input" id="input">
    <input type="submit" value="전송">
  </form>
  <ul id="chat">

  </ul>
</body>
</html>
// socket.js
const { sort } = require('nunjucks/src/filters');
const webSocket = require('ws');

let sockets = [];
module.exports = (server) => {
  const wss = new webSocket.Server({server});

  wss.on('connection',(ws,req)=>{
    sockets.push(ws);
    console.log(sockets.length);
    console.log(req.connection.remoteAddress);
    
    ws.on('close',(ws,req)=>{
      console.log('고객이 도망쳤다@')
    })

    ws.on("message",(data)=>{
      console.log(data.toString('utf-8'));
      sockets.forEach(v=>{
        v.send(data.toString('utf-8'))
      })
    })
  })
}

 

댓글