Ajax
Ajax 是通过 JavaScript 在页面内发起异步 HTTP 请求(通常使用 XMLHttpRequest 或 fetch),服务端返回响应数据(现代多为 JSON,早期为 XML),前端根据返回内容更新页面的局部 DOM,从而实现无需整页刷新的动态交互效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Ajax JSON 示例</title> </head> <body> <h2>🧾 服务器数据:</h2> <div id="result">等待数据中...</div>
<script> function getData() { fetch('/api/data') .then(response => { if (!response.ok) { throw new Error('网络响应失败'); } return response.json(); }) .then(data => { document.getElementById('result').innerText = `消息:${data.message}`; }) .catch(error => { document.getElementById('result').innerText = '请求失败:' + error.message; }); } getData(); setInterval(getData, 5000); </script> </body> </html>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const express = require('express'); const app = express();
app.get('/api/data', (req, res) => { const result = { message: '你好,这是来自服务器的 JSON 数据!时间:' + new Date().toLocaleTimeString() }; res.json(result); });
app.listen(3000, () => { console.log('服务运行在 <http://localhost:3000>'); });
|
Long Poll
Long Poll 是由客户端发起一个请求,当服务端没有信息更新时,会处于阻塞状态,直到有更新后,由服务器响应给客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Long Polling 示例</title> </head> <body> <h2>📡 消息推送:</h2> <div id="box">等待服务器推送消息...</div>
<script> function startLongPoll() { fetch('/longpoll') .then(res => res.json()) .then(data => { if (data.message) { document.getElementById('box').innerText = data.message; } startLongPoll(); }) .catch(err => { document.getElementById('box').innerText = '连接失败,重试中...'; setTimeout(startLongPoll, 3000); }); }
startLongPoll(); </script> </body> </html>
|
服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| const express = require('express'); const app = express();
let latestMessage = '暂无新消息'; let waitClients = [];
setInterval(() => { latestMessage = '新消息时间:' + new Date().toLocaleTimeString();
waitClients.forEach(res => res.json({ message: latestMessage })); waitClients = []; }, 10000);
app.get('/longpoll', (req, res) => { waitClients.push(res);
setTimeout(() => { const index = waitClients.indexOf(res); if (index !== -1) { waitClients.splice(index, 1); res.json({ message: null }); } }, 30000); });
app.listen(3000, () => { console.log('服务运行在 <http://localhost:3000>'); });
|
WebSocket
WebSocket 是通过 HTTP 请求建立的连接,在成功升级协议后切换为全双工通信,允许客户端和服务端彼此主动发送消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| socket = new WebSocket("ws://192.168.0.5:18899/ws","echo");
socket.onopen = function(event){ var target = document.getElementById("responseText"); target.value = "Web Socket 连接已经开启"; };
socket.onmessage = function(event){ var ta = document.getElementById("responseText"); ta.value = ta.value + '\\n' + event.data };
<script type="text/javascript"> var socket ; if(!window.WebSocket){ window.WebSocket = window.MozWebSocket; } var domain = window.location.host; if(window.WebSocket){ socket = new WebSocket("ws://" + domain +"/ws" ,"echo"); socket.onmessage = function (event){ var ta = document.getElementById("responseText"); ta.value = ta.value + '\\n' + event.data }; socket.onopen = function(event){ var target = document.getElementById("responseText"); target.value = "Web Socket 连接已开启!"; }; socket.onclose = function(event){ var target = document.getElementById("responseText"); target.value = ta.vale + "WebSocket 已断开"; }; }else { alert("Your browser does not support Web Socket"); }
function send(message){ if(!windows.WebSocket){ return ; } if (socket.readyState == WebSocket.OPEN){ socket.send(message); }else { alert("The Socket is not open"); } } </script>
|
服务器端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| BinaryWebSocketFrame 封装二进制数据的WebSocketFrame数据帧 TextWebSocketFrame 封装文本数据的WebSocketFrame数据帧 CloseWebSocketFrame 表示一个Close结束请求 ContinuationWebSocketFrame 分拆为多个WebSocket数据帧后,用于发送后续内容的数据帧 PingWebSocketFrame 心跳帧,一般用于客户端发送 PongWebSocketFrame 心跳帧,一般用于服务端发送
WebSocketServerProtocolHandler 负责开启处理握手过程,以及保活/关闭处理 WebSocketServerProtocolHandshakeHandler 负责进行协议升级握手处理 WebSocketSocketFrameEncoder 数据帧编码器,负责WebSocket数据帧编码。 WebSocketSocketFrameDecoder 数据帧解码器,负责WebSocket数据帧解码
@Slf4j public final class WebSocketEchoServer{ static class EchoInitializer extends ChannelInitializer<SocketChannel>{ @Override public void initChannel(SocketChannel ch){ ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpRequestDecoder()); pipeline.addLast(new HttpResponseEncoder()); pipeline.addLast(new HttpObjectAggregator(65535)); pipeline.addLast(new WebSocketServerProtocolHandler("/ws","echo",true,10*1024)); pipeline.addLast(new WebPageHandler()); pipeline.addLast(new TextWebSocketFrameHandler()); } } public static void start(String ip ) throws Exception{ EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try{ ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.DEBUG)) .childHandler(new EchoInitializer()); Channel ch = b.bind(18899).sync().channel(); log.info("WebSocket服务已启动http://{}:{}",ip,18899); ch.closeFutrue().sync(); }finally{ bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> { @Override protected void channelRead(ChannelHandlerContext ctx , WebSocketFrame frame) throws Exception { if(frame instanceof TextWebSocektFrame){ String request = ((TextWebSocketFrame) frame).text(); log.debug("服务端收到:"+ request); String echo = Dateutil.getTime() + ":" + request; TextWebSocketFream echoFrame = new TextWebSocketFrame(echo); ctx.channel().writeAndFlush(echoFrame); } else { String message = "unsupported frame type:" + frame.getClass().getName(); throw new UnsupportedOperationException(message); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if(evt instanceof WebSocketServerProtocolHandler.HandshakeComplete){ ctx.pipeline().remove(WebPageHandler.class); log.debug("WebSock HandShakeComplete 握手成功"); log.debug("新的WebSocket客户端加入,通道为:"+ ctx.channel()); } else { super.userEventTriggered(ctx,evt); } } }
|
WebSocket 升级过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 🧑 Client 🌐 Network 🖥️ Server
┌────────────┐ ┌─────────────┐ │JS 发起请求 │ ── GET / Upgrade: websocket ──▶ │HTTP Server │ └────────────┘ └─────────────┘ │验证 Sec-Key │ │返回 101 升级│ ◀────────────── 101 Switching Protocols ───────── ▼ 升级完成
🚀 WebSocket 建立,双向通信开始 ⇄ socket.send() ⇄ socket.onmessage()
❌ 发送 Close 帧 ▼ TCP 连接关闭
|
客户端基于HTTP 发送升级请求,请求首部包含以下内容 :
1 2 3 4 5 6 7
| GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== ← 客户端随机Base64编码值 Sec-WebSocket-Version: 13 ← 使用的WebSocket协议版本 Sec-WebSocket-Protocol: echo\\r\\n ← 可以用于描述自定义的子协议
|
服务端响应
1 2 3 4 5
| HTTP/1.1 101 Switching Protocols ← 响应码为101 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= ← 服务端计算后的 key 验证结果 Sec-WebSocket-Protocol: echo\\r\\n ← 用于描述自定义的子协议
|
报文格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 0 1 2 3 +---------------+---------------+---------------+---------------+ |FIN| RSV1|RSV2|RSV3| OPCODE | MASK | Payload length (7) | +---------------+---------------+-------------------------------+ | Extended payload length (16/64 bits, optional) | +---------------------------------------------------------------+ | Masking key (32 bits, optional) | +---------------------------------------------------------------+ | Payload data (x bytes) | +---------------------------------------------------------------+ FIN 1 bit 是否为消息的最后一帧(1 表示是) RSV1~3 3 bit 通常为 0,保留扩展用 OPCODE 4 bit 表示帧类型(见下) MASK 1 bit 表示 Payload 是否被掩码(客户端必须为 1) Payload length 7 bit 表示数据长度(0~125) 特殊值:126 = 16bit 扩展,127 = 64bit 扩展 Extended Payload Length 0/2/8 字节 当长度为 126 或 127 时才出现 Masking key 4 字节 仅客户端发送时存在,用于解码 payload Payload data 任意 真正要传输的数据(文本/二进制)
|
OPCODE (报文类型)
OPCODE |
类型 |
含义 |
0x0 |
Continuation |
后续帧(用于分片) |
0x1 |
Text Frame |
文本帧(UTF-8 编码) |
0x2 |
Binary Frame |
二进制帧 |
0x8 |
Connection Close |
关闭连接 |
0x9 |
Ping |
心跳探测(客户端发送) |
0xA |
Pong |
心跳回应 |