이번 포스팅에서는 WebSocket을 사용해서 간단한 채팅 기능을 구현해보고자 한다.
< 목차 >
- WebSocket ??
- WebSocket으로 채팅 기능 구현하기
1. WebSocket ??
웹소켓(WebSocket)은 HTTP 같은 통신 프로토콜의 일종으로서 서버와 클라이언트 간의 효율적인 양방향 통신을 실현하기 위해 등장한 개념이다. 위키백과에서는 다음과 같이 웹소켓을 정의하고 있다.
"웹소켓은 하나의 TCP 접속에 전이중 통신 채널을 제공하는 컴퓨터 통신 프로토콜이다" (출처 : 위키백과)
새로운 개념이 등장했을 때는 내가 기존에 알고 있던 것과 비교해 보면서 차이점을 중심으로 이해하고 받아들이는게 좋은 방법이라고 생각한다. HTTP 와 WebSocket의 차이점에 대해 살펴보면서 웹소켓에 대해 알아보도록 하자.
HTTP 통신에서는 하나의 요청에 반드시 하나의 응답이 있어야만 하는 구조였다. 이는 바꿔말하면 클라이언트 쪽에서 요청이 없다면 서버로부터 응답을 받을 수 없다는 의미이기도 하다. 즉, 클라이언트에서 서버로 요청을 보내고 서버는 이에 응답하는 방식으로 통신이 이루어지기 때문에 단방향 통신의 성격을 가지게 된다. 하지만 WebSocket에서는 클라이언트와 서버 간에 양방향 연결이 이루어지게 되어 클라이언트와 서버가 서로 데이터를 주고 받을 수 있는 양방향 통신이 가능해진다. WebSocket을 이용하면 서버도 클라이언트에게 요청을 보낼 수 있고 클라이언트도 서버에게 응답을 보낼 수 있게 되는 것이다.
클라이언트와 서버 간에 양방향 연결이 이루어지는 것을 우리는 "커넥션을 맺는다"고 표현한다. 그리고 클라이언트와 서버 간에 커넥션을 맺기 위해서는 한번의 요청과 응답을 주고받는 행위가 선행된다. 이를 핸드쉐이크(HandShake)라 한다. 이렇게 핸드쉐이크가 일어난 이후에는 이제 클라이언트와 서버가 서로 간에 데이터를 주고 받을 수 있는 연결 상태가 되는 것이다. 여기서 중요한 포인트는 바로 "서로 간에" 라는 부분이다. HTTP 통신에서는 클라이언트는 요청만, 서버는 응답만이 가능했다. 하지만 웹소켓에서는 서버가 클라이언트에게 요청을 보내는 행위도 가능해진 것이다. 이로써 클라이언트와 서버 간에 실시간 통신이 가능해진다. 실시간 통신이 가능하다는 이러한 특성 때문에 우리는 웹소켓을 활용해서 채팅 프로그램, 경매 사이트 등을 만들 수 있게 된다.
1. WebSocket으로 채팅 기능 구현하기
위에서 설명한대로 실시간 통신을 할 때는 HTTP가 아닌 WS라는 프로토콜을 사용하게 된다. 하지만 우리가 만들어 볼 것은 채팅 기능이 구현되어 있는 웹페이지이다. 즉, 우리는 클라이언트에게 웹페이지를 보여주면서 실시간 통신까지 하는 서버를 제작해야 하는 것이다. 그래서 우리는 WS 통신과 HTTP 통신을 같이 구현할 것이다. WS 통신을 이용해서는 데이터만을 주고 받고 HTTP 통신을 이용해서는 브라우저에 화면을 랜더하게끔 하는 것이다. 브라우저가 HTTP 통신과 함께 WS 통신도 할 것이므로 서버쪽에서도 HTTP 통신을 할 수 있는 코드와 WS 통신을 할 수 있는 코드를 작성해주면 된다. HTTP 통신은 기존처럼 express 라이브러리를 사용해서, WS 통신은 ws라는 외부 라이브러리를 사용해서 구현해보자.
메인 서버 파일인 server.js 파일은 다음과 같다.
웹소켓 통신과 관련된 코드들은 socket.js 파일로 분리하여 작성하였다. server.js 파일의 webSocket( )의 인자값으로 app.listen( ) 을 전달하게 되면 해당 내용이 socket.js 파일의 server 변수에 들어가게 된다. new webSocket.Server( { server } ) 의 server 변수에 app.listen( ) 부분이 들어가면서 express로 만든 서버와 포트를 공유할 수 있게 된다. HTTP 통신과 WS 통신을 할 때 포트를 분리해서 작업하는 경우도 있지만 편의를 위해 포트 하나를 공유해서 사용하기도 한다.
// socket.js 파일
const webSocket = require('ws') // 외부 라이브러리 'ws'사용
let sockets = [] // 연결된 소켓들을 담을 배열
module.exports = (server) => {
// 웹소켓 기능 구현
const wss = new webSocket.Server({ server }) // express와 포트 공유하기
// const wss = new webSocket.Server({ port: 3001 }) // 포트 나누기
// 웹소켓에서는 이벤트로 내용을 처리
// 임의로 이벤트명을 지정할 수도 있지만, 기본적으로 가지고 있는 이벤트명도 존재
// 커넥션이 일어나는 시점을 잡아서 코딩하기 위해 이벤트를 명시해주는 것 (이벤트 기법)
wss.on('connection', (ws, req)=>{
// ws 안에는 연결된 클라이언트의 정보가 담겨있다.
// req에는 처음 커넥션을 맺었을 때의 요청헤더 정보가 담겨있다.
console.log(req.connection.remoteAddress)
ws.id = req.headers['sec-websocket-key']
// sec-websocket-key를 통해 웹소켓 식별
sockets.push(ws)
// 연결이 될 때마다 sockets 배열에 소켓을 넣어준다.
console.log(sockets.length)
ws.on('close', (code, reason)=>{
// code : 연결이 종료되는 이유를 가르키는 숫자
// 기본값은 1000
// reason : 왜 종료되는지 사람이 읽을 수 있도록 나타내는 문자열
// utf-8 포멧 123바이트를 넘을 수 없다.
console.log('고객이 도망쳤다!!')
sockets = sockets.filter(v=>{
console.log(ws.id === v.id)
return ws.id !== v.id
})
console.log(sockets.length)
// sockets 배열의 length를 통해 연결이 끊긴 것을 확인할 수 있다.
})
// 브라우저로부터 온 메세지 받기
ws.on('message', (response)=>{
let obj = JSON.parse(response.toString())
let {type, data} = obj
switch(type) {
case 'send_msg':
sockets.forEach( v => v.send(data) )
break;
}
})
})
}
웹소켓이 정상적으로 생성되고 연결되었다면 다음과 같은 이벤트를 사용할 수 있다.
- 'connection' / onopen : 커넥션이 맺어졌을 때 발생하는 이벤트
- 'message' / onmessage : 데이터를 수신했을 때 발생하는 이벤트
- 'error' / onerror : 에러가 생겼을 때 발생하는 이벤트
- 'close' / onclose : 커넥션이 종료되었을 때 발생하는 이벤트
ws.send( ) 메소드를 사용해서 클라이언트에게 데이터를 전달.
ws.on( ) 메소드를 사용해서 클라이언트로부터 온 데이터를 수신.
브라우저에 랜더하는 index.html 파일에서는 <script>엘리먼트를 통해 다음과 같은 코드를 작성하여 브라우저의 JavaScript로 웹소켓을 생성하고 서버와 연결할 수 있게끔 하였다.
<!-- 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>Document</title>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', ()=>{
// 브라우저에서 new WebSocket()으로 webSocket이라는 객체 생성
const webSocket = new WebSocket('ws://localhost:3000')
// 브라우저는 ws://localhost:3000으로 요청을 보내게 된다.
// 연결에 성공하면 아래의 콜백함수가 실행됨
webSocket.onopen = () => {
console.log('웹소켓 connection 성공 ( HandShake )')
}
const form = document.querySelector('form')
form.addEventListener('submit', (e)=>{
e.preventDefault()
const { input } = e.target
// 발생하는 event에 따라 구분값을 만들어주기 위해
// 객체 형태로 메세지를 작성해서 보낸다.
// 단, 데이터를 전달할 때는 무조건 string 형태로 전달.
let data = {
type: 'send_msg',
data: input.value
}
webSocket.send(JSON.stringify(data)) // 데이터 전달은 string 형태로,,
input.value = ''
input.focus()
})
webSocket.onmessage = (event) => {
const chat = document.querySelector('#chat')
const liElement = document.createElement('li')
liElement.innerHTML = event.data
chat.appendChild(liElement)
console.log(event.data)
}
})
</script>
</head>
<body>
<h1><a href="/">LOGO</a></h1>
<h2>Hello Web Socket!!</h2>
<form action="/" method="get">
<input type="text" name="input" id="input">
<input type="submit" value="전송">
</form>
<ul id="chat">
</ul>
</body>
</html>
브라우저 쪽에서도 webSocket.send( ) 메소드를 사용해서 서버에게 데이터를 전달할 수 있다. 주의할 점은 string 형태로 데이터를 전달해야 한다는 것이다.
아래 영상은 지금까지 작성한 코드들로 실제 크롬 브라우저와 파이어폭스 브라우저에서 http://localhost:3000으로 접속한 뒤 실시간 채팅을 진행하는 시연 영상이다.
'Node > Express' 카테고리의 다른 글
Node.js - express (18) multer (2) (0) | 2022.03.15 |
---|---|
Node.js - express (17) multer (1) (0) | 2022.03.15 |
Node.js - express (16) CORS (0) | 2022.03.13 |
Node.js - express (15) Ajax - fetch , axios (0) | 2022.03.07 |
Node.js - express (14) Ajax - XMLHttpRequest( ) (0) | 2022.03.06 |