Netty學習(六)—WebSocket通信
WebSocket是一種将Socket套接字引入到B/S架構中,使浏覽器和伺服器之間可以通過套接字建立持久連接配接,雙方都能發送即時消息給對方,而不是傳統模式下請求應答通信方式;
個人首頁:tuzhenyu’s page
原文位址:Netty學習(六)—WebSocket通信
(0) 原理
- HTTP協定的弊端
- HTTP協定是一種被動的半雙工的通信協定,也就是說同一時刻隻有一個方向的資料傳輸;半雙工的原因是通信模式采用的傳統的用戶端主導的請求—應答模式,隻有用戶端發送請求服務端才能發動應答,而服務端不能主動發送應答消息;
- HTTP協定下每進行一次請求—應答通信都需要建立一次TCP連接配接,HTTP1,1對HTTP1.0進行改進,增添了keep-alive功能,也就是在一次TCP連接配接下進行多次請求—應答通信,但是通信仍然采用冗長的HTTP消息格式;
- HTTP協定消息冗長,因為一個完整的HTTP消息包括消息頭,消息體和換行符等,一條消息中的可用資料比例較低,相對于其他通信協定較為冗長;
- HTTP協定被動通信的解決方案
- HTTP是一種被動的單雙工通信協定,隻有用戶端發送請求服務端才能發動應答,而服務端不能主動發送應答消息;在需要服務端主動推送資料的情況下,HTTP協定有極大的局限性能;
- 通過Ajax輪詢的方式,通過前端Ajax技術定時發送輪詢請求,比如1秒發送一次請求,服務端收到請求後立即傳回,如果有資料則發送響應資料;
- 通過長輪詢long poll的方式,發送請求後保持連接配接直到服務端有響應資料傳回,用戶端收到資料後再次發起long poll長輪詢;
- 通過WebSocket方式,建立WebSocket連接配接後進行即時的全雙工的通信;
- WebSocket協定
- WebSocket協定是HTML5中提出的一種全雙工的持久連接配接的通信協定,通過HTTP協定握手建立連接配接後雙方可以進行即時的全雙工通信;
- WebSocket是基于HTTP協定的,借用了HTTP的協定來完成一部分握手,完成握手後直接進行Socket通信;
- WebSocket協定的優勢
- 持久的TCP連接配接,無論是ajax輪詢還是long poll長輪詢都是不斷的建立新TCP連接配接,頻繁的連接配接建立和斷開極大消耗資源;WebSocket通過HTTP協定完成握手建立連接配接後,通過該TCP連接配接進行雙向的通信無需重複建立連接配接;
- WebSocket建立連接配接後的雙向通信不再使用HTTP消息格式,沒有附加的消息頭,身份驗證等
- WebSocket是一種全雙工的通信協定,服務端可以主動發送消息給用戶端,不再需要用戶端輪詢;
(1) WebSocket執行個體
用戶端
- 建立WebSocket連接配接需要發送特定的HTTP消息,普通浏覽器通路無法實作需要在JS腳本裡建立連接配接
- 通過new WebSocket()方法建立WebSocket連接配接
- 回調判斷連接配接狀态觸發相應事件,包括連接配接建立事件,關閉事件,消息接收事件等
var socket;
if (!window.WebSocket){
window.WebSocket = window.MozWebSocket;
}
if(window.WebSocket){
socket = new WebSocket("ws://localhost:8000");
socket.onmessage = function (event) {
var ta = document.getElementById('responseText');
ta.value = "";
ta.value = event.data;
};
socket.onopen = function (event) {
var ta = document.getElementById('responseText');
ta.value = "打開WebSocket正常";
};
socket.onclose = function (event) {
var ta = document.getElementById('responseText');
ta.value = "";
ta.value = "WebSocket關閉";
}
}else {
alert("抱歉,您的浏覽器不支援WebSocket協定");
}
- 發送WebSocket的消息
- 連接配接建立完成後,直接發送webSocekt消息
function send(message) {
if(!window.WebSocket)
return;
if (socket.readyState == WebSocket.OPEN){
console.log("send the massage");
socket.send(message);
}
else
alert("WebSocket 連接配接沒有建立成功");
}
服務端
- 使用Netty建立服務端,往ChannelPipeline管道中添加相應的Handler添加相應的Handler處理器
public void run(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch)
throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("http-codec", new HttpServerCodec());
pipeline.addLast("aggregator",new HttpObjectAggregator());
pipeline.addLast("http-chunked",new ChunkedWriteHandler());
pipeline.addLast("handler", new WebSocketServerHandler());
}
});
Channel ch = b.bind(port).sync().channel();
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
- 消息經過管道傳遞到自定義的處理器WebSocketServerHander中
- 調用channelRead()方法判斷傳入的消息類型,分為傳統HTTP消息和WebSocket消息
public void channelRead0(ChannelHandlerContext ctx, Object msg)
throws Exception {
if (msg instanceof FullHttpRequest) {
handleHttpRequest(ctx, (FullHttpRequest) msg); // 傳統的HTTP接入
}else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg); // WebSocket接入
}
}
- 對于傳統的HTTP進行WebSocket連接配接握手處理
private void handleHttpRequest(ChannelHandlerContext ctx,
FullHttpRequest req) throws Exception {
// 如果HTTP解碼失敗,傳回HHTP異常
if (!req.getDecoderResult().isSuccess()
|| (!"websocket".equals(req.headers().get("Upgrade")))) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1,
BAD_REQUEST));
return;
}
// 構造握手響應傳回,本機測試
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
"ws://localhost:8080/websocket", null, false);
handshaker = wsFactory.newHandshaker(req);
if (handshaker == null) {
WebSocketServerHandshakerFactory
.sendUnsupportedWebSocketVersionResponse(ctx.channel());
} else {
handshaker.handshake(ctx.channel(), req);
}
}
- 對于WebSocket消息,對其指令類型判斷後進行處理,WebSocket消息類型分為:關閉連接配接指令,Ping心跳消息和普通消息等;
private void handleWebSocketFrame(ChannelHandlerContext ctx,
WebSocketFrame frame) {
// 判斷是否是關閉鍊路的指令
if (frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(),
(CloseWebSocketFrame) frame.retain());
return;
}
// 判斷是否是Ping消息
if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(
new PongWebSocketFrame(frame.content().retain()));
return;
}
// 本例程僅支援文本消息,不支援二進制消息
if (!(frame instanceof TextWebSocketFrame)) {
throw new UnsupportedOperationException(String.format(
"%s frame types not supported", frame.getClass().getName()));
}
// 傳回應答消息
String request = ((TextWebSocketFrame) frame).text();
if (!"start".equals(request)){
stop = true;
System.out.println("stop");
}
if (logger.isLoggable(Level.FINE)) {
logger.fine(String.format("%s received %s", ctx.channel(), request));
}
while (!stop){
try{
ctx.channel().writeAndFlush(
new TextWebSocketFrame("歡迎使用Netty WebSocket服務,現在時刻:"
+ new java.util.Date().toString()));
Thread.sleep();
}catch (Exception e){
e.printStackTrace();
}
}
}
總結
- WebSocket通信是一種對HTTP長連接配接keep-alive的一種改進,通過HTTP協定完成握手連接配接後能夠進行雙向即時通信,無需重複建立TCP連接配接并且服務端能夠主動的發送消息;