← Back to list
Networking
#WebSocket#실시간통신#전이중#RFC6455#Upgrade핸드셰이크
Last updated · 2026-10-18

WebSocket: Real-Time Bidirectional Communication

1. Overview

WebSocket is a full-duplex bidirectional communication protocol that lets a client and a server exchange data simultaneously over a single TCP connection, standardized as IETF RFC 6455 (2011). Only the initial connection is established via an HTTP handshake; afterward it leaves the HTTP request-response model behind and freely exchanges messages over a persistent connection.

The fundamental reason WebSocket emerged is that HTTP's one-way, stateless request-response model is structurally ill-suited to real-time interaction. In traditional HTTP the server can respond only when the client requests, so there was no way for the client to immediately receive events that arise on the server (a new message, a price change, a notification). The early web worked around this limitation with polling—a makeshift where the client periodically repeats the query "anything new?"—but since most requests ended in empty responses, this greatly wasted network and server resources.

Attempts to imitate real-time behavior predate WebSocket. Representative examples were the "Comet" technique, which streams an endless flow into a hidden iframe, and long polling, but these were nothing more than workarounds that skirted the HTTP request-response constraint. With no standardized protocol, implementations diverged and compatibility problems with proxies and firewalls were frequent. WebSocket is the result of joint standardization by the W3C (API) and the IETF (protocol) to replace these stopgaps, and it was a qualitative leap in that it gives browsers a consistent WebSocket API and defines a clear wire format for servers and intermediaries to follow.

As rich web applications that require the server to proactively push data (server push)—like Google Maps dragging, chat, and stock-trading screens—became commonplace in the late 2000s, the inefficiency of repeatedly sending HTTP headers (hundreds of bytes to several KB) and opening new TCP connections on every request emerged as a serious bottleneck. WebSocket was designed precisely to target this point, keeping a connection open once established so that either side can speak first. The fact that the per-frame overhead after connection setup is as little as 2 bytes is the core of the tens-of-times efficiency gap over HTTP polling.

WebSocket's characteristics can be summarized in four points. First, full-duplex bidirectionality, whereby server and client can send data first at any time without waiting for the other's request. Second, persistent connection, which reuses the TCP connection once established, so connection setup/teardown costs and handshake latency are not repeated. Third, low overhead, communicating in small frame units without HTTP headers to save bandwidth. Fourth, support for both text and binary, so it can directly exchange not only text messages like JSON but also binary data like images and audio. These four characteristics combine to make WebSocket the web-standard protocol that most naturally satisfies the "real-time" requirement.

That said, one must recognize from the outset that these benefits come at a cost. A persistent connection means the server must keep as much connection state resident in memory as there are connected users, and bidirectionality, along with the flexibility that either side may send a message first, places the burden of flow control, authentication, and security on the application. In other words, WebSocket is a trade in which "you gain real-time behavior but give up the simplicity that stateless HTTP provided for free," and judging early in the design whether this trade pays off is key. This document addresses, in this light, everything from protocol structure to scaling and security in turn.

2. WebSocket Handshake and Protocol Switching

WebSocket's most original design is that it starts on top of HTTP and then leaves HTTP. The connection begins as an ordinary HTTP GET request but requests protocol switching via the Upgrade: websocket header. If the server accepts, the same TCP connection is "upgraded" as is into a WebSocket channel, and from then on HTTP does not intervene at all. The sequence diagram below shows the entire handshake process.

sequenceDiagram
    participant C as Client(Browser)
    participant S as Server
    Note over C,S: 1) HTTP Upgrade handshake
    C->>S: GET /chat HTTP/1.1<br/>Upgrade: websocket<br/>Sec-WebSocket-Key: (random 16B base64)
    S->>C: HTTP/1.1 101 Switching Protocols<br/>Sec-WebSocket-Accept: (hash value)
    Note over C,S: 2) Upgrade the same TCP connection to WebSocket
    C->>S: Text/binary frame (anytime)
    S->>C: Text/binary frame (anytime)
    Note over C,S: 3) Keep-alive via Ping/Pong
    C->>S: Close frame (graceful close)

The core of the handshake is the challenge-response of Sec-WebSocket-Key and Sec-WebSocket-Accept. The client base64-encodes a 16-byte random number and sends it as Sec-WebSocket-Key; the server appends a fixed magic string (258EAFA5-E914-47DA-95CA-C5AB0DC85B11), SHA-1 hashes it, base64-encodes the result, and replies with it as Sec-WebSocket-Accept. This value is not for cryptographic security but is a verification device to prevent an intermediate caching proxy from mistaking the WebSocket handshake for an ordinary HTTP response and caching or reusing it. Only when the response code 101 Switching Protocols is returned is the connection upgraded.

A design that starts over HTTP also yields the practical benefit of firewall and proxy traversal. Because WebSocket uses the same ports as HTTP—80 (ws) and 443 (wss)—and begins the handshake with a standard HTTP request, corporate firewalls and intermediate proxies often allow the traffic without opening a separate port. In particular, TLS-encrypted wss:// prevents intermediaries from peering into the payload, reducing interference and blocking, so in practice wss:// is treated as the de facto default, if only for compatibility. The fact that it rides on web infrastructure as is, compared with other real-time protocols that use dedicated ports, was a hidden driver of WebSocket's spread.

The handshake stage also performs subprotocol negotiation. The client carries a list of application-level protocols it understands (e.g., chat, wamp, mqtt) in the Sec-WebSocket-Protocol header, and when the server selects one and replies, both sides share the same message convention. Because WebSocket itself is merely a conduit that carries messages and does not define their meaning, in practice one defines an application protocol on top of it with a serialization format like JSON, MessagePack, or Protocol Buffers plus one's own message schema. This layer separation is a design advantage that lets the transport mechanism and message semantics evolve independently.

After the upgrade, the unit of communication is not an HTTP message but a frame. A frame consists of the FIN bit (whether it is the last fragment of a message), the opcode (frame type), the masking bit, the payload length, (for client→server) the masking key, and the payload. Opcodes split into data frames (0x1 text, 0x2 binary) and control frames (0x8 Close, 0x9 Ping, 0xA Pong). In particular there is a mandatory rule that every frame the client sends must be XOR-masked with a 32-bit masking key, a design meant to prevent a cache poisoning attack in which a malicious script repeatedly sends predictable plaintext to poison an intermediate proxy's cache. The table below summarizes the main frame components.

Component Size Role
FIN 1 bit Marks whether it is the last frame of a message (fragmentation support)
Opcode 4 bit Frame type (text, binary, Close, Ping, Pong)
Mask 1 bit Whether the payload is masked (mandatory for client→server)
Payload len 7/16/64 bit Variable-length encoding minimizes overhead for short messages
Masking-key 0/4 byte 32-bit key used when masking
Payload Variable Actual data (UTF-8 text or binary)

Another important concept in frame design is fragmentation. A single logical message can be split across several frames: only the first frame carries the opcode, subsequent frames are marked as continuation (0x0), and the FIN bit is set to 1 in the last frame. Thanks to this structure the receiver can receive and process data as a stream even without knowing the total message size in advance, which is advantageous for transferring large binaries (files, media) and for data that is variable in length and generated incrementally, like a generative AI's token-by-token responses. Control frames (Ping, Pong, Close) are also allowed to be interleaved between data frames, so connection status can be checked even during a large transfer.

The reason control frames and data frames are separated is to separate the concerns of connection management and data transfer (separation of concerns). The actual content the application exchanges is delivered as text/binary data frames, whereas the meta tasks of confirming the connection is alive and closing it gracefully are handled by control frames. Thanks to this separation, application logic can focus solely on data processing while connection keep-alive and teardown can be handled transparently at the protocol/library layer.

The fact that the frame length field uses variable-length encoding is also central to efficiency. If the payload is 125 bytes or less, a single 7-bit length field suffices; if larger, a 16-bit or 64-bit extended field is used. That is, a short message like one line of chat carries only a tiny 2-to-6-byte header, and the length field grows only for large transfers. This is a textbook example of the design principle "make the common case cheapest," with a large cumulative bandwidth saving in a real-time service where countless small messages flow.

3. Communication Architecture and Connection Lifecycle

WebSocket is fundamentally different from stateless HTTP in that it is a stateful connection, which directly affects server architecture design. The concept diagram below shows a typical architecture including a WebSocket server to which many clients are connected and a message broker.

graph TB
    subgraph Clients["Clients"]
      C1["Browser A"]
      C2["Browser B"]
      C3["Mobile app"]
    end
    LB["Load balancer<br/>(Sticky Session/L7)"]
    subgraph Backend["WebSocket server tier"]
      W1["WS server #1<br/>(holds connection state)"]
      W2["WS server #2<br/>(holds connection state)"]
    end
    MB["Message broker<br/>(Redis Pub/Sub, Kafka)"]
    C1 -->|wss://| LB
    C2 -->|wss://| LB
    C3 -->|wss://| LB
    LB --> W1
    LB --> W2
    W1 <--> MB
    W2 <--> MB

The connection lifecycle flows through the stages Open → Message exchange → Keep-alive (Ping/Pong) → Close. From the browser API perspective, after creating new WebSocket(url) each stage is handled with the onopen, onmessage, onerror, and onclose event handlers. Because it is an event-driven asynchronous model, the application reacts via callbacks without needing to poll for message arrival, and sending is immediate with a single send(). However, in an unstable mobile network the connection drops frequently, so a robust service requires the client to itself implement logic that detects onclose and retries the connection with exponential backoff, resynchronizing state that changed while disconnected. Because of this reconnection/resynchronization burden, in practice libraries like the aforementioned Socket.IO are preferred.

Another design point of the Open stage is mapping connections to sessions. The server must manage each established connection by associating it with domain concepts such as user, channel, or room, so that it can selectively send (targeted push) messages only to a particular user or group. For example, in a chat-room model, connections are grouped by room, and one user's utterance is broadcast only to the connections of members in the same room. Because this mapping table is state resident in server memory, during horizontal scaling it must be shared and coordinated among servers through the broker backplane described earlier. In the Open stage the server holds each connection as a unique session object in memory, so the number of connected users becomes the number of concurrent connections (state) the server must maintain. Handling tens of thousands to hundreds of thousands of concurrent connections requires a structure that processes many connections with few threads via event-driven asynchronous I/O (Node.js, Netty, Go goroutines, etc.).

In the message-exchange stage, ordering guarantees and backpressure become design considerations. Because WebSocket operates over a single TCP connection, messages within the same connection are guaranteed in transmission order, but if the receiver cannot keep up with the processing rate, the send buffer piles up and memory pressure and latency accumulate. For example, in a trading service that pushes down thousands of price ticks per second, if a client is slow the server must guarantee latest-data-first with a strategy of conflating or dropping old ticks. This is a flow-control problem peculiar to persistent connections that did not exist in HTTP's request-response.

The Ping/Pong frames of the Keep-alive stage are especially important in practice. Intermediate load balancers, proxies, and NAT devices regard a connection with no traffic for a certain time as idle and arbitrarily cut it (often around 60 seconds), so to prevent this one must periodically send a Ping to signal that the connection is alive. Also, when a TCP connection drops abnormally (e.g., a mobile terminal's network switch) the application does not know immediately, so one must implement heartbeat logic that judges a connection a dead connection and cleans it up, inducing reconnection, if a Pong response to a Ping does not arrive within a certain time. In the Close stage, a status code (e.g., 1000 normal closure, 1006 abnormal closure) is carried in the Close frame so that both sides close the connection by agreement. A normal close goes through a closing handshake in which both sides exchange Close frames, but in abnormal situations like a network outage TCP drops without this exchange, so the server must detect it via a heartbeat timeout and clean up.

From an operational standpoint, WebSocket is a target for which observability is tricky to secure. HTTP is easy to monitor because each request leaves a status code and response time in the logs, but a long-lived WebSocket connection has no per-request metrics, so connection-centric metrics such as concurrent-connection count, connection-duration distribution, message throughput, reconnection frequency, and zombie-connection ratio must be instrumented separately. Therefore, in practice one collects connection open/close events and message latency as metrics and builds dashboards that treat a surge in abnormal close codes (1006, etc.) as a failure signal. If this instrumentation design is missing, it is hard to catch failures early in a large-scale real-time service.

Meanwhile, the way failures propagate also differs from HTTP for a persistent connection. HTTP recovers naturally with the next request even if a request fails, but if one WebSocket server goes down, the tens of thousands of connections it held drop simultaneously, and a thundering herd of clients all attempting reconnection at once can transfer overload to the remaining servers. To mitigate this, one should mix a random delay (jitter) into the reconnection timing to distribute the load, and on the server side place connection-setup rate limiting (admission control) to absorb a sudden influx.

4. Comparison with Existing Real-Time Techniques

To understand WebSocket's standing accurately, one must also look at why it differs from its predecessors and alternatives. Polling is the simplest to implement, but, for example, if ten thousand chat clients polling at a one-second interval connect, ten thousand empty requests per second occur, most of which become meaningless traffic. Long Polling reduces empty responses by having the server hold the response until new data arises, but the cost of re-establishing the connection and re-sending HTTP headers on every response remains. SSE (Server-Sent Events) is efficient for server→client one-way streaming and has the advantages of automatic reconnection and HTTP/2 friendliness, but the client→server direction still depends on a separate HTTP request. The table below compares the characteristics of the four techniques.

Category Polling Long Polling SSE WebSocket
Direction One-way (request-based) One-way (request-based) Server→client one-way Bidirectional (full-duplex)
Connection New connection each time Reconnect per response Persistent (one-way) Persistent (bidirectional)
Real-time Low (interval-dependent) Medium High Very high
Overhead Very large (header repetition) Large Small Minimal (frame 2B+)
Data format No restriction No restriction Text (UTF-8) Text + binary
Typical use Simple status polling Notifications Newsfeed, stock ticker Chat, games, collaboration, trading

It is also worth noting the boundaries with other bidirectional technologies adjacent to WebSocket. gRPC bidirectional streaming is HTTP/2-based and provides strongly typed contracts (Protocol Buffers) and code generation, giving it strength in service-to-service communication, but it is hard to use directly in a browser and needs a separate proxy (gRPC-Web). MQTT is a lightweight publish-subscribe IoT protocol optimized for sensor communication on low-power, unstable networks, and in web environments it is often combined as MQTT over WebSocket. In other words, WebSocket has the clearest standing as a general-purpose bidirectional conduit between browser and server, while contract-centric server-to-server communication is suited to gRPC and large-scale IoT device collection to MQTT, each with its own niche. From a professional engineer's perspective, the discernment to distinguish the boundaries of overlapping technology groups according to the requirement context and choose among them is important.

The essence of where the differences arise lies on two axes: "does it keep the connection open" and "who can speak first." Polling and long polling stay within HTTP's request-response frame, with the client holding the initiative, so server push is fundamentally impossible; WebSocket upgrades the connection and leaves that frame itself. A practical implication: for a 1:1 notification or one-way streaming, SSE is a more reasonable choice that is simpler and fits HTTP infrastructure well, and only when low-latency bidirectional communication is essential—as with chat, multiplayer games, real-time collaborative editing, and stock trading—is WebSocket the right answer. For example, messengers like Slack and KakaoTalk Web, the real-time order books of Upbit and Binance, and Google Docs collaborative editing are all implemented with WebSocket-family technology. In particular, collaborative editing lays concurrency-control algorithms such as OT (Operational Transformation) or CRDT on top of WebSocket so that simultaneous edits by multiple users do not conflict, propagating each party's edit operations to the others in real time and converging them. This is a representative case showing that WebSocket plays the role of the transport foundation for complex real-time collaboration, beyond simple message delivery. Using WebSocket unconditionally can be over-engineering that only inflates state-keeping cost and operational complexity.

Looking at the efficiency difference in concrete numbers makes its implication clear. If 1,000 people perform polling that carries an average 300-byte HTTP header at a one-second interval, about 300 KB per second of pure header traffic occurs even with no message body, and the server must process 1,000 connection setups/teardowns per second. By contrast, if the same users are switched to WebSocket, connections are fixed at 1,000 and only a few-byte frame goes back and forth only when an actual event occurs, so network traffic and server compute load plummet to a single-digit-percentage level. This very gap is the practical reason large-scale real-time services choose WebSocket; conversely, for a service with low event frequency and loose real-time requirements, this benefit is not felt, and simple polling may be more economical.

Another practical implication worth noting is the qualitative difference in latency. With polling at a one-second interval, there is a structural latency of 0.5 seconds on average and 1 second at worst after an event occurs. Shortening the interval reduces latency but creates the trade-off of a surge in empty requests. By contrast, WebSocket pushes as soon as an event arises, so latency converges to the network round-trip level (a few to tens of ms). In areas where tens of ms of latency sway the user experience and fairness—like multiplayer games and real-time auctions—this qualitative difference effectively determines the technology choice.

5. Deep Dive: Scalability, Security, and Latest Trends

The hardest challenge in applying WebSocket to an actual large-scale service is the horizontal scaling of stateful connections. Because HTTP is stateless, it does not matter which server receives a request, but with WebSocket a particular server holds a particular connection's state. Thus a situation arises where server B must send a message to a user connected to server A, and to solve this a message broker backplane is needed. In practice one places Redis Pub/Sub or Kafka as the central bus, and each WebSocket server routes messages over the connections it holds. For example, for a service on the scale of a million concurrent connections, a typical configuration places dozens of servers each accommodating 50,000–100,000 connections and relays broadcasts via Redis Pub/Sub. The load balancer must understand the Upgrade header at L7 and pin the same connection to the same server with a sticky session.

Capacity planning is also approached differently from HTTP. An HTTP service gauges capacity by requests per second (RPS) and average response time, but for WebSocket the number of concurrent connections is itself the resident resource, so per-connection memory and file-descriptor consumption become the primary constraint. For example, if one holds a few tens of KB of buffer per connection, 100,000 connections alone keep several GB of memory resident. Therefore, before adoption one should perform a load test that simulates mass connections (e.g., measuring the limit by gradually increasing connections with virtual clients) to fix in advance the safe connection capacity per server and the horizontal-scaling unit. If this estimation is poor, everything is fine in normal times but a traffic surge triggers cascading failures from connection refusal and memory exhaustion.

Authentication design has a constraint peculiar to WebSocket. The browser's standard WebSocket constructor cannot set custom HTTP headers, so one cannot use, as is, the method of passing an authentication token via the Authorization: Bearer <token> header like a REST API. Because of this, in practice one carries a short-lived token in the handshake URL's query parameter, or uses a pattern of exchanging authentication information in the first message right after connection setup. The query-parameter method risks the token remaining in logs and history, so one uses a short-expiry one-time token, and the post-connection message-authentication method requires state management that blocks data transmission until authentication completes. Thus authentication over a stateful connection requires a design pattern different from stateless HTTP.

The threat to watch most on the security side is CSWSH (Cross-Site WebSocket Hijacking). Because the WebSocket handshake does not receive full protection from the browser's Same-Origin Policy (SOP) and cookies are attached automatically, an attacker's site can covertly open a WebSocket using the victim's authentication cookie. Therefore the server must always validate the Origin header at handshake and also use token-based authentication that does not rely on cookies alone (e.g., passing a one-time token at connection). The transport segment should also, as a principle, use wss:// (default port 443), which is TLS-encrypted instead of plaintext ws://, to block eavesdropping and man-in-the-middle attacks. Rate limiting against message flooding, a message size cap, and input validation should also be applied together at the application layer.

Backplane design comes with trade-offs. Redis Pub/Sub has low latency and is simple to implement, but it does not store messages and has a fire-and-forget characteristic of receiving only messages after the subscription point, so if delivery guarantees are needed one must use a broker that persists a log, like Kafka. Conversely, Kafka is strong in durability and reprocessing but has greater latency and operational complexity than Redis. Therefore the key is to design the broker and the delivery-guarantee level differently according to business requirements such as "loss of chat messages is not permitted" or "for game state only the latest value matters." Also, at hundreds of thousands of concurrent connections, OS/infrastructure-level tuning such as the file-descriptor limit (ulimit), kernel TCP buffers, and the load balancer's connection limit must proceed in parallel for real capacity to be secured.

As for the latest trends, several currents stand out. First, WebSocket in HTTP/2 and HTTP/3 environments. RFC 8441 defined a way to bootstrap WebSocket within a single HTTP/2 stream (:protocol = websocket), and further, WebTransport provides both reliable and unreliable bidirectional streams over HTTP/3 (QUIC) and is emerging as a successor technology to WebSocket in the games and media fields that need low latency and multiple streams. Whereas WebSocket is a single ordered stream and thus has the HOL Blocking limitation in which one message's delay blocks later messages, WebTransport supports multiple streams and datagrams to relax this constraint, so the two are expected to develop as a complementary or substitutive relationship. Second, the permessage-deflate (RFC 7692) compression extension is widely used to save bandwidth for text messages. However, compression consumes additional CPU load and memory (the compression context), so if messages are small and frequent it can actually be a loss, and it must be selectively applied to the traffic characteristics. Third, higher-level libraries like Socket.IO and SignalR, which provide automatic reconnection and fallback (polling substitution) even when a connection drops, are used almost as a de facto standard, complementing robustness across diverse network environments.

Finally, the spread of generative AI is again pushing up demand for WebSocket. As the UX in which an LLM incrementally generates its answer token by token and streams it to the user in real time becomes standard, WebSocket (or SSE) is widely used as the conduit through which the server immediately pushes generated tokens. Since bidirectionality is also useful for handling user intervention mid-stream (stopping generation, follow-up queries), WebSocket's suitability is being re-examined in conversational AI and agent interfaces.

6. Considerations and Implications

  • Application strategy (fitness of technology choice): WebSocket is not a cure-all. SSE suffices for one-way server push and polling for simple status checks; WebSocket should be chosen only when low-latency bidirectionality is the core requirement. Weighing functional requirements (real-time behavior, directionality) together with non-functional requirements (concurrent-connection scale, operational capability) to avoid over-engineering is the professional engineer's role.
  • Trade-off (efficiency vs. complexity): WebSocket maximizes transmission efficiency by minimizing frame overhead, but operational complexity and memory cost rise greatly versus HTTP due to stateful connections, sticky sessions, the broker backplane, heartbeats, and so on. One must verify via quantitative analysis (concurrent-connection count, message frequency, latency requirements) whether the efficiency gain exceeds this cost.
  • Security and governance perspective: Given the characteristics of an always-on connection and automatic cookie attachment, the risks of CSWSH and authentication bypass are ever-present, so Origin validation, token authentication, wss:// encryption, and rate limiting must be built in as standard controls at the design stage. Because always-on encryption makes network-based threat detection difficult, an architecture that shifts the center of gravity of controls to the endpoint/application layer (aligned with zero trust) must proceed in parallel.
  • Availability and failure response: A stateful connection has the characteristic of dropping all at once on server restart or deployment, so one must drain connections gradually during zero-downtime deployment and implement client-side automatic reconnection and backoff as a must. Zombie-connection cleanup and preventing a reconnection thundering herd (applying jitter) are also central to operational design.
  • Outlook and related technologies: WebSocket has an evolution path open via HTTP/2 bootstrapping (RFC 8441) and HTTP/3-based WebTransport, and its importance is expected to grow as demand for low-latency bidirectionality rises—real-time collaboration, the metaverse, IoT telemetry, generative AI's streaming responses (token-by-token transmission), and more. Its linkage with the design of message brokers (Kafka), service mesh, and observability should also be considered together.

References


In one line: WebSocket is a protocol that upgrades a single TCP connection into a full-duplex channel via the HTTP Upgrade handshake so that server and client exchange bidirectional real-time communication with minimal overhead; it suits chat, games, collaboration, and trading where low-latency bidirectionality is essential, but the scalability of stateful connections and CSWSH security must be designed together.