WebSocket은 클라이언트와 서버가 한 번 연결된 뒤, 그 연결을 유지하면서 데이터를 실시간으로 주고받기 위한 통신 방식이다.
HTML5에서 제공하는 표준 기술이며,HTTP환경에서 시작해 하나의TCP연결을 통해 통신한다.
여기서 클라이언트는 보통 브라우저를 의미한다.
서버는 클라이언트의 요청을 처리하고 필요한 데이터를 보내 주는 프로그램을 의미한다.
기존 웹 통신은 대부분HTTP를 사용한다.
HTTP는 클라이언트가 먼저 요청해야 서버가 응답하는 방식이다.
예를 들어 사용자가 버튼을 누르거나 페이지를 새로고침하면 브라우저가 서버에 요청을 보낸다.
그러면 서버는 그 요청에 대한 응답을 돌려준다.
반면WebSocket은 한 번 연결된 뒤에는 클라이언트와 서버가 서로 자유롭게 데이터를 주고받을 수 있다.
클라이언트가 매번 새 요청을 보내지 않아도 서버가 먼저 데이터를 보낼 수 있다.
그래서 실시간 채팅, 실시간 알림, 실시간 게임처럼 즉시 반응해야 하는 기능에 사용된다.
전 이중 통신이란 무엇인가
전 이중 통신은 같은 연결 안에서 양쪽이 동시에 데이터를 보내고 받을 수 있는 통신 방식이다.
쉽게 말하면 전화 통화와 비슷하다.
전화를 하면 내가 말할 수도 있고, 상대방도 말할 수 있다.
한쪽만 계속 말하고 다른 쪽은 듣기만 하는 구조가 아니다.
단방향 통신은 한쪽 방향으로만 데이터가 흐르는 방식이다.
텔레비전 방송이나 라디오는 대표적인 단방향 통신이다.
사용자는 방송을 보거나 라디오를 들을 수는 있지만, 텔레비전 방송국이나 라디오 방송국으로 직접 데이터를 보내지는 못한다.
WebSocket은 전 이중 통신을 지원한다.
따라서 클라이언트가 서버로 메시지를 보낼 수도 있고, 서버가 클라이언트로 메시지를 보낼 수도 있다.
이 양방향 구조가 실시간 기능의 핵심이다.
HTTP와 WebSocket의 차이
HTTP는 요청과 응답이 한 묶음으로 움직인다.
클라이언트가request를 보내면 서버가response를 보낸다.
그 응답이 끝나면 기본적으로 그 요청의 처리는 끝난다.
예를 들어 일반 웹 페이지에서는 사용자가 게시글 목록을 요청해야 서버가 게시글 목록을 보내 준다.
사용자가 아무 요청도 하지 않으면 서버는 보통 먼저 데이터를 보내지 않는다.
이 구조는 문서나 화면을 요청해서 받아오는 데에는 잘 맞는다.
하지만 채팅처럼 새 메시지가 생기는 즉시 화면에 보여야 하는 기능에는 불편하다.
서버에 새 메시지가 생겼는지 클라이언트가 계속 물어봐야 하기 때문이다.
WebSocket은 처음에 연결을 맺은 뒤 그 연결을 유지한다.
연결이 유지되는 동안에는 클라이언트와 서버가 필요한 순간에 바로 데이터를 보낼 수 있다.
따라서 매번 새 요청을 만들 필요가 줄어든다.
HTTP는 요청과 응답이 반복되고,WebSocket은 연결을 유지한 상태에서 양방향으로 데이터를 주고받는다.
왼쪽의HTTP는 클라이언트가 요청해야 서버가 응답할 수 있다.
오른쪽의WebSocket은 처음 연결을 맺은 뒤, 연결이 유지되는 동안 양쪽이 데이터를 주고받을 수 있다.
하나의 TCP 연결을 유지한다는 의미
TCP는 데이터를 안정적으로 주고받기 위해 사용하는 네트워크 연결 방식이다.
이 단계에서는TCP를 “클라이언트와 서버 사이에 만들어지는 통신 통로” 정도로 이해하면 된다.
여기서 중요한 차이는TCP연결을 내부적으로 재사용할 수 있는지 여부가 아니다.
초보자 기준에서는HTTP가 요청과 응답 중심으로 동작하고,WebSocket은 연결을 유지한 상태에서 메시지를 계속 주고받는다는 흐름을 먼저 이해하면 된다.
WebSocket은 하나의TCP연결을 만든 뒤 그 통로를 계속 유지한다.
그래서 채팅 메시지처럼 계속 오가는 데이터를 처리할 때 유리하다.
쉽게 말하면HTTP는 필요할 때마다 문을 두드리고 답을 받는 방식에 가깝다.
WebSocket은 문을 열어 둔 상태에서 계속 대화하는 방식에 가깝다.
정리하면WebSocket은 실시간 양방향 통신을 위해 연결을 계속 유지하는 기술이다.
이 흐름을 이해하면 이후에 나오는Handshake,send(),onmessage,Session도 훨씬 쉽게 이어진다.
WebSocket은 연결을 계속 유지하면서 데이터를 주고받는 방식이다.
그래서 연결을 시작할 때 클라이언트와 서버가 먼저 서로 확인하는 과정이 필요하다.
이 과정을Handshake라고 한다.
Handshake는 통신을 시작하기 전에 “이제WebSocket방식으로 연결해도 되는지” 확인하는 절차다.
쉽게 말하면 전화를 걸었을 때 상대방이 전화를 받기 전까지는 대화가 시작되지 않는 것과 비슷하다.
상대방이 전화를 받아야 그때부터 서로 말을 주고받을 수 있다.
WebSocket은 연속적으로 데이터를 주고받는 통신 방식이기 때문에, 연결을 안정적으로 시작하기 위해Handshake를 먼저 진행한다.
이 흐름을 알아야 뒤에서 나오는101 Switching Protocols,Upgrade, 개발자 도구의Network확인도 자연스럽게 이해된다.
Handshake가 필요한 이유
WebSocket은 단순히 한 번 요청하고 응답받고 끝나는 구조가 아니다.
연결이 열리면 그 연결을 계속 유지한다.
그래서 처음 연결할 때 서버가 이 연결을 받아들일 수 있는지 확인해야 한다.
만약 서버가WebSocket통신을 처리할 준비가 되어 있지 않다면 연결을 열면 안 된다.
연결을 열어도 메시지를 제대로 주고받을 수 없기 때문이다.
그래서 클라이언트는 먼저 서버에WebSocket연결을 요청하고, 서버는 그 요청을 받아들일지 판단한다.
또한WebSocket은 연결된 상태에서 데이터를 계속 주고받는다.
따라서 시작 단계에서 연결이 제대로 수립되었는지 확인해야 이후 데이터 전송도 안정적으로 이어질 수 있다.
즉,Handshake는 안정적인 양방향 통신을 시작하기 위한 첫 단계다.
기존 TCP 기반 프로토콜과 WebSocket의 차이
TCP는 데이터를 안정적으로 주고받기 위한 기본 통신 통로다.
많은 네트워크 통신은TCP연결 위에서 동작한다.
여기서layer는 통신을 처리하는 단계라고 이해하면 된다.
기존의 다른TCP기반 프로토콜은 보통TCP layer에서 연결을 수립하는 과정을 거친다.
즉, 통신의 기본 연결 자체를TCP단계에서 맺고 그 위에서 데이터를 주고받는다.
WebSocket도 결국TCP연결 위에서 동작한다.
하지만 연결을 시작하는 방식이 다르다.
WebSocket은 처음에HTTP요청을 기반으로Handshake를 진행한 뒤, 연결이 성공하면WebSocket통신으로 전환된다.
이 차이를 초보자 기준으로 정리하면 다음과 같다.
- 기존
TCP기반 프로토콜은TCP layer에서 연결 수립 과정을 거친다.WebSocket은 처음에HTTP요청으로 연결 전환을 요청한다.- 서버가 받아들이면 이후에는 같은 연결을 유지하면서 계속 데이터를 주고받는다.
즉,
WebSocket은TCP연결 위에서 동작하지만, 시작 과정에서는HTTP요청 기반의Handshake를 사용한다.
HTTP Upgrade의 의미
WebSocket연결은 처음에HTTP요청으로 시작한다.
이때 클라이언트는 서버에게 “이 연결을WebSocket통신으로 바꾸고 싶다”라고 요청한다.
이 요청을HTTP Upgrade라고 이해하면 된다.
Upgrade는 말 그대로 통신 방식을 바꾼다는 의미다.
처음에는HTTP로 요청을 보내지만, 서버가 허락하면 이후 통신은WebSocket방식으로 바뀐다.
서버가 이 전환을 받아들이면Status Code로101을 응답한다.
101 Switching Protocols는 프로토콜을 바꾼다는 뜻이다.
여기서는HTTP로 시작한 연결이WebSocket으로 전환되었다는 의미다.
개발자 도구에서101이 보이면WebSocket연결 전환이 성공했다는 중요한 신호로 볼 수 있다.
WebSocket 연결 흐름
WebSocket통신은 크게 세 단계로 이해하면 된다.
- 첫 번째는
Handshake단계다.- 두 번째는 양방향 통신 단계다.
- 세 번째는 연결 종료 단계다.
먼저 브라우저가 서버에
HTTP Upgrade요청을 보낸다.
서버가 이 요청을 받아들이면101 Switching Protocols로 응답한다.
이후 연결이 열린 상태가 되고, 클라이언트와 서버는 메시지를 계속 주고받을 수 있다.
마지막에는 한쪽에서 연결 종료를 요청하고 연결이 닫힌다.
WebSocket은 처음에는HTTP요청으로 연결을 시작하고, 서버가 받아들이면101응답을 통해WebSocket통신으로 전환된다.
이후에는 연결을 유지한 상태에서 양방향으로 데이터를 주고받고, 마지막에는 연결 종료 과정을 거친다.
통신 방식 핵심 정리
WebSocket은 연결을 유지하는 통신 방식이기 때문에 시작 단계가 중요하다.
처음에는HTTP요청으로 서버에 연결 전환을 요청한다.
서버가 이를 받아들이면101 Switching Protocols응답이 오고, 그 뒤부터는WebSocket방식으로 메시지를 주고받는다.
핵심만 정리하면 다음과 같다.
WebSocket은 전 이중 통신을 위해 연결을 유지한다.- 연결을 시작할 때
Handshake가 필요하다.WebSocket의Handshake는HTTP요청을 기반으로 진행된다.- 서버가 연결 전환을 허락하면
101 Switching Protocols가 응답된다.- 연결이 열린 뒤에는 클라이언트와 서버가 서로 데이터를 보낼 수 있다.
정리하면
WebSocket의 통신 방식은HTTP요청으로 연결을 시작하고, 성공 후에는 유지된 연결을 통해 실시간 양방향 통신을 하는 구조다.
초기 웹은 문서를 전달하고, 링크를 통해 다른 문서로 이동하는 목적이 컸다.
이 목적에는HTTP방식이 잘 맞았다.
사용자가 어떤 문서를 요청하면 서버가 그 문서를 보내 주면 되었기 때문이다.
하지만 웹에서 처리해야 하는 기능이 점점 복잡해졌다.
단순히 문서를 보여 주는 것을 넘어서, 화면이 바로 바뀌고 사용자의 행동에 즉시 반응해야 하는 기능이 많아졌다.
예를 들어 채팅 메시지는 상대방이 보내는 순간 바로 보여야 하고, 알림은 새 내용이 생기는 순간 바로 떠야 한다.
이런 기능은 기존의HTTP요청-응답 방식만으로는 자연스럽게 처리하기 어렵다.
그래서WebSocket이 등장하기 전에는Polling,Long Polling,HTTP Streaming같은 방식으로 실시간에 가까운 기능을 구현했다.
초기 웹과 HTTP가 잘 맞았던 이유
초기 웹의 핵심 목적은 문서 전달이었다.
사용자가 어떤 페이지를 요청하면 서버가 해당 문서를 보내 준다.
사용자는 문서 안의 링크를 눌러 다른 문서로 이동한다.
이 흐름에서는HTTP방식이 충분히 적합하다.
클라이언트가 요청하고, 서버가 응답하면 하나의 작업이 끝난다.
문서를 가져오는 작업은 대부분 “필요할 때 요청하고 받아오기”만으로 처리할 수 있기 때문이다.
예를 들어 사용자가 게시글 목록 페이지를 연다고 생각하면 된다.
브라우저가 서버에 게시글 목록을 요청한다.
서버는 게시글 목록 화면을 응답한다.
이런 구조에서는 서버가 먼저 계속 데이터를 보낼 필요가 크지 않다.
웹에서 실시간 기능이 필요해진 이유
웹이 발전하면서 사용자는 페이지를 새로고침하지 않아도 화면이 바뀌기를 기대하게 되었다.
채팅에서는 새 메시지가 바로 보여야 한다.
SNS에서는 새 알림이 바로 떠야 한다.
멀티 플레이어 게임에서는 다른 사용자의 움직임이 거의 즉시 반영되어야 한다.
이런 기능은 공통점이 있다.
서버에 새 데이터가 생긴 순간, 클라이언트 화면에도 바로 반영되어야 한다.
즉, 클라이언트가 매번 요청할 때까지 기다리면 늦다.
실시간 기능에서는 서버와 클라이언트가 계속 연결된 상태에서 데이터를 바로 주고받는 구조가 필요하다.
이 요구를 더 직접적으로 해결하기 위해 등장한 기술이WebSocket이다.
Polling 방식
Polling은 클라이언트가 서버에 계속 물어보는 방식이다.
예를 들어 브라우저가 3초마다 서버에 “새 메시지 있어?”라고 요청한다고 생각하면 된다.
서버에 새 메시지가 있으면 메시지를 응답하고, 없으면 없다고 응답한다.
이 방식은 구현 흐름이 단순하다.
클라이언트가 일정 시간마다 요청을 보내면 되기 때문이다.
하지만 실시간 기능에는 비효율적이다.
새 데이터가 없어도 계속 요청이 발생하기 때문이다.
예를 들어 채팅방에 아무도 메시지를 보내지 않아도 브라우저는 계속 서버에 요청한다.
사용자가 많아질수록 서버는 실제로 필요 없는 확인 요청까지 계속 처리해야 한다.
즉,Polling은 실시간처럼 보이게 만들 수는 있지만, 실제 구조는 반복 확인 방식이다.
서버가 새 데이터가 생긴 순간 바로 밀어 보내는 구조는 아니다.
Long Polling과 HTTP Streaming
Long Polling은 일반Polling보다 오래 기다리는 방식이다.
클라이언트가 요청을 보내면 서버가 바로 응답하지 않는다.
새 데이터가 생길 때까지 잠시 기다렸다가, 데이터가 생기면 응답한다.
이 방식은 일반Polling보다 불필요한 응답을 줄일 수 있다.
하지만 응답이 끝나면 클라이언트가 다시 요청을 보내야 한다.
결국 연결을 완전히 유지한 상태에서 자유롭게 양방향 통신을 하는 구조는 아니다.
HTTP Streaming은 서버가 응답 연결을 유지하면서 데이터를 조금씩 보내는 방식이다.
서버에서 클라이언트로 계속 데이터를 흘려보낼 수 있다는 점에서 실시간에 가까운 처리가 가능하다.
하지만 클라이언트와 서버가 서로 자유롭게 주고받는 전 이중 구조와는 차이가 있다.
이 세 방식은WebSocket이 등장하기 전 실시간에 가까운 기능을 만들기 위해 사용된 방식이다.
하지만WebSocket은 실시간 양방향 통신을 위해 만들어진 방식이므로 구조가 더 직접적이다.
Polling과 WebSocket의 차이
Polling과WebSocket의 가장 큰 차이는 서버에 새 데이터가 있는지 확인하는 방식이다.
Polling은 클라이언트가 계속 서버에 물어본다.
WebSocket은 연결을 유지하고 있다가, 새 데이터가 생기면 서버가 바로 보낼 수 있다.
채팅으로 비교하면 차이가 더 쉽다.
Polling방식에서는 브라우저가 계속 “새 채팅 있어?”라고 확인해야 한다.
WebSocket방식에서는 누군가 메시지를 보내는 순간 서버가 연결된 사용자들에게 바로 전달할 수 있다.
Polling은 클라이언트가 계속 요청을 보내야 하지만,WebSocket은 연결을 유지한 상태에서 서버도 즉시 데이터를 보낼 수 있다.
왼쪽의Ajax Polling은 반복적으로GET /poll요청을 보내고, 오른쪽의WebSocket은 한 번 연결한 뒤 그 연결을 통해 데이터를 주고받는다.
실시간 웹 애플리케이션에서 WebSocket을 사용하는 이유
실시간 웹 애플리케이션은 서버와 클라이언트 사이의 반응 속도가 중요하다.
새 데이터가 생긴 뒤 화면에 늦게 반영되면 사용자는 답답하게 느낀다.
채팅, 게임, 공동 문서 편집처럼 여러 사용자가 동시에 상호작용하는 기능에서는 특히 그렇다.
WebSocket은 연결을 유지한 상태에서 데이터를 주고받는다.
그래서 서버에 새 데이터가 생기면 클라이언트에게 바로 보낼 수 있다.
클라이언트도 서버로 즉시 메시지를 보낼 수 있다.
이 구조 덕분에SNS, 멀티 플레이어 게임, 공유 문서, 실시간 채팅 같은 기능에 적합하다.
정리하면WebSocket은 실시간 웹 애플리케이션을 자연스럽게 구현하기 위해 등장한 통신 방식이다.
WebSocket통신은 연결을 열고, 그 연결을 유지하면서 데이터를 주고받고, 마지막에 연결을 닫는 흐름으로 동작한다.
일반적인HTTP통신처럼 요청 하나에 응답 하나가 끝나는 구조가 아니다.
연결이 유지되는 동안 클라이언트와 서버는 여러 번 데이터를 주고받을 수 있다.
이 차이를 이해하면WebSocket이 왜 실시간 채팅에 적합한지 알 수 있다.
채팅에서는 한 번 메시지를 보내고 끝나는 것이 아니라, 사용자가 접속해 있는 동안 계속 메시지가 오간다.
그래서 매번 새 요청을 만드는 방식보다 연결을 유지하는 방식이 더 자연스럽다.
WebSocket통신의 핵심은 연결을 유지한 상태에서 클라이언트와 서버가 서로 필요한 순간에 데이터를 보낼 수 있다는 점이다.
서버도 클라이언트에게 먼저 데이터를 보낼 수 있기 때문에 실시간 알림이나 채팅 메시지 전달에 적합하다.
연결을 열고 유지하고 닫는 흐름
WebSocket통신은 크게 세 단계로 나눌 수 있다.
- 연결을 여는 단계
- 데이터를 주고받는 단계
- 연결을 닫는 단계
연결을 여는 단계에서는
Opening Handshake가 진행된다.
Opening Handshake는 통신을 시작하기 전에 클라이언트와 서버가 서로 연결 가능 여부를 확인하는 과정이다.
이 과정이 성공하면 연결 상태가CONNECTED가 된다.
CONNECTED상태는 클라이언트와 서버가 서로 연결되어 있는 상태를 의미한다.
이 상태에서는 데이터를 계속 주고받을 수 있다.
이때 주고받는 데이터는 하나의message단위로 이동한다고 이해하면 된다.
마지막에는Closing Handshake를 통해 연결을 닫는다.
Closing Handshake는 통신을 끝내기 전에 연결 종료를 서로 확인하는 과정이다.
연결이 닫히면 더 이상 데이터를 주고받을 수 없다.
WebSocket은 연결 시작과 종료에도 별도의 프로토콜 절차가 있고, 연결 중에는 메시지를 계속 주고받을 수 있다.
처음에는HTTP UPGRADE요청과HTTP 101응답이 오가고, 연결된 뒤에는message단위로 데이터가 이동한다.
request-response 방식과 다른 점
일반적인
HTTP통신은request-response방식이다.
request-response는 클라이언트가 요청을 보내고, 서버가 그 요청에 대한 응답을 보내는 구조다.
즉, 클라이언트가 먼저 움직여야 서버가 응답할 수 있다.
예를 들어 브라우저가 게시글 목록을 요청하면 서버는 게시글 목록을 응답한다.
브라우저가 다시 댓글 목록을 요청하면 서버는 댓글 목록을 응답한다.
이처럼HTTP는 요청과 응답을 기준으로 동작한다.
반면WebSocket은 연결이 열린 뒤에는 요청과 응답을 한 묶음으로만 보지 않는다.
연결된 상태에서 클라이언트가 서버로 데이터를 보낼 수 있고, 서버도 클라이언트로 데이터를 보낼 수 있다.
따라서 새 데이터가 생길 때마다 클라이언트가 계속 요청하지 않아도 된다.
이 차이 때문에WebSocket은 실시간 기능에 적합하다.
서버에 새 채팅 메시지나 새 알림이 생기면, 서버가 연결된 클라이언트에게 바로 보낼 수 있기 때문이다.
서버가 클라이언트에게 먼저 보낼 수 있다는 의미
WebSocket의 중요한 특징 중 하나는 서버가 클라이언트에게 먼저 데이터를 보낼 수 있다는 점이다.
일반적인HTTP흐름에서는 클라이언트가 먼저 요청하지 않으면 서버가 응답을 보내기 어렵다.
하지만WebSocket에서는 연결이 이미 열려 있기 때문에 서버가 필요한 순간 데이터를 보낼 수 있다.
채팅 예제로 생각하면 쉽다.
사용자A가 메시지를 보내면 그 메시지는 서버로 간다.
서버는 이미 연결되어 있는 사용자B, 사용자C에게 그 메시지를 바로 전달할 수 있다.
이때 사용자B와 사용자C가 계속 새 메시지가 있는지 물어볼 필요가 없다.
서버가 메시지를 받는 순간 연결된 클라이언트에게 바로 보내면 된다.
이 구조가 실시간 채팅의 핵심이다.
서버는 여러 클라이언트와
WebSocket연결을 유지하면서 실시간으로 메시지를 전달할 수 있다.
채팅에서는 각 사용자의 브라우저가 서버에 연결되고, 서버가 메시지를 중간에서 전달한다.
실시간 통신이 가능하다는 의미
실시간 통신은 데이터가 생긴 뒤 바로 전달되는 통신을 의미한다.
여기서 “바로”는 사용자가 기다리거나 새로고침하지 않아도 화면에 반영되는 흐름을 말한다.
WebSocket은 연결을 유지한다.
그래서 서버와 클라이언트가 계속 통신 가능한 상태로 남아 있다.
이 상태에서는 새 데이터가 생겼을 때 바로 메시지를 보낼 수 있다.
실시간 알림을 예로 들면 서버에 새 알림이 생기는 순간 클라이언트 화면에 알림을 띄울 수 있다.
채팅을 예로 들면 상대방이 보낸 메시지를 거의 즉시 내 화면에 보여 줄 수 있다.
멀티 플레이어 게임을 예로 들면 다른 사용자의 움직임을 빠르게 반영할 수 있다.
WebSocket의 실시간성은 연결을 유지하는 구조에서 나온다.
연결이 유지되어 있기 때문에 새 데이터가 생길 때마다 통신을 다시 준비하지 않고 바로 전달할 수 있다.
동작 방식 핵심 정리
WebSocket통신은 연결을 열고, 유지된 연결 안에서 데이터를 주고받고, 마지막에 연결을 닫는 방식으로 동작한다.
이 흐름은 일반적인HTTP의request-response방식과 다르다.
핵심만 정리하면 다음과 같다.
Opening Handshake로 연결을 시작한다.- 연결이 성공하면
CONNECTED상태가 된다.- 연결이 유지되는 동안
message단위로 데이터를 주고받는다.- 클라이언트와 서버가 서로 데이터를 보낼 수 있다.
- 서버도 클라이언트에게 먼저 데이터를 보낼 수 있다.
- 마지막에는
Closing Handshake로 연결을 종료한다.정리하면
WebSocket은 연결을 계속 유지하면서 클라이언트와 서버가 양방향으로 실시간 데이터를 주고받는 통신 방식이다.
브라우저에서는
JavaScript와HTML5 API를 사용해WebSocket클라이언트를 구현한다.
여기서 클라이언트 구현이란 브라우저가 서버에 연결하고, 서버로 데이터를 보내고, 서버에서 온 데이터를 받는 코드를 작성한다는 뜻이다.
WebSocket클라이언트의 핵심은 어렵게 볼 필요가 없다.
먼저WebSocket객체를 만들어 서버에 연결한다.
그 다음send()로 데이터를 보내고,onmessage로 서버에서 온 데이터를 받는다.
즉, 클라이언트 구현의 핵심 흐름은 연결하기 → 보내기 → 받기 → 연결 상태 처리하기다.
이 흐름만 잡으면 채팅 예제의 코드도 훨씬 쉽게 읽을 수 있다.
WebSocket 객체로 서버에 연결하기
WebSocket연결은new WebSocket()으로 시작한다.
new는 새 객체를 만든다는 뜻이다.
객체는 여러 기능과 값을 묶어 둔 도구라고 이해하면 된다.
WebSocket객체는 서버와 연결하고, 데이터를 보내고, 서버에서 온 데이터를 받을 수 있는 기능을 가진 객체다.
괄호 안에는 접속할 웹 소켓 서버 주소를 넣는다.
// exam01_websocket_connect.html let ws = new WebSocket("ws://localhost:8088/chat"); // 웹 소켓 서버에 연결 요청이 코드는
ws://localhost:8088/chat주소로 웹 소켓 연결을 요청한다.
여기서ws변수에는 서버와 연결하기 위한WebSocket객체가 저장된다.
주소의 마지막/chat은 서버에 만들어 둔 웹 소켓 프로그램의 매핑명이다.
즉, 클라이언트는/chat이라는 연결 지점으로 접속하고, 서버는 그 요청을 받아 웹 소켓 통신을 시작한다.
ws와 wss 프로토콜
WebSocket주소는 일반 웹 주소처럼http://로 시작하지 않는다.
일반 웹 소켓 통신은ws://로 시작한다.
보안이 적용된 웹 소켓 통신은wss://로 시작한다.
ws는 보안 처리가 없는 웹 소켓 통신이다.
wss는 보안 처리가 적용된 웹 소켓 통신이다.
웹 주소에서http와https가 구분되는 것처럼, 웹 소켓 주소에서는ws와wss가 구분된다.
초보자 기준에서는 이렇게 이해하면 된다.
ws는 일반 웹 소켓 연결이다.wss는 보안 웹 소켓 연결이다.- 실제 배포 환경에서는 보안 연결인
wss가 더 중요하게 사용된다.// exam02_websocket_protocol.html let normalWs = new WebSocket("ws://localhost:8088/chat"); // 일반 웹 소켓 연결 let secureWs = new WebSocket("wss://example.com/chat"); // 보안 웹 소켓 연결이 예제는
ws와wss주소 형식의 차이를 보여준다.
실습에서는 보통 로컬 서버를 사용하므로ws://localhost:8088/chat형태를 많이 사용한다.
WebSocket URL 구조
WebSocket URL은 웹 소켓 서버에 접속하기 위한 주소다.
URL은 인터넷에서 특정 자원을 찾기 위한 주소라고 이해하면 된다.
웹 소켓 주소도 어느 서버의 어떤 웹 소켓 프로그램에 연결할지 알려 주는 역할을 한다.
예를 들어 아래 주소를 보자.// exam03_websocket_url.html let ws = new WebSocket("ws://localhost:8088/chat"); // 서버 주소와 매핑명을 포함한 웹 소켓 주소이 주소는 세 부분으로 나누어 볼 수 있다.
ws://는 웹 소켓 통신을 사용한다는 뜻이다.localhost:8088은 접속할 서버 주소와 포트 번호다./chat은 서버에 등록된 웹 소켓 매핑명이다.
localhost는 내 컴퓨터를 뜻한다.
8088은 서버가 열려 있는 포트 번호다.
포트 번호는 한 컴퓨터 안에서 어떤 프로그램으로 들어갈지 구분하는 번호라고 이해하면 된다.
즉,ws://localhost:8088/chat은 “내 컴퓨터에서 실행 중인8088번 서버의/chat웹 소켓 프로그램에 연결하겠다”는 의미다.
send로 데이터 보내기
서버로 데이터를 보낼 때는
send()메서드를 사용한다.
메서드는 객체가 가지고 있는 기능이다.
따라서ws.send()는ws라는WebSocket객체를 통해 서버로 데이터를 보내는 기능이다.
// exam04_websocket_send.html let ws = new WebSocket("ws://localhost:8088/chat"); // 웹 소켓 서버 연결 ws.onopen = function() { ws.send("안녕"); // 연결 성공 후 서버로 메시지 전송 };이 코드는 서버와 연결된 뒤
"안녕"이라는 문자열을 서버로 보낸다.
여기서 중요한 점은send()를 아무 때나 호출하면 안 된다는 것이다.
연결이 열리기 전에 데이터를 보내려고 하면 제대로 전송되지 않을 수 있다.
그래서 위 예제에서는onopen안에서send()를 호출한다.
onopen은 서버와 연결이 성공했을 때 실행되는 이벤트 핸들러다.
이벤트 핸들러는 특정 상황이 발생했을 때 실행되는 함수라고 이해하면 된다.
데이터를 보낼 때는 연결이 열린 뒤send()를 사용한다.
onmessage로 데이터 받기
서버에서 클라이언트로 메시지가 오면
message이벤트가 발생한다.
이 이벤트를 처리하는 속성이onmessage다.
서버에서 받은 데이터는 이벤트 객체의data속성에 들어 있다.
// exam05_websocket_message.html let ws = new WebSocket("ws://localhost:8088/chat"); // 웹 소켓 서버 연결 ws.onmessage = function(e) { console.log(e.data); // 서버에서 받은 메시지 출력 };이 코드에서
e는 이벤트 객체다.
이벤트 객체는 이벤트가 발생했을 때 관련 정보를 담고 있는 객체다.
e.data에는 서버가 보낸 메시지가 들어 있다.
예를 들어 서버가"안녕"이라는 메시지를 보내면e.data값도"안녕"이 된다.
채팅 예제에서는 이 값을 화면에 출력해서 사용자가 받은 메시지를 볼 수 있게 만든다.
보내는 코드는send(), 받는 코드는onmessage다.
이 두 가지가 웹 소켓 클라이언트 코드에서 가장 중요하다.
웹 소켓 관련 이벤트
WebSocket은 연결 상태나 메시지 수신 상태에 따라 이벤트가 발생한다.
이벤트는 특정 일이 생겼을 때 자동으로 실행되는 흐름이다.
웹 소켓에서는 대표적으로open,close,message,error이벤트가 사용된다.
WebSocket은 연결, 메시지 수신, 오류, 연결 종료 상황을 이벤트 핸들러로 처리한다.
onopen,onmessage,onerror,onclose는 각각 다른 상황에서 실행된다.
open,close,message,error이벤트는WebSocket연결 상태와 데이터 수신 상태를 구분해서 처리하기 위해 사용된다.
연결이 열리면open, 연결이 닫히면close, 메시지가 오면message, 오류가 나면error가 발생한다.
주요 이벤트 핸들러 코드
이벤트 핸들러는 이벤트가 발생했을 때 실행할 함수를 등록하는 방식이다.
웹 소켓에서는 연결 성공, 연결 종료, 오류 발생, 메시지 수신 상황을 각각 따로 처리할 수 있다.
// exam06_websocket_events.html let ws = new WebSocket("ws://localhost:8088/chat"); // 웹 소켓 연결 객체 생성 ws.onopen = function(e) { console.log("연결 성공"); // 서버와 접속되면 실행 }; ws.onclose = function(e) { console.log("연결 종료"); // 서버와 접속이 해제되면 실행 }; ws.onerror = function(e) { console.log("오류 발생"); // 웹 소켓 오류가 생기면 실행 }; ws.onmessage = function(e) { console.log(e.data); // 서버에서 메시지가 오면 실행 };이 코드는 웹 소켓에서 자주 사용하는 이벤트 핸들러를 한 번에 보여준다.
연결이 성공하면onopen이 실행된다.
연결이 끊기면onclose가 실행된다.
통신 중 오류가 생기면onerror가 실행된다.
서버에서 메시지가 오면onmessage가 실행된다.
클라이언트 구현 핵심 정리
웹 소켓 클라이언트 구현은 복잡한 구조가 아니다.
서버에 연결할WebSocket객체를 만들고, 그 객체에 필요한 이벤트 처리 코드를 붙이면 된다.
핵심만 정리하면 다음과 같다.
new WebSocket()으로 서버 연결을 요청한다.- 웹 소켓 주소는
ws://서버주소/매핑명형태로 작성한다.- 일반 통신은
ws, 보안 통신은wss를 사용한다.- 서버로 데이터를 보낼 때는
send()를 사용한다.- 서버에서 데이터를 받을 때는
onmessage를 사용한다.- 연결 성공은
onopen, 연결 종료는onclose, 오류는onerror로 처리한다.정리하면 웹 소켓 클라이언트는
WebSocket객체 하나를 중심으로 연결, 송신, 수신, 오류, 종료 상황을 처리하는 구조다.
채팅 클라이언트 예제는 브라우저에서 채팅 화면을 만들고, 사용자가 입력한 메시지를
WebSocket서버로 보내는 코드다.
서버에서 다시 메시지가 오면 그 메시지를 화면에 출력한다.
이 예제는 단순히 버튼을 누르는 화면이 아니다.
WebSocket연결 생성, 메시지 전송, 메시지 수신, 화면 출력이 모두 들어 있다.
그래서 앞에서 배운new WebSocket(),send(),onmessage가 실제로 어떻게 사용되는지 확인하기 좋다.
채팅 클라이언트의 핵심은 사용자가 입력한 값을 서버로 보내고, 서버에서 받은 값을 다시 화면에 누적해서 보여 주는 것이다.
이 흐름을 잡으면 코드가 훨씬 쉽게 읽힌다.
채팅 화면 구조
채팅 화면은 크게 다섯 부분으로 나눌 수 있다.
- 사용자 이름을 입력하는 칸
- 채팅에 참여하는 버튼
- 채팅 메시지가 출력되는 영역
- 메시지를 입력하는 영역
- 메시지를 전송하는 버튼
사용자는 먼저 이름을 입력한다.
그 다음 채팅 참여 버튼을 누르면 서버와WebSocket연결이 만들어진다.
연결된 뒤 메시지를 입력하고 전송 버튼을 누르면 메시지가 서버로 전송된다.
서버에서 메시지가 다시 오면 화면의 채팅 영역에 메시지가 추가된다.
내가 보낸 메시지인지, 다른 사람이 보낸 메시지인지에 따라class값을 다르게 지정한다.
이렇게 하면 화면에서 내 메시지와 다른 사람 메시지를 다르게 꾸밀 수 있다.
전체 코드 흐름
// exam03_chat_client.html <body> <div id="chatt"> <h1>웹 소켓 채팅</h1> <input type="text" id="mid" value="게스트"> <input type="button" value="채팅참여" id="btnJoin"> <br/> <div id="talk"></div> <div id="sendZone"> <textarea id="msg">안녕...</textarea> <input type="button" value="전송" id="btnSend"> </div> </div> <script> function getId(id) { return document.getElementById(id); // id로 화면 요소 찾기 } let data = {}; // 서버로 보낼 데이터를 담을 객체 let ws; // 웹 소켓 연결 객체 let mid = getId("mid"); // 사용자 이름 입력칸 let btnJoin = getId("btnJoin"); // 채팅 참여 버튼 let btnSend = getId("btnSend"); // 전송 버튼 let talk = getId("talk"); // 메시지 출력 영역 let msg = getId("msg"); // 메시지 입력 영역 btnJoin.onclick = function() { ws = new WebSocket("ws://" + location.host + "/chat"); // 현재 서버의 /chat으로 연결 ws.onmessage = function(msg) { let data = JSON.parse(msg.data); // 문자열 메시지를 객체로 변환 let css; if (data.mid == mid.value) { css = "class=me"; // 내가 보낸 메시지 } else { css = "class=other"; // 다른 사람이 보낸 메시지 } let item = `<div ${css}> <span><b>${data.mid}</b></span> [ ${data.date} ]<br/> <span>${data.msg}</span> </div>`; talk.innerHTML += item; // 화면에 메시지 추가 talk.scrollTop = talk.scrollHeight; // 스크롤을 아래로 이동 }; this.style.color = "blue"; // 참여 상태 표시 this.value = "채팅참여중"; // 버튼 글자 변경 }; msg.onkeyup = function(ev) { if (ev.keyCode == 13) { send(); // Enter 키를 누르면 전송 } }; btnSend.onclick = function() { send(); // 전송 버튼을 누르면 전송 }; function send() { if (msg.value.trim() != "") { data.mid = getId("mid").value; // 사용자 이름 저장 data.msg = msg.value; // 메시지 내용 저장 data.date = new Date().toLocaleString(); // 현재 시간 저장 let temp = JSON.stringify(data); // 객체를 문자열로 변환 ws.send(temp); // 서버로 메시지 전송 } msg.value = ""; // 입력칸 비우기 } </script> </body>이 코드는 채팅 클라이언트 전체 흐름을 보여 준다.
처음에는 화면 요소를 변수에 저장한다.
그 다음 채팅 참여 버튼을 눌렀을 때WebSocket연결을 만든다.
메시지를 받으면onmessage가 실행되고, 메시지를 보낼 때는send()함수가 실행된다.
코드에서 헷갈리기 쉬운 변수 이름
이 코드에서 초보자가 헷갈리기 쉬운 부분은 변수 이름이다.
바깥에 있는msg는 메시지를 입력하는textarea를 가리킨다.
반면ws.onmessage = function(msg)에서 괄호 안의msg는 서버에서 메시지를 받았을 때 전달되는 이벤트 객체다.
이 둘은 이름은 같지만 역할이 다르다.
또한 바깥의data는 서버로 보낼 데이터를 담는 객체다.
반면onmessage안에서JSON.parse(msg.data)로 만든data는 서버에서 받은 문자열을 객체로 바꾼 값이다.
이 코드에서는 이름이 같아서 처음 보면 헷갈릴 수 있지만, 위치가 다르기 때문에 각각 다른 값으로 동작한다.
이 단계에서는 이렇게 이해하면 된다.
- 바깥의
msg는 입력창이다.onmessage안의msg는 서버에서 온 메시지 이벤트다.- 바깥의
data는 보낼 데이터다.onmessage안의data는 받은 데이터다.즉, 같은 이름이 보여도 코드 위치에 따라 역할이 다르므로, 입력값인지 수신값인지 구분해서 읽어야 한다.
화면 요소를 변수에 저장하는 이유
코드에서
getId()함수는 화면 요소를 쉽게 찾기 위해 만든 함수다.
document.getElementById()는 화면에서 특정id를 가진 요소를 찾는 기능이다.
예를 들어getId("mid")는id가mid인 입력칸을 찾는다.
찾은 화면 요소는 변수에 저장한다.
이렇게 하면 같은 요소를 여러 번 사용할 때 코드를 짧고 읽기 쉽게 만들 수 있다.
// exam04_get_element.html function getId(id) { return document.getElementById(id); // id로 화면 요소 찾기 } let mid = getId("mid"); // 사용자 이름 입력칸 저장 let msg = getId("msg"); // 메시지 입력 영역 저장이 예제에서
mid는 사용자 이름 입력칸을 가리킨다.
msg는 메시지 입력 영역을 가리킨다.
이후 코드에서는mid.value,msg.value처럼 입력된 값을 꺼내 사용할 수 있다.
채팅 참여 버튼을 눌렀을 때의 흐름
채팅 참여 버튼을 누르면
btnJoin.onclick에 등록된 함수가 실행된다.
onclick은 사용자가 버튼을 클릭했을 때 실행할 동작을 지정하는 이벤트 핸들러다.
이 함수 안에서 가장 중요한 코드는new WebSocket("ws://" + location.host + "/chat")이다.
이 코드는 현재 접속 중인 서버의/chat주소로WebSocket연결을 요청한다.
location.host는 현재 브라우저가 접속한 서버 주소와 포트 번호를 의미한다.
예를 들어 현재 주소가localhost:8088이라면,"ws://" + location.host + "/chat"은ws://localhost:8088/chat이 된다.
// exam05_join_chat.html btnJoin.onclick = function() { ws = new WebSocket("ws://" + location.host + "/chat"); // 현재 서버의 /chat으로 연결 this.style.color = "blue"; // 버튼 글자색 변경 this.value = "채팅참여중"; // 버튼 문구 변경 };이 코드는 채팅 참여 버튼을 눌렀을 때 서버와 연결하고, 버튼 상태를 참여 중으로 바꾼다.
즉, 버튼 클릭은 단순한 화면 변경이 아니라 서버와 채팅 연결을 시작하는 동작이다.
서버에서 메시지를 받는 흐름
서버에서 메시지가 오면
ws.onmessage가 실행된다.
이때 서버에서 받은 메시지는msg.data에 들어 있다.
채팅 예제에서는 서버에서 받은 데이터가JSON문자열 형태라고 보고 처리한다.
JSON은 여러 값을 문자열 형태로 묶어 주고받기 위한 데이터 형식이다.
채팅 메시지에는 사용자 이름, 메시지 내용, 보낸 시간이 함께 필요하다.
이런 여러 값을 하나로 묶어 보내기 위해JSON을 사용한다.
// exam06_receive_message.html ws.onmessage = function(msg) { let data = JSON.parse(msg.data); // 받은 JSON 문자열을 객체로 변환 console.log(data.mid); // 보낸 사람 이름 확인 console.log(data.msg); // 메시지 내용 확인 console.log(data.date); // 보낸 시간 확인 };
JSON.parse()는 문자열을 객체로 바꾸는 기능이다.
서버에서 받은 메시지는 문자열이기 때문에, 화면에 필요한 값을 꺼내 쓰려면 객체로 바꿔야 한다.
객체로 바꾸면data.mid,data.msg,data.date처럼 각각의 값을 꺼낼 수 있다.
내 메시지와 다른 사람 메시지를 구분하는 흐름
채팅 화면에서는 내가 보낸 메시지와 다른 사람이 보낸 메시지를 다르게 보여 줄 수 있다.
이 예제에서는 서버에서 받은 메시지의mid값과 현재 입력칸의mid.value값을 비교한다.
두 값이 같으면 내가 보낸 메시지로 본다.
그래서class=me를 적용한다.
두 값이 다르면 다른 사람이 보낸 메시지로 본다.
그래서class=other를 적용한다.
// exam07_message_owner.html let css; if (data.mid == mid.value) { css = "class=me"; // 내가 보낸 메시지 } else { css = "class=other"; // 다른 사람이 보낸 메시지 }이 코드는 메시지의 작성자를 기준으로 화면 표시 방식을 나누는 코드다.
class는 화면 요소에 적용할 스타일 이름이다.
따라서me와other에 서로 다른 스타일을 주면 내 메시지와 다른 사람 메시지를 다르게 보이게 만들 수 있다.
메시지를 화면에 출력하는 흐름
서버에서 받은 메시지는 화면에 누적해서 출력된다.
이 예제에서는 메시지 하나를HTML문자열로 만든 뒤,talk.innerHTML에 추가한다.
innerHTML은 특정 화면 요소 안의HTML내용을 의미한다.
talk.innerHTML += item은 기존 채팅 내용 뒤에 새 메시지item을 추가한다는 뜻이다.
// exam08_print_message.html let item = `<div ${css}> <span><b>${data.mid}</b></span> [ ${data.date} ]<br/> <span>${data.msg}</span> </div>`; // 화면에 추가할 메시지 HTML 생성 talk.innerHTML += item; // 기존 채팅 내용 뒤에 새 메시지 추가 talk.scrollTop = talk.scrollHeight; // 스크롤을 가장 아래로 이동
talk.scrollTop = talk.scrollHeight는 채팅창 스크롤을 아래로 내리는 코드다.
새 메시지가 계속 아래에 추가되기 때문에, 최신 메시지를 바로 보이게 하려면 스크롤을 아래로 이동해야 한다.
메시지를 서버로 보내는 흐름
메시지를 보낼 때는
send()함수가 실행된다.
이 함수는 전송할 메시지가 비어 있는지 먼저 확인한다.
그 다음 사용자 이름, 메시지 내용, 현재 시간을 객체에 담는다.
그 객체를JSON.stringify()로 문자열로 바꾼 뒤ws.send()로 서버에 보낸다.
여기서JSON.stringify()는 객체를JSON문자열로 바꾸는 기능이다.
WebSocket으로 보낼 데이터를 문자열 형태로 정리하기 위해 사용한다.
// exam09_send_message.html function send() { if (msg.value.trim() != "") { data.mid = getId("mid").value; // 사용자 이름 저장 data.msg = msg.value; // 메시지 내용 저장 data.date = new Date().toLocaleString(); // 현재 시간 저장 let temp = JSON.stringify(data); // 객체를 JSON 문자열로 변환 ws.send(temp); // 서버로 메시지 전송 } msg.value = ""; // 입력칸 비우기 }
trim()은 문자열 양쪽의 공백을 제거하는 기능이다.
msg.value.trim() != ""는 공백만 있는 메시지는 보내지 않겠다는 뜻이다.
메시지를 보낸 뒤에는msg.value = ""로 입력칸을 비운다.
그래야 다음 메시지를 새로 입력하기 편하다.
엔터 키와 전송 버튼 처리
이 예제에서는 메시지를 보내는 방법이 두 가지다.
하나는Enter키를 누르는 방식이다.
다른 하나는 전송 버튼을 클릭하는 방식이다.
두 방식 모두 마지막에는 같은send()함수를 호출한다.
이렇게 하면 메시지 전송 로직을 한곳에서 관리할 수 있다.
// exam10_send_event.html msg.onkeyup = function(ev) { if (ev.keyCode == 13) { send(); // Enter 키를 누르면 메시지 전송 } }; btnSend.onclick = function() { send(); // 전송 버튼을 누르면 메시지 전송 };
onkeyup은 키보드에서 키를 눌렀다가 뗐을 때 실행되는 이벤트 핸들러다.
ev.keyCode == 13은 눌린 키가Enter키인지 확인하는 조건이다.
전송 버튼을 클릭할 때도send()를 호출하므로, 두 입력 방식의 결과는 같다.
채팅 클라이언트 핵심 정리
채팅 클라이언트 예제는 여러 코드가 섞여 있지만 흐름은 단순하다.
화면 요소를 찾고, 서버에 연결하고, 메시지를 보내고, 받은 메시지를 화면에 출력한다.
핵심만 정리하면 다음과 같다.
getId()로 화면 요소를 찾는다.- 채팅 참여 버튼을 누르면
WebSocket연결을 만든다.- 서버에서 메시지가 오면
onmessage가 실행된다.- 받은
JSON문자열은JSON.parse()로 객체로 바꾼다.- 내가 보낸 메시지와 다른 사람 메시지를
class로 구분한다.- 전송할 메시지는
JSON.stringify()로 문자열로 바꾼다.ws.send()로 서버에 메시지를 보낸다.Enter키와 전송 버튼은 모두send()함수를 호출한다.정리하면 채팅 클라이언트는 화면 입력값을
JSON문자열로 만들어 서버에 보내고, 서버에서 받은JSON문자열을 다시 화면에 출력하는 구조다.
WebSocket채팅이 동작하려면 클라이언트 코드만 있어서는 안 된다.
브라우저가ws://localhost:8088/chat으로 연결을 요청하면, 서버 쪽에도 이 요청을 받아 줄WebSocket서버 프로그램이 있어야 한다.
Spring Boot에서는WebSocket서버를 구현할 수 있다.
서버는 클라이언트가 접속했을 때 연결을 받고, 클라이언트가 메시지를 보냈을 때 그 메시지를 처리하고, 연결이 끊겼을 때 정리 작업을 한다.
서버 구현의 핵심은 클라이언트의 연결 요청을 받을 주소를 만들고, 연결·메시지 수신·연결 종료 상황을 각각 메서드로 처리하는 것이다.
이 구조를 이해하면 뒤에 나오는 채팅 서버 예제도 쉽게 읽을 수 있다.
Spring Boot에서 WebSocket을 사용한다는 의미
Spring Boot는 웹 애플리케이션을 빠르게 만들 수 있도록 도와주는Java기반 프레임워크다.
프레임워크는 개발자가 자주 사용하는 구조와 기능을 미리 준비해 둔 틀이라고 이해하면 된다.
WebSocket서버를 직접 처음부터 만들려면 연결 처리, 메시지 처리, 종료 처리 같은 부분을 모두 세세하게 구현해야 한다.
하지만Spring Boot에서는WebSocket관련 기능을 추가하고, 정해진 어노테이션을 사용하면 서버 프로그램을 더 쉽게 만들 수 있다.
여기서 어노테이션은 코드 위에 붙여서 역할을 표시하는 문법이다.
예를 들어@ServerEndpoint는 이 클래스가WebSocket서버의 연결 지점이라는 뜻을 나타낸다.
Spring환경에서도WebSocket을 활용할 수 있고, 필요하면SockJS같은 보조 기술과 함께 사용할 수 있다.
SockJS는 브라우저나 연결 환경이 제한적일 때WebSocket을 더 안정적으로 사용할 수 있도록 도와주는 기술이다.
WebSocket 의존성 설정
의존성은 프로젝트에서 사용할 외부 기능을 추가하는 설정이다.
Spring Boot프로젝트에서WebSocket기능을 사용하려면spring-boot-starter-websocket의존성을 추가해야 한다.
이 의존성이 있어야Spring Boot가 제공하는WebSocket관련 기능을 사용할 수 있다.
의존성이 없으면WebSocket서버를 만들기 위한 클래스나 설정을 제대로 사용할 수 없다.
// exam_build.gradle dependencies { implementation 'org.springframework.boot:spring-boot-starter-websocket' // 웹 소켓 기능 추가 }이 설정은
Spring Boot프로젝트에WebSocket기능을 추가한다.
implementation은 이 프로젝트에서 해당 라이브러리를 사용하겠다는 의미다.
Spring WebSocket Messaging
Spring은 단순한WebSocket연결뿐 아니라 메시징 구조도 지원한다.
메시징 구조는 단순히 데이터를 주고받는 것에서 더 나아가, 메시지를 목적지에 맞게 처리하는 구조를 의미한다.
예를 들어 채팅방이 여러 개라면 모든 메시지를 모든 사용자에게 보내면 안 된다.
특정 채팅방에 있는 사용자에게만 메시지를 보내야 한다.
이런 구조를 더 체계적으로 만들 때STOMP같은 메시징 방식이 함께 사용될 수 있다.
Spring WebSocket Messaging은Servlet기반WebSocket애플리케이션을 만들고,SockJS,STOMP와 함께 사용할 수 있는 기능을 제공한다.
이번 예제는 먼저 기본WebSocket연결과 메시지 처리 흐름을 이해하는 데 초점을 둔다.
ServerEndpoint 클래스가 필요한 이유
클라이언트가
WebSocket으로 서버에 연결하려면 서버 쪽에 연결을 받을 지점이 있어야 한다.
이 연결 지점을Endpoint라고 한다.
Endpoint는 클라이언트가 접속할 수 있는 서버의 입구라고 이해하면 된다.
@ServerEndpoint는 해당 클래스가WebSocket서버의 입구 역할을 한다는 표시다.
괄호 안에는 클라이언트가 접속할 주소를 적는다.
예를 들어@ServerEndpoint(value="/chat")이면 클라이언트는/chat주소로 접속한다.
// examWebSocketServer.java import jakarta.websocket.server.ServerEndpoint; import org.springframework.stereotype.Service; @Service @ServerEndpoint(value="/chat") public class WebSocketServer { // /chat 주소로 들어오는 웹 소켓 연결을 처리하는 클래스 }이 코드에서
/chat은 클라이언트가 접속할 매핑명이다.
클라이언트 코드의ws://localhost:8088/chat에서 마지막/chat과 서버의@ServerEndpoint(value="/chat")가 서로 연결된다.
ServerEndpointExporter 빈 등록이 필요한 이유
Spring에서 빈은Spring이 직접 만들고 관리하는 객체다.
기본적으로Spring빈은 싱글톤으로 관리된다.
싱글톤은 하나의 객체를 만들어 두고 여러 곳에서 함께 사용하는 방식이다.
보통@Service,@Component같은 어노테이션이 붙은 클래스는Spring이 객체로 만들어 관리한다.
그래서 다른 객체가 필요할 때@Autowired같은 방식으로 의존 객체를 주입받을 수 있다.
의존 객체 주입은 필요한 객체를 직접 만들지 않고Spring에게 받아 쓰는 방식이라고 이해하면 된다.
하지만@ServerEndpoint가 붙은 클래스는 일반적인Spring빈 생성 방식과 다르게 동작할 수 있다.
웹 소켓 연결이 생성될 때마다@ServerEndpoint클래스의 인스턴스가 만들어지고 관리되기 때문이다.
인스턴스는 실제로 메모리에 만들어진 객체라고 이해하면 된다.
이 차이 때문에@ServerEndpoint클래스 안에서@Autowired가 붙은 멤버들이 기대한 대로 초기화되지 않을 수 있다.
그래서 클라이언트로부터 웹 소켓 접속 요청이 들어올 때마다@ServerEndpoint클래스가 웹 소켓 엔드포인트로 동작할 수 있도록 초기화 설정이 필요하다.
이때 사용하는 설정이ServerEndpointExporter빈 등록이다.
ServerEndpointExporter는@ServerEndpoint가 붙은 클래스를 찾아서WebSocket서버 엔드포인트로 등록해 주는 역할을 한다.
// examWebSocketConfig.java import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Component public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); // @ServerEndpoint 클래스를 웹 소켓 엔드포인트로 등록 } }이 설정 파일은
WebSocket서버 프로그램을 사용할 수 있도록 준비하는 코드다.
@Component는 이 클래스를Spring이 관리하는 객체로 등록하겠다는 의미다.
@Bean은 메서드가 반환하는 객체를Spring빈으로 등록하겠다는 의미다.
WebSocket 서버 클래스의 기본 구조
WebSocket서버 클래스는 연결 상황에 따라 실행될 메서드를 가지고 있어야 한다.
대표적으로 연결 성공, 메시지 수신, 연결 종료 상황을 처리한다.
이때 사용하는 어노테이션은 세 가지다.
@OnOpen은 클라이언트가 접속했을 때 실행된다.@OnMessage는 클라이언트가 메시지를 보냈을 때 실행된다.@OnClose는 클라이언트 연결이 종료되었을 때 실행된다.// examWebSocketBasicServer.java import jakarta.websocket.OnClose; import jakarta.websocket.OnMessage; import jakarta.websocket.OnOpen; import jakarta.websocket.Session; import jakarta.websocket.server.ServerEndpoint; import org.springframework.stereotype.Service; @Service @ServerEndpoint(value="/chat") public class WebSocketBasicServer { @OnOpen public void onOpen(Session s) { System.out.println("접속 시작"); // 클라이언트가 연결되면 실행 } @OnMessage public void onMessage(String msg, Session session) throws Exception { System.out.println(msg); // 클라이언트가 보낸 메시지를 받으면 실행 } @OnClose public void onClose(Session s) { System.out.println("접속 종료"); // 클라이언트 연결이 닫히면 실행 } }이 코드는
WebSocket서버 클래스의 기본 형태를 보여 준다.
클라이언트가/chat으로 접속하면onOpen()이 실행된다.
클라이언트가 메시지를 보내면onMessage()가 실행된다.
연결이 끊기면onClose()가 실행된다.
Session 객체의 의미
Session은 클라이언트와 서버 사이의 연결 정보를 나타내는 객체다.
객체는 관련된 값과 기능을 묶어 둔 코드 단위라고 이해하면 된다.
채팅으로 생각하면 사용자 한 명이 서버에 접속할 때 하나의 연결이 만들어진다.
이 연결 정보를 서버에서 다루기 위해 사용하는 것이Session이다.
여러 사용자가 접속하면 여러 개의Session이 생긴다.
서버는 이Session을 이용해 특정 클라이언트에게 메시지를 보낼 수 있다.
채팅 서버에서는 접속한 사용자들의Session을 모아 두었다가, 한 사용자가 메시지를 보내면 모든Session에 메시지를 다시 보내는 방식으로 동작한다.
즉,Session은 서버가 연결된 클라이언트를 구분하고 메시지를 보내기 위해 필요한 연결 정보다.
서버 구현 핵심 정리
Spring Boot에서WebSocket서버를 구현하려면 먼저 의존성을 추가해야 한다.
그 다음ServerEndpointExporter를 빈으로 등록해@ServerEndpoint클래스가 웹 소켓 엔드포인트로 동작할 수 있게 해야 한다.
마지막으로 실제 서버 클래스에서 연결, 메시지, 종료 상황을 처리한다.
핵심만 정리하면 다음과 같다.
spring-boot-starter-websocket의존성을 추가한다.@ServerEndpoint는 웹 소켓 서버의 접속 주소를 만든다.ServerEndpointExporter는@ServerEndpoint클래스를 웹 소켓 엔드포인트로 등록한다.@OnOpen은 클라이언트 접속 시 실행된다.@OnMessage는 메시지 수신 시 실행된다.@OnClose는 연결 종료 시 실행된다.Session은 클라이언트와 서버 사이의 연결 정보를 나타낸다.정리하면
Spring Boot웹 소켓 서버는 클라이언트가 접속할 주소를 만들고, 연결·메시지 수신·종료 상황을 각각 메서드로 처리하는 구조다.
채팅 서버 예제는 클라이언트가 보낸 메시지를 서버가 받은 뒤, 현재 접속 중인 모든 클라이언트에게 다시 보내는 구조다.
겉으로 보면 채팅 메시지가 여러 브라우저에 동시에 보이는 기능이지만, 내부 흐름은 접속자 저장, 메시지 수신, 전체 전송, 접속 종료 정리로 나뉜다.
클라이언트는ws://localhost:8088/chat주소로 서버에 접속한다.
서버에서는@ServerEndpoint(value="/chat")이 붙은 클래스가 이 연결을 받는다.
즉, 클라이언트의/chat연결 요청과 서버의/chat매핑이 서로 맞아야 통신이 시작된다.
채팅 서버의 핵심은 접속한 클라이언트의Session을 저장해 두고, 한 클라이언트가 보낸 메시지를 저장된 모든Session에게 다시 보내는 것이다.
이 구조를 이해하면 실시간 채팅이 왜 여러 사용자에게 동시에 보이는지 알 수 있다.
전체 서버 코드 흐름
// examWebSocketChatt.java import jakarta.websocket.OnClose; import jakarta.websocket.OnMessage; import jakarta.websocket.OnOpen; import jakarta.websocket.Session; import jakarta.websocket.server.ServerEndpoint; import org.springframework.stereotype.Service; import java.util.Collections; import java.util.HashSet; import java.util.Set; @Service @ServerEndpoint(value="/chat") public class WebSocketChatt { private static Set<Session> clientSet = Collections.synchronizedSet(new HashSet<Session>()); // 접속한 클라이언트 세션 저장 @OnOpen public void onOpen(Session s) { if (!clientSet.contains(s)) { clientSet.add(s); // 새 세션 추가 System.out.println("[세션 오픈] " + s); // 접속 로그 출력 } else { System.out.println("이미 연결된 세션임!!!"); // 중복 접속 확인 } } @OnMessage public void onMessage(String msg, Session session) throws Exception { System.out.println("[수신 메시지] " + msg); // 클라이언트가 보낸 메시지 출력 for (Session s : clientSet) { System.out.println("[송신 메시지] " + msg); // 전송할 메시지 출력 s.getBasicRemote().sendText(msg); // 접속 중인 클라이언트에게 메시지 전송 } } @OnClose public void onClose(Session s) { System.out.println("[세션 종료] " + s); // 종료 로그 출력 clientSet.remove(s); // 종료된 세션 제거 } }이 코드는
/chat주소로 들어오는WebSocket연결을 처리하는 서버 코드다.
클라이언트가 접속하면onOpen()이 실행된다.
클라이언트가 메시지를 보내면onMessage()가 실행된다.
클라이언트 연결이 종료되면onClose()가 실행된다.
서버는 접속한 클라이언트의Session을clientSet에 저장한다.
그리고 메시지를 받으면clientSet에 들어 있는 모든Session을 하나씩 꺼내 같은 메시지를 다시 보낸다.
그래서 한 사람이 보낸 채팅 메시지가 다른 사람의 화면에도 나타난다.
ServerEndpoint와 chat 매핑
@ServerEndpoint(value="/chat")은 이 클래스가/chat주소의 웹 소켓 서버 역할을 한다는 뜻이다.
클라이언트가ws://localhost:8088/chat으로 연결하면 이 클래스가 연결을 처리한다.
// examServerEndpointMapping.java @Service @ServerEndpoint(value="/chat") public class WebSocketChatt { // /chat 주소로 들어오는 웹 소켓 연결을 처리 }클라이언트 주소와 서버 매핑은 서로 맞아야 한다.
클라이언트가/chat으로 접속하는데 서버가/socket으로 열려 있으면 연결되지 않는다.
이 관계는 아래처럼 이해하면 된다.
- 클라이언트 연결 주소:
ws://localhost:8088/chat- 서버 매핑 주소:
@ServerEndpoint(value="/chat")마지막 경로인
/chat이 서로 맞아야 클라이언트와 서버가 연결된다.
접속한 클라이언트를 Session으로 저장하는 이유
Session은 클라이언트와 서버 사이의 연결 정보를 나타내는 객체다.
채팅에서는 사용자 한 명이 서버에 접속할 때 하나의Session이 만들어진다고 이해하면 된다.
서버가 메시지를 여러 사용자에게 보내려면 누가 접속해 있는지 알아야 한다.
그래서 접속한 사용자의Session을 모아 둔다.
이 예제에서는clientSet이 접속자 목록 역할을 한다.
// examClientSet.java private static Set<Session> clientSet = Collections.synchronizedSet(new HashSet<Session>()); // 접속 세션 목록
Set은 중복을 허용하지 않는 자료구조다.
자료구조는 데이터를 어떤 방식으로 저장할지 정한 틀이다.
Set을 사용하면 같은Session이 중복으로 들어가는 것을 막을 수 있다.
여기서static이 붙은 이유도 중요하다.
static은 객체마다 따로 생기는 값이 아니라, 클래스 차원에서 함께 사용하는 값이라는 의미다.
@ServerEndpoint클래스는 웹 소켓 연결이 만들어질 때마다 인스턴스가 새로 생성될 수 있다.
인스턴스는 실제로 메모리에 만들어진 객체를 의미한다.
만약 접속자 목록이 각 인스턴스마다 따로 만들어지면, 서버가 전체 접속자를 한곳에서 관리하기 어렵다.
채팅 서버는 한 사용자가 보낸 메시지를 모든 접속자에게 다시 보내야 한다.
그래서 모든 연결이 같은clientSet을 함께 보도록static으로 선언한다.
즉,static clientSet은 접속한 모든 클라이언트의Session을 하나의 공통 목록으로 관리하기 위한 구조다.
HashSet은Set의 한 종류다.
중복 없는 목록을 저장할 때 사용할 수 있다.
Collections.synchronizedSet()은 여러 클라이언트가 동시에 접속하거나 메시지를 보낼 때 목록이 꼬이지 않도록 동기화된Set으로 감싸는 기능이다.
여기서 동기화는 여러 작업이 동시에 접근할 때 충돌이 나지 않도록 순서를 맞추는 처리라고 이해하면 된다.
채팅 서버는 여러 사용자가 동시에 접속할 수 있으므로 접속자 목록을 안전하게 관리해야 한다.
즉,clientSet은 현재 서버에 접속해 있는 클라이언트들의 연결 목록이다.
OnOpen에서 접속 세션 추가하기
@OnOpen은 클라이언트가 웹 소켓 서버에 접속했을 때 실행된다.
채팅 화면에서 사용자가 채팅 참여 버튼을 누르면 클라이언트는/chat으로 연결을 요청하고, 서버에서는onOpen()이 실행된다.
// examOnOpen.java @OnOpen public void onOpen(Session s) { if (!clientSet.contains(s)) { clientSet.add(s); // 접속한 세션을 목록에 추가 System.out.println("[세션 오픈] " + s); // 접속 로그 출력 } else { System.out.println("이미 연결된 세션임!!!"); // 이미 목록에 있는 세션 } }
contains()는 목록 안에 해당 값이 있는지 확인하는 기능이다.
clientSet.contains(s)는clientSet안에 현재 접속한Session이 이미 있는지 확인한다.
!clientSet.contains(s)는 아직 이Session이 목록에 없다는 뜻이다.
따라서 새로 접속한 클라이언트라면clientSet.add(s)로 목록에 추가한다.
이 처리가 필요한 이유는 간단하다.
서버가 나중에 메시지를 보낼 때, 이 목록을 보고 누구에게 메시지를 보낼지 결정하기 때문이다.
채팅 서버에서 접속자 목록을 저장하는 일은 메시지를 보낼 대상을 준비하는 일이다.
OnMessage에서 메시지 받기
@OnMessage는 클라이언트가 서버로 메시지를 보냈을 때 실행된다.
클라이언트 코드에서ws.send(temp)가 실행되면 서버의onMessage()가 호출된다.
// examOnMessageReceive.java @OnMessage public void onMessage(String msg, Session session) throws Exception { System.out.println("[수신 메시지] " + msg); // 클라이언트가 보낸 메시지 확인 }
msg에는 클라이언트가 보낸 메시지가 들어온다.
채팅 클라이언트 예제에서는 사용자 이름, 메시지 내용, 시간을JSON.stringify()로 문자열로 바꿔 보냈다.
따라서 서버가 받는msg도JSON문자열이다.
예를 들어 클라이언트가 아래와 같은 문자열을 보낼 수 있다.// 출력결과 // {"mid":"게스트","msg":"안녕","date":"2026. 5. 4."}서버는 이 문자열을 받아 로그로 출력한다.
이 예제에서는 서버가 메시지 내용을 분석하지 않고, 받은 문자열 그대로 다시 클라이언트들에게 보낸다.
모든 클라이언트에게 메시지 보내기
채팅에서는 한 사람이 보낸 메시지를 접속 중인 다른 사람들도 볼 수 있어야 한다.
그래서 서버는 받은 메시지를 현재 접속 중인 모든 클라이언트에게 다시 보낸다.
이때 사용하는 구조가 반복문이다.
반복문은 같은 작업을 여러 번 실행할 때 사용하는 문법이다.
여기서는clientSet에 저장된Session을 하나씩 꺼내 메시지를 보내기 위해 사용한다.
// examBroadcastMessage.java @OnMessage public void onMessage(String msg, Session session) throws Exception { System.out.println("[수신 메시지] " + msg); // 받은 메시지 출력 for (Session s : clientSet) { System.out.println("[송신 메시지] " + msg); // 보낼 메시지 출력 s.getBasicRemote().sendText(msg); // 각 클라이언트에게 메시지 전송 } }
for (Session s : clientSet)은clientSet에 저장된Session을 하나씩 꺼내겠다는 뜻이다.
꺼낸Session은 반복문 안에서s라는 이름으로 사용한다.
s.getBasicRemote().sendText(msg)는 해당Session의 클라이언트에게 문자열 메시지를 보내는 코드다.
즉, 서버가 클라이언트 브라우저로 메시지를 보내는 부분이다.
이 예제에서는 보낸 사람을 포함한 모든 접속자에게 메시지를 보낸다.
그래서 내가 보낸 메시지도 내 화면에 다시 출력된다.
클라이언트는 그 메시지를 받은 뒤mid값을 비교해서 내 메시지인지 다른 사람 메시지인지 구분한다.
브로드캐스트란 무엇인가
브로드캐스트는 하나의 메시지를 여러 대상에게 보내는 방식이다.
채팅 서버에서는 한 클라이언트가 보낸 메시지를 접속 중인 모든 클라이언트에게 보내므로 브로드캐스트 구조라고 볼 수 있다.
쉽게 말하면 한 사람이 채팅방에 말을 하면, 같은 채팅방에 있는 모든 사람이 그 말을 듣는 것과 같다.
서버는 중간에서 메시지를 받고, 접속자 목록을 기준으로 모두에게 전달한다.
이 예제의 브로드캐스트 흐름은 다음과 같다.
- 사용자 한 명이 메시지를 보낸다.
- 서버의
onMessage()가 메시지를 받는다.- 서버는
clientSet에 저장된Session을 하나씩 꺼낸다.- 각
Session에게 같은 메시지를 보낸다.- 각 클라이언트의
onmessage가 실행되어 화면에 메시지가 출력된다.브로드캐스트는 실시간 채팅에서 한 사람의 메시지를 여러 사용자 화면에 동시에 보여 주기 위한 핵심 구조다.
OnClose에서 접속 세션 제거하기
@OnClose는 클라이언트와 서버의 연결이 종료되었을 때 실행된다.
사용자가 브라우저를 닫거나 연결이 끊기면 해당 클라이언트의Session은 더 이상 사용할 수 없다.
// examOnClose.java @OnClose public void onClose(Session s) { System.out.println("[세션 종료] " + s); // 종료된 세션 출력 clientSet.remove(s); // 접속 목록에서 세션 제거 }
remove()는 목록에서 특정 값을 제거하는 기능이다.
clientSet.remove(s)는 종료된Session을 접속자 목록에서 제거한다.
이 처리가 필요한 이유는 서버가 끊어진 클라이언트에게 메시지를 보내려고 하지 않게 하기 위해서다.
만약 종료된Session을 계속 목록에 두면, 서버는 존재하지 않는 연결에도 메시지를 보내려고 할 수 있다.
그러면 오류가 생기거나 불필요한 처리가 늘어난다.
따라서 연결이 종료되면 반드시 접속자 목록에서도 해당Session을 제거해야 한다.
채팅 서버 실행 흐름
채팅 서버의 전체 실행 흐름은 접속, 저장, 수신, 전송, 종료로 정리할 수 있다.
이 흐름은 클라이언트 코드와 연결해서 보면 더 쉽게 이해된다.
클라이언트가 채팅 참여 버튼을 누르면new WebSocket("ws://" + location.host + "/chat")이 실행된다.
그러면 서버의@OnOpen메서드가 실행되고, 서버는 해당 클라이언트의Session을 저장한다.
클라이언트가 메시지를 보내면ws.send(temp)가 실행된다.
그러면 서버의@OnMessage메서드가 실행되고, 서버는 받은 메시지를 모든Session에게 다시 보낸다.
클라이언트가 연결을 종료하면 서버의@OnClose메서드가 실행된다.
그러면 서버는 종료된Session을 접속자 목록에서 제거한다.
// 출력결과 // [세션 오픈] Session 정보 // [수신 메시지] {"mid":"게스트","msg":"안녕","date":"2026. 5. 4."} // [송신 메시지] {"mid":"게스트","msg":"안녕","date":"2026. 5. 4."} // [세션 종료] Session 정보실제
Session정보는 실행 환경마다 다르게 출력된다.
중요한 것은 접속, 수신, 송신, 종료 흐름이 순서대로 확인된다는 점이다.
채팅 서버 핵심 정리
채팅 서버 예제는 코드가 길어 보이지만 역할은 명확하다.
접속한 클라이언트를 저장하고, 메시지를 받으면 모든 클라이언트에게 다시 보내고, 연결이 종료되면 목록에서 제거한다.
핵심만 정리하면 다음과 같다.
@ServerEndpoint(value="/chat")은/chat웹 소켓 연결을 처리한다.clientSet은 접속 중인 클라이언트의Session목록이다.@OnOpen은 새 클라이언트가 접속했을 때 실행된다.clientSet.add(s)는 접속한 클라이언트를 목록에 저장한다.@OnMessage는 클라이언트가 메시지를 보냈을 때 실행된다.s.getBasicRemote().sendText(msg)는 클라이언트에게 메시지를 보낸다.@OnClose는 연결이 종료되었을 때 실행된다.clientSet.remove(s)는 종료된 클라이언트를 목록에서 제거한다.정리하면 채팅 서버는 접속자의
Session을 관리하면서, 받은 메시지를 모든 접속자에게 다시 보내는 브로드캐스트 구조로 동작한다.
WebSocket연결이 성공했는지는 브라우저 개발자 도구에서 확인할 수 있다.
채팅 화면이 보인다고 해서 무조건WebSocket연결이 성공한 것은 아니다.
화면은 열렸지만 서버와의 연결이 실패했을 수도 있다.
그래서 실습에서는 채팅 화면뿐만 아니라 개발자 도구의Network탭을 함께 확인해야 한다.
Network탭은 브라우저가 서버와 어떤 요청을 주고받았는지 보여 주는 곳이다.
여기서/chat연결 요청이 보이고, 상태 코드가101이면WebSocket연결 전환이 성공한 것이다.
WebSocket연결 확인의 핵심은Network탭에서Status Code가101 Switching Protocols인지 확인하는 것이다.
101은 처음HTTP로 시작한 연결이WebSocket통신으로 전환되었다는 뜻이다.
Network 탭에서 chat 요청 확인하기
WebSocket연결 요청은 채팅 참여 버튼을 누르는 순간 발생한다.
따라서 개발자 도구의Network탭을 먼저 열어 둔 뒤 채팅 참여 버튼을 누르는 것이 좋다.
그래야 브라우저가 서버로 보낸/chat연결 요청을 놓치지 않고 확인할 수 있다.
Network탭은 웹 페이지가 서버와 주고받은 요청 목록을 보여 준다.
일반 웹 페이지 요청, 이미지 요청,AJAX요청,WebSocket연결 요청 등을 확인할 수 있다.
WebSocket실습에서는 이 목록에서chat요청을 찾으면 된다.
Network탭에서chat요청의 상태 코드가101이고 타입이websocket이면WebSocket연결이 성공한 것이다.
오른쪽 화면에chat요청이 보이고,Status가101,Type이websocket으로 표시되어 있다.
Status Code 101의 의미
Status Code는 서버가 요청에 대해 어떤 결과를 돌려주었는지 알려 주는 숫자다.
일반 웹 페이지에서는200이 성공을 의미하는 경우가 많다.
하지만WebSocket연결에서는101이 중요하다.
101 Switching Protocols는 프로토콜을 전환한다는 뜻이다.
프로토콜은 통신 규칙이라고 이해하면 된다.
처음에는HTTP규칙으로 연결 요청을 보냈지만, 서버가 이를 받아들이면WebSocket규칙으로 바뀐다.
즉,101은 단순히 페이지가 잘 열렸다는 뜻이 아니다.
HTTP연결을WebSocket연결로 바꾸는 데 성공했다는 뜻이다.
따라서WebSocket실습에서 연결 성공 여부를 볼 때는101이 가장 중요한 확인 기준이다.
Type이 websocket인지 확인하기
Network탭에는 요청의 종류를 나타내는Type정보도 표시된다.
여기서Type이websocket이면 해당 요청이 일반HTTP요청이 아니라WebSocket연결 요청이라는 뜻이다.
일반 페이지 요청은 보통document, 이미지 요청은png나jpeg, 비동기 요청은fetch또는xhr처럼 표시될 수 있다.
반면WebSocket연결은websocket으로 표시된다.
이 값을 확인하면 현재 보고 있는chat요청이 진짜WebSocket연결인지 구분할 수 있다.
Status가101이고Type이websocket이면 연결 성공 여부를 더 확실하게 판단할 수 있다.
Headers 탭에서 Request URL 확인하기
chat요청을 클릭하면 오른쪽에서 자세한 정보를 볼 수 있다.
그중Headers탭은 요청 주소와 응답 상태, 요청 헤더, 응답 헤더를 확인하는 곳이다.
먼저Request URL을 확인한다.
Request URL이ws://localhost:8088/chat으로 되어 있다면 클라이언트가/chat웹 소켓 서버로 연결을 요청했다는 뜻이다.
이 주소는 클라이언트 코드의 아래 흐름과 연결된다.
- 클라이언트 코드:
new WebSocket("ws://" + location.host + "/chat")- 실제 요청 주소:
ws://localhost:8088/chat- 서버 매핑 주소:
@ServerEndpoint(value="/chat")이 세 값에서 마지막 경로인
/chat이 서로 맞아야 한다.
클라이언트가/chat으로 요청하는데 서버가 다른 주소로 열려 있으면 연결이 실패한다.
Connection과 Upgrade 헤더 확인하기
Headers탭에서는Connection: upgrade와Upgrade: websocket도 확인할 수 있다.
이 값들은HTTP연결을WebSocket연결로 바꾸는 과정과 관련이 있다.
Connection: upgrade는 현재 연결을 업그레이드하겠다는 의미다.
여기서 업그레이드는 기존 연결을 다른 통신 방식으로 전환한다는 뜻이다.
Upgrade: websocket은 어떤 방식으로 전환할지를 나타낸다.
즉, 이 값은 연결을websocket방식으로 바꾸겠다는 의미다.
101 Switching Protocols,Connection: upgrade,Upgrade: websocket은HTTP연결이WebSocket통신으로 전환되었음을 보여준다.
Request URL이ws://localhost:8088/chat이고, 응답 상태가101이면/chat웹 소켓 연결이 정상적으로 열린 것이다.
연결 성공 여부를 판단하는 순서
WebSocket연결을 확인할 때는 화면만 보지 말고 개발자 도구에서 순서대로 확인하는 것이 좋다.
확인 순서는 다음과 같다.
- 개발자 도구의
Network탭을 먼저 연다.- 채팅 참여 버튼을 누른다.
- 요청 목록에서
chat요청을 찾는다.Status가101인지 확인한다.Type이websocket인지 확인한다.Headers탭에서Request URL이ws://localhost:8088/chat인지 확인한다.Connection: upgrade가 있는지 확인한다.Upgrade: websocket이 있는지 확인한다.이 항목들이 맞으면
WebSocket연결이 정상적으로 열린 것으로 볼 수 있다.
특히101,websocket,upgrade세 가지가 핵심이다.
연결이 안 될 때 먼저 확인할 것
WebSocket연결이 되지 않으면 먼저 주소가 맞는지 확인해야 한다.
클라이언트가 요청하는 주소와 서버의 매핑 주소가 다르면 연결되지 않는다.
예를 들어 클라이언트가ws://localhost:8088/chat으로 요청한다면 서버에는@ServerEndpoint(value="/chat")이 있어야 한다.
만약 서버가@ServerEndpoint(value="/socket")으로 되어 있다면/chat요청을 받을 수 없다.
다음으로 서버가 실행 중인지 확인해야 한다.
서버가 꺼져 있으면 브라우저가 아무리 연결을 요청해도 받을 대상이 없다.
또한 포트 번호가 맞는지도 확인해야 한다.
실습 서버가8088에서 실행 중인데 클라이언트가 다른 포트로 요청하면 연결되지 않는다.
확인할 내용은 다음과 같다.
- 서버가 실행 중인지 확인한다.
- 클라이언트 주소가
ws://localhost:8088/chat인지 확인한다.- 서버 매핑이
@ServerEndpoint(value="/chat")인지 확인한다.- 포트 번호
8088이 실제 실행 포트와 같은지 확인한다.- 개발자 도구의
Network탭을 먼저 열고 채팅 참여 버튼을 눌렀는지 확인한다.연결 실패를 확인할 때는 화면보다
Network탭의 요청 주소, 상태 코드, 타입을 먼저 보는 것이 정확하다.
브라우저 연결 확인 핵심 정리
브라우저에서
WebSocket연결을 확인하는 핵심은 개발자 도구의Network탭이다.
채팅 화면이 보이는 것만으로는 연결 성공을 확실히 알 수 없다.
반드시chat요청의 상태와 헤더를 확인해야 한다.
핵심만 정리하면 다음과 같다.
Network탭을 먼저 열고 채팅 참여 버튼을 누른다.Network탭에서chat요청을 찾는다.Status Code가101 Switching Protocols인지 확인한다.Type이websocket인지 확인한다.Request URL이ws://localhost:8088/chat인지 확인한다.Connection: upgrade는 연결 전환 요청을 의미한다.Upgrade: websocket은WebSocket으로 전환한다는 의미다.101은HTTP에서WebSocket으로 프로토콜 전환이 성공했다는 뜻이다.정리하면
WebSocket연결 성공은 개발자 도구에서101,websocket,upgrade값을 확인하면 된다.
WebSocket은 실시간 양방향 통신을 만들기 위한 기술이다.
기존HTTP처럼 클라이언트가 요청하고 서버가 응답하는 흐름만으로는 채팅, 실시간 알림, 실시간 게임처럼 즉시 반응해야 하는 기능을 자연스럽게 처리하기 어렵다.
WebSocket은 처음에HTTP요청을 기반으로 연결을 시작한다.
서버가 이 연결을 받아들이면101 Switching Protocols응답을 통해WebSocket통신으로 전환된다.
그 뒤에는 연결을 유지한 상태에서 클라이언트와 서버가 서로 데이터를 주고받는다.
정리하면WebSocket은 처음에는HTTP로 연결을 시작하지만, 연결이 성공한 뒤에는 유지된 연결을 통해 실시간 양방향 통신을 수행하는 방식이다.
WebSocket 전체 흐름 한 번에 보기
WebSocket의 전체 흐름은 어렵게 볼 필요가 없다.
연결을 열고, 데이터를 주고받고, 마지막에 연결을 닫는 구조다.
흐름을 순서대로 정리하면 다음과 같다.
- 클라이언트가
WebSocket서버 주소로 연결을 요청한다.- 이 요청은 처음에
HTTP Upgrade요청으로 시작된다.- 서버가 요청을 받아들이면
101 Switching Protocols를 응답한다.- 연결이 열리면
CONNECTED상태가 된다.- 연결이 유지되는 동안 클라이언트와 서버가 메시지를 주고받는다.
- 통신이 끝나면
Closing Handshake를 통해 연결을 닫는다.이 흐름에서 가장 중요한 단계는
Handshake다.
Handshake는 클라이언트와 서버가WebSocket으로 통신해도 되는지 먼저 확인하는 절차다.
이 절차가 성공해야 이후에 실시간 메시지 전송이 가능하다.
HTTP와 WebSocket을 구분하는 기준
HTTP와WebSocket은 모두 웹에서 사용되는 통신 방식이지만, 동작 목적이 다르다.
HTTP는 요청과 응답 중심이다.
클라이언트가 먼저request를 보내면 서버가response를 보낸다.
반면WebSocket은 연결 유지 중심이다.
한 번 연결된 뒤에는 클라이언트와 서버가 서로 필요한 순간에 데이터를 보낼 수 있다.
그래서 서버가 클라이언트에게 먼저 메시지를 보내는 것도 가능하다.
초보자 기준에서는 이렇게 구분하면 된다.
HTTP는 필요할 때마다 요청하고 응답받는 방식이다.WebSocket은 연결을 열어 둔 상태에서 계속 대화하는 방식이다.HTTP는 문서나 화면을 요청해서 받아오는 데 적합하다.WebSocket은 채팅이나 알림처럼 즉시 반응해야 하는 기능에 적합하다.즉,
HTTP는 요청-응답에 강하고,WebSocket은 실시간 양방향 통신에 강하다.
클라이언트 구현 핵심
브라우저 쪽에서는
JavaScript와HTML5 API를 사용해WebSocket클라이언트를 구현한다.
클라이언트는 서버에 연결하고, 메시지를 보내고, 서버에서 온 메시지를 받아 화면에 출력한다.
클라이언트 구현의 핵심은 다음과 같다.
new WebSocket()으로 서버에 연결한다.- 일반 연결은
ws를 사용한다.- 보안 연결은
wss를 사용한다.- 서버로 데이터를 보낼 때는
send()를 사용한다.- 서버에서 데이터를 받을 때는
onmessage를 사용한다.- 연결 성공은
onopen에서 처리한다.- 연결 종료는
onclose에서 처리한다.- 오류 발생은
onerror에서 처리한다.채팅 클라이언트 예제에서는 사용자가 입력한 이름, 메시지 내용, 시간을 객체에 담는다.
그 객체를JSON.stringify()로 문자열로 바꾼 뒤send()로 서버에 보낸다.
서버에서 메시지가 다시 오면JSON.parse()로 객체로 바꾸고 화면에 출력한다.
서버 구현 핵심
Spring Boot에서WebSocket서버를 구현하려면 먼저spring-boot-starter-websocket의존성을 추가해야 한다.
그 다음@ServerEndpoint가 붙은 클래스를 만들고, 클라이언트가 접속할 매핑 주소를 지정한다.
서버 구현의 핵심은 다음과 같다.
@ServerEndpoint(value="/chat")은/chat주소의 웹 소켓 연결을 처리한다.ServerEndpointExporter는@ServerEndpoint클래스를 웹 소켓 엔드포인트로 등록한다.@OnOpen은 클라이언트가 접속했을 때 실행된다.@OnMessage는 클라이언트가 메시지를 보냈을 때 실행된다.@OnClose는 클라이언트 연결이 종료되었을 때 실행된다.Session은 클라이언트와 서버 사이의 연결 정보를 나타낸다.채팅 서버 예제에서는 접속한 클라이언트의
Session을Set에 저장한다.
그리고 한 클라이언트가 메시지를 보내면 서버는 저장된 모든Session에게 같은 메시지를 다시 보낸다.
이 구조를 브로드캐스트라고 한다.
채팅 예제의 전체 데이터 흐름
채팅 예제는 클라이언트와 서버가 서로 따로 움직이는 코드가 아니다.
클라이언트 코드와 서버 코드가/chat연결을 기준으로 이어진다.
전체 흐름은 다음과 같다.
- 사용자가 채팅 참여 버튼을 누른다.
- 클라이언트가
ws://localhost:8088/chat으로 연결을 요청한다.- 서버의
@ServerEndpoint(value="/chat")가 연결을 받는다.- 서버의
@OnOpen이 실행되고Session이 저장된다.- 사용자가 메시지를 입력하고 전송한다.
- 클라이언트가
JSON.stringify()로 메시지를 문자열로 바꾼다.- 클라이언트가
ws.send()로 서버에 메시지를 보낸다.- 서버의
@OnMessage가 메시지를 받는다.- 서버가 모든
Session에게sendText()로 메시지를 다시 보낸다.- 클라이언트의
onmessage가 실행된다.- 클라이언트가
JSON.parse()로 받은 문자열을 객체로 바꾼다.- 화면의 채팅 영역에 메시지가 출력된다.
채팅 예제의 핵심은 클라이언트가 메시지를 보내고, 서버가 그 메시지를 모든 접속자에게 다시 보내고, 각 클라이언트가 받은 메시지를 화면에 출력하는 흐름이다.
개발자 도구에서 확인할 핵심
WebSocket실습에서는 코드만 보는 것보다 브라우저 개발자 도구에서 연결 상태를 확인하는 것이 중요하다.
채팅 화면이 보이더라도 실제WebSocket연결이 성공했는지는Network탭에서 확인해야 한다.
확인할 핵심은 다음과 같다.
Network탭을 먼저 연다.- 채팅 참여 버튼을 누른다.
- 요청 목록에서
chat요청을 찾는다.Status Code가101 Switching Protocols인지 확인한다.Type이websocket인지 확인한다.Request URL이ws://localhost:8088/chat인지 확인한다.Connection: upgrade를 확인한다.Upgrade: websocket을 확인한다.
101 Switching Protocols는HTTP연결이WebSocket통신으로 전환되었다는 뜻이다.
따라서 실습에서 연결 성공을 판단할 때 가장 중요한 값은101이다.
최종 정리
WebSocket은 실시간 기능을 만들기 위한 통신 방식이다.
처음에는HTTP요청으로 연결을 시작하지만, 연결이 성공하면 같은 연결을 유지하면서 클라이언트와 서버가 양방향으로 데이터를 주고받는다.
마지막으로 핵심만 다시 정리하면 다음과 같다.
WebSocket은HTML5표준 기술이다.WebSocket은HTTP환경에서 시작된다.WebSocket은 하나의TCP연결을 유지한다.WebSocket은 전 이중 통신을 지원한다.Handshake는 연결을 시작하기 위한 확인 절차다.101 Switching Protocols는WebSocket전환 성공을 의미한다.- 클라이언트는
new WebSocket(),send(),onmessage를 중심으로 동작한다.- 서버는
@ServerEndpoint,@OnOpen,@OnMessage,@OnClose,Session을 중심으로 동작한다.- 채팅 서버는 접속한
Session을 관리하고, 받은 메시지를 모든 클라이언트에게 다시 보낸다.결국
WebSocket은 실시간 채팅처럼 서버와 클라이언트가 계속 연결되어 있어야 하는 기능을 만들기 위한 핵심 통신 방식이다.