Netty案例介紹(websocket服務)
- 2020 年 1 月 26 日
- 筆記
WebSocket案例
1.需求分析
Http協議是無狀態
的, 瀏覽器和伺服器間的請求響應一次,下一次會重新創建連接.所有在有些情況下並不是太適用。這時websocket就是我們的一種實現方案,具體的websocket的內容網上很多,自行查閱哦,本文主要是介紹基於netty如何實現websocket通訊。
要求:
- 實現基於webSocket的長連接的全雙工的交互
- 改變Http協議多次請求的約束,實現長連接了, 伺服器可以發送消息給瀏覽器
- 客戶端瀏覽器和伺服器端會相互感知,比如伺服器關閉了,瀏覽器會感知,同樣瀏覽器關閉了,伺服器會感知
2.案例程式碼實現
2.1 服務端處理器
伺服器處理器中我們僅僅需要處理請求和客戶端連接和埠的情況,具體如下:
package com.dpb.netty.websocket; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import java.time.LocalDateTime; /** * @program: netty4demo * @description: 處理器 * @author: 波波烤鴨 * @create: 2019-12-30 22:39 */ public class SocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception { // 列印接收到的消息 System.out.println("服務端接受到的消息:" + textWebSocketFrame.text()); // 返回消息給客戶端 channelHandlerContext.writeAndFlush(new TextWebSocketFrame("伺服器時間: " + LocalDateTime.now() + " : " + textWebSocketFrame.text())); } /** * 客戶端連接的時候觸發 * @param ctx * @throws Exception */ @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // LongText() 唯一的 ShortText() 不唯一 System.out.println("handlerAdded:" + ctx.channel().id().asLongText()); System.out.println("handlerAdded:" + ctx.channel().id().asShortText()); } /** * 客戶端斷開連接的時候觸發 * @param ctx * @throws Exception */ @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { System.out.println("handlerRemoved:" + ctx.channel().id().asLongText()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("異常發生了..."); ctx.close(); } }
2.2 服務端
服務端程式碼在原有的基礎上需要轉換相關的協議,具體如下:
package com.dpb.netty.websocket; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.stream.ChunkedWriteHandler; import java.net.ServerSocket; /** * @program: netty4demo * @description: 基於WebSocket協議的服務端 * @author: 波波烤鴨 * @create: 2019-12-30 22:31 */ public class SocketServerDemo { public static void main(String[] args) throws Exception { // 1.創建對應的EventLoopGroup對象 EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); try{ bootstrap.group(bossGroup,workGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { // websocket 相關的配置 ChannelPipeline pipeline = socketChannel.pipeline(); //因為基於http協議,使用http的編碼和解碼器 pipeline.addLast(new HttpServerCodec()); //是以塊方式寫,添加ChunkedWriteHandler處理器 pipeline.addLast(new ChunkedWriteHandler()); /* 說明 1. http數據在傳輸過程中是分段, HttpObjectAggregator ,就是可以將多個段聚合 2. 這就就是為什麼,當瀏覽器發送大量數據時,就會發出多次http請求 */ pipeline.addLast(new HttpObjectAggregator(8192)); /* 說明 1. 對應websocket ,它的數據是以 幀(frame) 形式傳遞 2. 可以看到WebSocketFrame 下面有六個子類 3. 瀏覽器請求時 ws://localhost:8888/hello 表示請求的uri 4. WebSocketServerProtocolHandler 核心功能是將 http協議升級為 ws協議 , 保持長連接 5. 是通過一個 狀態碼 101 */ pipeline.addLast(new WebSocketServerProtocolHandler("/hello")); // 自定義handler,處理業務邏輯 pipeline.addLast(new SocketServerHandler()); } }); System.out.println("服務啟動了...."); ChannelFuture future = bootstrap.bind(8888).sync(); future.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } }
2.3 客戶端實現
在客戶端中我們就簡單的實現一個HTML頁面,在其中發送websocket協議相關的請求,然後接受相關的消息,如下,注意注釋
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>websocket案例測試</title> </head> <body> <script> var socket; //判斷當前瀏覽器是否支援websocket if(window.WebSocket) { //go on socket = new WebSocket("ws://localhost:8888/hello"); //相當於channelReado, ev 收到伺服器端回送的消息 socket.onmessage = function (ev) { var rt = document.getElementById("responseText"); rt.value = rt.value + "n" + ev.data; } //相當於連接開啟(感知到連接開啟) socket.onopen = function (ev) { var rt = document.getElementById("responseText"); rt.value = "連接開啟了.." } //相當於連接關閉(感知到連接關閉) socket.onclose = function (ev) { var rt = document.getElementById("responseText"); rt.value = rt.value + "n" + "連接關閉了.." } } else { alert("當前瀏覽器不支援websocket") } //發送消息到伺服器 function send(message) { if(!window.socket) { //先判斷socket是否創建好 return; } if(socket.readyState == WebSocket.OPEN) { //通過socket 發送消息 socket.send(message) } else { alert("連接沒有開啟"); } } </script> <form onsubmit="return false"> <textarea name="message" style="height: 300px; width: 300px"></textarea> <input type="button" value="發生消息" onclick="send(this.form.message.value)"> <textarea id="responseText" style="height: 300px; width: 300px"></textarea> <input type="button" value="清空內容" onclick="document.getElementById('responseText').value=''"> </form> </body> </html>
3.測試分析
分別運行伺服器和客戶端,訪問測試,如下:


運行效果:

同時在第一次運行的時候打開調試環境:
