WebSockets 教學:運作原理與適用時機
WebSockets tutorial: how they work and when to use them
WebSockets 透過 HTTP 的 Upgrade 握手建立持續連線,讓瀏覽器與伺服器可隨時互相傳送訊息,適合需要即時推送的情況。文章以 Flask、Flask-Sock 和原生 JavaScript 示範伺服器主動發送事件及回覆瀏覽器訊息。作者建議只在需要伺服器主動推送時使用 WebSockets,其餘情況保留 HTTP,並按頁面協定選用 ws:// 或 wss://。
The short version
- A WebSocket is a connection that stays open between the browser and the server, so either side can send a message at any time.
- HTTP is request and response: the server can only answer when the client asks. WebSockets are events, and neither side has to reply.
- A WebSocket starts as an HTTP request with an Upgrade header, and the same connection then carries WebSocket messages.
- Use WebSockets when the server has to push updates without being asked, like chat messages or progress from a long-running job. Keep HTTP for everything else.
- Use wss:// rather than ws://, and pick it from the page's own protocol so the same code works locally and in production.
A WebSocket is a connection between a browser and a server that stays open, so either side can send a message whenever it wants. Plain HTTP can't do that. With HTTP the server only ever answers requests, so if the client doesn't ask, the server has no way to tell it anything.
That one difference decides when you need WebSockets. Below I go through how HTTP works, how WebSockets work, when to pick each one, and then a small working demo with a Flask server and plain JavaScript in the browser. The video above covers the same ground if you'd rather watch.
How HTTP works: the client asks, the server answers
Every website you visit runs on HTTP. It's a request-response protocol. Your browser (the client) sends a request to the server for index.html, the server sends back the HTML, and the browser renders the page. Then the browser asks for the site's icon, and the server sends the icon back.
Every exchange starts with the client. If the browser never asks for the icon, the server never sends it, because there's no request to respond to. For loading pages and calling most APIs, that's exactly what you want.
The problem HTTP can't solve
Say you're building a messaging app like WhatsApp or Discord. Other people send messages to the server all the time, and your browser has to show them as they arrive. But your browser doesn't know when a new message has landed, so it doesn't know when to ask.
The usual workaround is polling: the browser asks "anything new?" every few seconds. Most of those requests come back empty, and a message can still sit on the server for a few seconds before the next poll picks it up. It works, but it's the wrong shape for the problem. What you want is for the server to say "here's a new message" the moment it has one.
How WebSockets work: events in both directions
A WebSocket starts life as an HTTP request. The browser sends a normal request with an Upgrade: websocket header, the server answers with 101 Switching Protocols, and from then on that same connection stays open and carries WebSocket messages instead of HTTP. That's why people describe WebSockets as an upgrade of HTTP.
After the upgrade, the model is completely different. HTTP is request and response. WebSockets are events. The client can send an event and the server can respond, or the server can send an event and the client can respond, and neither side has to respond at all. Replying is good practice, but nothing in the protocol requires it.
So a real conversation over a WebSocket can look like this: the server sends an event, the client says nothing back, the server sends another one, the client sends one of its own, and then the server sends two more. No request ever has to come first.
WebSocket vs HTTP
| HTTP | WebSocket | |
|---|---|---|
| Who can start an exchange | Only the client | Either side |
| Connection | A request, then a response | Stays open until one side closes it |
| Does every message need a reply? | Yes, every request gets a response | No, a reply is optional |
| URL scheme |
http:// and https://
|
ws:// and wss://
|
| Good for | Pages and most APIs | Chat, games, live updates, notifications |
Use WebSockets when the server has to push something to the client without being asked. Keep HTTP for everything else. They aren't competitors, and most real apps use both: HTTP to load the page and fetch data, and one WebSocket for the live parts.
When to use WebSockets
These are the cases where I'd reach for them:
- A messaging app, where new messages have to show up the moment someone sends them.
- A multiplayer game, where every player needs to see what the others do many times a second, and sends their own moves up the same connection.
- A long-running job on the server. It can send an update to the browser as each step finishes, and the user watches it happen instead of refreshing.
- A shared document or whiteboard, where everyone sees each other's edits as they type.
If only the server ever needs to talk, Server-Sent Events are a simpler option worth a look, since they run over plain HTTP. If neither side needs to push anything, you don't need either one.
Build a WebSocket server in Flask
Here's a minimal app to see all of this working. The server sends random events to the browser on its own schedule, and whenever the browser sends a message, the server replies with "Message received".
I'm using Python with Flask and the Flask-Sock extension, and plain JavaScript in the browser with no libraries. Most people will be on Node.js, and that's fine: the server has the same shape in any language, and the browser code is identical.
Install the two packages:
pip install flask flask-sock
The project has three files:
websockets-demo/
├── app.py
├── templates/
│ └── index.html
└── static/
└── index.js
Here's the server, app.py:
from flask import Flask, render_template
from flask_sock import Sock
from simple_websocket import ConnectionClosed
app = Flask(__name__)
sock = Sock(app)
EVENTS = [
"New order received",
"User signed up",
"Report finished generating",
"Payment processed",
"Backup completed",
]
@app.route("/")
def index():
return render_template("index.html")
@sock.route("/ws")
def websocket(ws):
# Two threads send on this socket, so they take turns
send_lock = threading.Lock()
def send(message):
with send_lock:
ws.send(message)
def background_sender():
while ws.connected:
time.sleep(random.uniform(1, 3))
try:
send(random.choice(EVENTS))
except ConnectionClosed:
break
threading.Thread(target=background_sender, daemon=True).start()
# receive() blocks until the client sends something, and raises
# ConnectionClosed when it leaves, which Flask-Sock handles for us
while True:
data = ws.receive()
send(f"Message received: {data}")
if __name__ == "__main__":
app.run(debug=True)
The / route is plain HTTP. It renders the page, and that's all it does.
The /ws route is the WebSocket. Flask-Sock handles the upgrade handshake and hands the function a ws object for that one client. The function then does two jobs at once:
- It starts a background thread running
background_sender, which sends a random event from the list every one to three seconds, for as long as the client stays connected. This is the part HTTP can't do: the server talks without being asked. - It sits in a loop waiting for the client. Every time a message arrives, it sends back "Message received" with the text.
Both of those send on the same socket from different threads, so they share a lock and never write at the same moment. When the browser tab closes, receive() raises ConnectionClosed, Flask-Sock cleans up, and the background thread stops within a few seconds.
The browser side: the native WebSocket API
Browsers have WebSockets built in, so the client needs no library. The page itself is a text box plus a list the messages go into. Here's templates/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WebSockets demo</title>
</head>
<body>
<p>Status: <strong id="status">Connecting...</strong></p>
<form id="form">
<input id="message" placeholder="Say something" autocomplete="off">
<button>Send</button>
</form>
<ul id="log"></ul>
<script src="{{ url_for('static', filename='index.js') }}"></script>
</body>
</html>
And static/index.js, which is where the WebSocket lives:
// Same rule as http and https: use the secure wss:// when the page itself is on https
const scheme = location.protocol === "https:" ? "wss:" : "ws:";
const socket = new WebSocket(`${scheme}//${location.host}/ws`);
const statusText = document.getElementById("status");
const log = document.getElementById("log");
function addLine(text) {
const item = document.createElement("li");
item.textContent = text;
log.append(item);
}
socket.addEventListener("open", () => {
statusText.textContent = "Connected";
});
socket.addEventListener("message", (event) => {
addLine(`Server: ${event.data}`);
});
socket.addEventListener("close", () => {
statusText.textContent = "Disconnected";
});
socket.addEventListener("error", () => {
addLine("The connection had an error");
});
document.getElementById("form").addEventListener("submit", (event) => {
event.preventDefault();
const input = document.getElementById("message");
if (socket.readyState === WebSocket.OPEN && input.value) {
socket.send(input.value);
addLine(`You: ${input.value}`);
input.value = "";
}
});
The first two lines pick the URL. The difference between ws:// and wss:// is the same as the difference between http:// and https://: one is encrypted and the other isn't. Use the secure one. Deriving it from the page's own protocol means the same code works on your laptop over http and in production over https. Browsers also block a plain ws:// connection from a page served over https, so getting this wrong breaks the app in production.
The rest is event handlers. open fires when the connection is up, message fires for every event the server sends, close fires when either side ends the connection, and error fires when something goes wrong. socket.send() is how the browser sends its own events, and the form calls it when you press Send.
Run it
From the project folder:
flask --app app run
Open http://127.0.0.1:5000. The status switches to "Connected", and within a few seconds events start appearing in the list, without you sending anything. Type "hello" and press Send, and the server answers with "Message received: hello" in between its own events.
Watch the list for a minute and you'll see the server keep talking to the browser without a single request from it.
Learn how it works, and let AI write the syntax
I didn't spend long on the code in the video, and I'd do the same here. Everyone uses a different stack, and AI writes most of the code for us now anyway. Ask any AI tool for "a WebSocket server in Express" and you'll get something close to the Flask code above in a few seconds.
What AI can't do for you is decide whether you need a WebSocket in the first place. That takes knowing how the protocols work under the hood: HTTP only answers, a WebSocket lets either side speak, and each one fits different problems. WebSockets aren't better than HTTP, and you shouldn't use them everywhere. When the client and server need to talk in real time without waiting on requests and responses, that's where they're the right tool.
Originally published on hassandev.me.
來源:DEV Community · dev.to