WebSocket부터 이해하자
- Socket
- WebSocket
- CS
들어가며
웹소켓 재연결 로직부터 짜기 시작하면, 정작 왜 연결이 끊기는지는 모른 채 코드만 붙이게 된다. HTTP와 무엇이 다르길래 연결을 계속 유지해야 하고, 그 유지 비용은 어디서 오는지부터 봐야 재연결이 왜 필요한지도 보일 것이라 생각했다.
Socket이란
Socket은 사전적으로 다른 부분이 들어갈 수 있도록 만들어 놓은 구멍 또는 서로 다른 것이 연결될 수 있도록 마련된 창구를 뜻한다.

네트워크에서 Socket은 컴퓨터 프로그램이 네트워크를 통해 데이터를 주고받을 수 있도록 하는 연결점이다. 벽에 콘센트가 없으면 전기 플러그를 꽂아 전기를 쓸 수 없듯이, 네트워크 통신도 송신 측(Client Socket)과 수신 측(Server Socket) 양쪽에 소켓이 열려 있어야 그 통로로 데이터가 오간다.
이 소켓을 프로세스 간 통신(IPC)에 활용하는 네트워크 프로그래밍 기법이 Socket Programming이다. Client Socket과 Server Socket은 같은 구조를 쓰지만, 역할에 따라 동작 흐름이 다르다.

클라이언트 소켓은 다음 순서로 동작한다.
| 단계 | 함수 | 설명 |
|---|---|---|
| 1 | 소켓 생성 | 연결 대상 정보(IP, Port 등)가 없는 빈 소켓을 생성 |
| 2 | 연결 요청 (connect) | 대상 IP/포트를 지정해 연결을 요청하며, 상대가 수락해야 connect()가 완료됨 |
| 3-1 | 데이터 송수신 (send) | 클라이언트가 데이터를 보낼 시점과 크기를 직접 제어 가능 |
| 3-2 | 데이터 수신 (recv) | 상대가 데이터를 언제, 얼마나 보낼지 알 수 없으므로, 일반적으로 별도의 스레드에서 처리 |
| 4 | 소켓 종료 (close) | 통신이 완료되면 소켓을 닫아 연결을 종료 |
서버 소켓은 한 단계가 더 많다. 연결 요청을 받아 대기하는 listen과, 실제 연결을 수립하는 accept가 추가된다.
| 단계 | 함수 | 설명 |
|---|---|---|
| 1 | 소켓 생성 (socket) | 연결 대상 정보가 없는 빈 소켓을 생성 |
| 2 | 소켓과 포트 바인딩 (bind) | 생성된 소켓에 포트 번호를 연결하며 프로세스마다 다른 포트를 써야 데이터가 올바르게 전달되지만, 채팅 서버처럼 하나의 프로세스가 같은 포트를 공유하는 여러 소켓을 가질 수도 있음 |
| 3 | 연결 요청 대기 (listen) | 클라이언트의 연결 요청을 받을 준비 상태로 대기하다가, 요청이 감지되면 반환됨 |
| 4 | 연결 수락 (accept) | 실제 연결을 수립하는 단계로, 요청이 수락되면 해당 클라이언트 전용의 새 소켓을 생성하고 기존 Server Socket은 요청 수락 기능만 담당함 |
| 5 | 데이터 송수신 (send, recv) | 클라이언트 소켓과 동일하게 동작하며, 수신은 비동기적으로 처리하기 위해 보통 별도의 스레드에서 운영됨 |
| 6 | 소켓 종료 (close) | Server Socket은 자신의 소켓뿐 아니라 클라이언트 연결 시 새로 생성된 소켓도 함께 관리 및 종료해야 함 |
포트(Port)도 이 흐름에서 각자의 역할을 맡는다. 네트워크에서 데이터를 교환할 때, 운영체제는 각 프로세스에 고유한 포트 번호를 부여하여 통신 대상을 식별한다. 데이터 전송 시 목적지 포트를 지정하면, 수신 측에서 해당 포트 번호를 가진 프로세스가 데이터를 받아 처리한다.
WebSocket이 필요했던 이유
가장 널리 쓰이는 HTTP 프로토콜은 요청과 응답으로 구성된 단방향 통신 방식을 따른다. 클라이언트가 요청을 보내면 서버는 이를 처리한 뒤 응답을 반환하고, 응답이 끝나면 클라이언트와 서버 간 연결은 즉시 종료된다. 서버가 연결을 계속 유지할 필요가 없는 덕분에 적은 자원으로도 다수의 요청을 처리할 수 있어, 전통적인 웹사이트 구현에는 이 구조가 잘 맞는다.
문제는 실시간 양방향 상호작용이 필요한 애플리케이션에서 드러난다. 두 가지 제약이 있다.
첫째, HTTP에서는 항상 클라이언트가 연결을 시작한다. 서버는 클라이언트의 요청이 오기 전까지 아무 데이터도 보낼 수 없어, 먼저 이벤트를 전달하는 것이 불가능하다. 그래서 서버 이벤트를 알리려면 Polling(주기적 요청)을 쓰는데, 이벤트가 드문 경우에는 불필요한 호출과 리소스 낭비로 이어진다.
둘째, 연결이 유지되지 않아 상태나 문맥을 이어가기 어렵다. 요청-응답 후 즉시 종료되는 단방향 연결이라 Context를 유지할 수 없고, HTTP 헤더 크기도 커서 짧은 메시지를 자주 주고받는 서비스에서는 네트워크 대역폭 낭비가 심해진다.
WebSocket이 나오기 전에도 HTTP로 실시간 통신을 구현하려는 시도는 있었다. 다만 모두 요청/응답 헤더가 불필요하게 크다는 한계를 공유했다.
HTTP Polling
클라이언트가 1초, 10초 등 주기적으로 서버에 요청을 보내 새로운 이벤트를 확인하는 방식이다.

| 구분 | 내용 |
|---|---|
| 장점 | 클라이언트와 서버 모두 구현이 간단함 |
| 단점 | 메시지가 도착해도 다음 요청 주기까지 기다려야 해 실시간성이 보장되지 않고, 응답 여부와 무관하게 주기적으로 요청을 보내 서버에 불필요한 부하가 발생함 |
HTTP Long Polling
클라이언트가 서버로 요청을 보내고, 이벤트가 발생할 때까지 기다리는 방식이다. 이벤트가 발생하면 서버가 응답을 보내고, 클라이언트는 즉시 다시 요청을 보내 연결을 유지한다.

| 구분 | 내용 |
|---|---|
| 장점 | Polling에 비해 요청 횟수가 줄어들어 네트워크 트래픽이 감소함 |
| 단점 | 이벤트 발생 빈도가 높으면 결과적으로 Polling과 요청량 차이가 크지 않고, 여러 클라이언트에서 동시에 이벤트가 발생하면 서버에 부하가 집중될 수 있음 |
HTTP Streaming(SSE)
클라이언트가 서버로 요청을 보내 연결을 맺은 후, 이벤트가 발생해도 연결을 끊지 않는 방식이다.

| 구분 | 내용 |
|---|---|
| 장점 | 서버는 지속적으로 클라이언트로 메시지를 전송 가능 |
| 단점 | 클라이언트는 스트리밍 중 동일한 TCP 포트에서 읽기와 쓰기를 동시에 수행할 수 없어 추가 요청을 보내려면 별도 포트를 써야 하고, 클라이언트 측 문제도 서버가 즉시 감지하기 어려움 |
WebSocket이란
WebSocket은 이 한계를 넘어 클라이언트와 서버 간 효율적인 실시간 양방향 통신을 제공하는 프로토콜이다. 하나의 서버가 여러 클라이언트와 동시에 데이터를 주고받을 수 있으며, 빠르고 안정적인 실시간 메시지 교환이 가능하다.
WebSocket은 이름에 Socket이 포함되어 있지만, 앞서 본 네트워크 소켓(TCP Socket)과는 개념적으로 다르다. 웹 환경에서 두 프로그램이 메시지를 교환하기 위한 응용 계층 프로토콜이며, 브라우저와 서버 간 실시간 통신을 위해 설계되었다.
| 구분 | 설명 |
|---|---|
| Socket | TCP/IP 프로토콜을 기반으로 동작하며, 클라이언트와 서버 간 지속적인 연결을 유지하는 실시간 통신 방식 |
| WebSocket | HTTP 프로토콜을 사용하는 웹 환경에서 실시간 통신을 구현하기 위해 등장한 프로토콜로, 초기에 HTTP 핸드셰이크를 수행한 후 연결을 WebSocket 프로토콜로 업그레이드하여 양방향 실시간 통신을 가능하게 함 |
OSI 7계층 구조에서 TCP는 4계층(전송 계층)에 위치하며, WebSocket은 7계층(응용 계층)에서 HTTP 핸드셰이크 이후 TCP 연결을 업그레이드하여 사용한다. 이 덕분에 WebSocket은 TCP의 순서 보장·재전송 등 신뢰성 있는 특성을 그대로 물려받는다.
물론 연결을 계속 유지하는 방식이라 대가도 있다.
| 단점 | 설명 |
|---|---|
| 구현 복잡성 | HTTP 기반의 Polling, Long Polling, Streaming 방식보다 설계와 구현이 상대적으로 복잡하다. |
| 호환성 문제 | WebSocket은 HTML5 이후 등장한 기술이라 이전 환경에서는 적용이 어려우며, Internet Explorer는 버전 10 이상부터 지원한다. |
| Stateful 특성 | WebSocket은 Stateful Protocol로 클라이언트와 서버가 연결 상태를 지속적으로 유지해야 하며, 그만큼 네트워크 및 서버 자원이 소모되어 일정한 운영 비용이 발생한다. |
| 비정상 종료 대응 필요 | 네트워크 장애나 브라우저 종료 등으로 인해 예기치 않게 연결이 끊어질 수 있으므로, 이에 대한 복구 및 재연결 전략이 반드시 필요하다. |
Stateless는 서버가 클라이언트의 상태 정보를 저장하지 않는 방식이다. 각 요청은 서로 독립적이며, 요청 1건당 응답 1건만 처리된다. Stateful은 반대로 서버가 클라이언트의 상태 정보를 보관해, 이전 상태가 이후 요청 처리에 영향을 주고 연결을 계속 관리해야 한다. WebSocket이 연결을 계속 유지해야 하는 이유도, 그 유지 비용이 발생하는 이유도 여기에 있다.
웹소켓 통신 과정
WebSocket은 HTTP와 완전히 별개의 프로토콜이 아니라, HTTP를 기반으로 업그레이드되는 형태의 통신이다. 클라이언트가 WebSocket 연결을 시작할 때, 먼저 서버에 HTTP GET 요청을 보낸다. 이 요청에는 Connection: Upgrade와 Upgrade: websocket 같은 특수 헤더가 포함된다.
GET /chat HTTP/1.1
Host: www.test.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13서버가 WebSocket을 지원한다면, 다음과 같은 응답을 보내며 프로토콜 업그레이드(HTTP → WebSocket)를 완료한다.
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
이 과정을 핸드셰이크(Handshake)라고 하며, 완료되는 즉시 클라이언트와 서버는 WebSocket 프로토콜을 이용한 양방향 통신을 시작할 수 있다.
핸드셰이크가 완료되면, 클라이언트와 서버는 메시지를 Frame 단위로 주고받는다. 하나의 메시지는 여러 개의 데이터 조각(Frame)으로 구성될 수 있으며, 브라우저 환경에서는 send() 메서드를 통해 텍스트 또는 바이너리 데이터를 전송할 수 있다. Frame은 세 종류로 나뉜다. 텍스트 데이터가 담긴 텍스트 프레임, 바이너리 데이터가 담긴 바이너리 데이터 프레임, 커넥션이 유지되는지 확인하기 위한 핑/퐁 프레임이다.
데이터 송수신이 완료되면, 클라이언트나 서버 중 어느 한쪽이 Close Frame을 전송해 연결 종료를 요청한다. 상대가 이에 대한 응답으로 Close Frame을 다시 보내면, WebSocket 연결이 완전히 종료된다.
정리하며
WebSocket이 HTTP와 다른 지점은 결국 하나로 모인다. 요청마다 연결을 맺고 끊는 대신, 핸드셰이크 이후의 연결을 계속 유지한다는 것이다. Stateful하다는 것은 폴링 없이도 서버가 먼저 메시지를 보낼 수 있다는 이점이자, 동시에 서버·클라이언트 어느 한쪽이 죽거나 네트워크가 끊기면 그 상태를 누군가는 복구해야 한다는 뜻이기도 하다.