一、WebSocket的簡介及優勢
WebSocket 是一種網絡通信協議。RFC6455 定義了它的通信標准。WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。
首先可以看下HTTP協議的有哪些不好的地方:HTTP 協議是一種無狀態的、無連接的、單向的應用層協議。它采用了請求/響應模型。通信請求只能由客戶端發起,服務端對請求做出應答處理。這種通信模型有一個弊端:HTTP 協議無法實現服務器主動向客戶端發起消息。
為了解決這些痛點,WebSocket就應運而生了。WebSocket 連接允許客戶端和服務器之間進行全雙工通信,以便任一方都可以通過建立的連接將數據推送到另一端。WebSocket 只需要建立一次連接,就可以一直保持連接狀態。
二、WebSocket客戶端API
2.1 WebSocket 構造函數
var Socket=new WebSocket("ws://localhost:20000/web");
2.2 WebSocket 屬性
以下是 WebSocket 對象的屬性。假定我們使用了以上代碼創建了 Socket 對象:
屬性 | 描述 |
---|---|
Socket.readyState | 只讀屬性 readyState 表示連接狀態,可以是以下值:0 - 表示連接尚未建立。1 - 表示連接已建立,可以進行通信。2 - 表示連接正在進行關閉。3 - 表示連接已經關閉或者連接不能打開。 |
Socket.bufferedAmount | 只讀屬性 bufferedAmount 已被 send() 放入正在隊列中等待傳輸,但是還沒有發出的 UTF-8 文本字節數。 |
2.3 WebSocket 事件
以下是 WebSocket 對象的相關事件。假定我們使用了以上代碼創建了 Socket 對象:
事件 | 事件處理程序 | 描述 |
---|---|---|
open | Socket.onopen | 連接建立時觸發 |
message | Socket.onmessage | 客戶端接收服務端數據時觸發 |
error | Socket.onerror | 通信發生錯誤時觸發 |
close | Socket.onclose | 連接關閉時觸發 |
2.4 WebSocket 方法
以下是 WebSocket 對象的相關方法。假定我們使用了以上代碼創建了 Socket 對象:
方法 | 描述 |
---|---|
Socket.send() | 使用連接發送數據 |
Socket.close() | 關閉連接 |
三、WebSocket客戶端代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Netty WebSocket</title> </head> <body> <br> <script type="text/javascript"> var websocket; if(!window.WebSocket){ window.WebSocket=window.MozWebSocket; } if(window.WebSocket){ websocket=new WebSocket("ws://localhost:20000/web"); websocket.onmessage=function (event) { console.log("websocket接受消息"+event.data); var text=document.getElementById('responseText'); text.value=""; text.value=event.data; } websocket.onopen=function (event) { console.log("websocket打開"); var text=document.getElementById('responseText'); text.value=""; text.value="打開websocket服務正常"; } websocket.onclose=function (event) { console.log("websocket關閉"); var text=document.getElementById('responseText'); text.value=""; text.value="關閉websocket服務"; } websocket.onerror=function (event) { console.log("websocket異常"); var text=document.getElementById('responseText'); text.value=""; text.value="websocket服務異常"; } }else{ alert("你的瀏覽器不支持WebSocket"); } function send(message) { if(websocket){ if(websocket.readyState==WebSocket.OPEN){ console.log("通過websocket發送消息"); websocket.send(message); } }else{ alert("未建立websocket連接"); } } </script> <form onsubmit="return false;"> <input type="text" name="message" value="Netty實踐"/> <br><br> <input type="button" value="發送消息" onclick="send (this.form.message.value)"/> <h3>應答消息</h3> <textarea id="responseText" style="width: 500px;height: 300px;"></textarea> </form> </body> </html>
四、netty服務端代碼
4.1 netty啟動類
public class NettyServer { public static void main(String[] args) throws Exception{ //服務器啟動 new NettyServer().start(20000); } public void start(int port) throws Exception{ //用於監聽連接的線程組 EventLoopGroup bossGroup=new NioEventLoopGroup(); //用於發送接收消息的線程組 EventLoopGroup workGroup=new NioEventLoopGroup(); try{ //啟動類引導程序 ServerBootstrap b=new ServerBootstrap(); //綁定兩個線程組 b.group(bossGroup,workGroup); //設置非阻塞,用它來建立新accept的連接,用於構造serverSocketChannel的工廠類 b.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel channel){ ChannelPipeline channelPipeline=channel.pipeline(); // HttpServerCodec:將請求和應答消息解碼為HTTP消息 channelPipeline.addLast(new HttpServerCodec()); // HttpObjectAggregator:將HTTP消息的多個部分合成一條完整的HTTP消息 channelPipeline.addLast(new HttpObjectAggregator(65536)); // ChunkedWriteHandler:向客戶端發送HTML5文件 channelPipeline.addLast(new ChunkedWriteHandler()); //在管道中添加自己實現的Handler處理類 channelPipeline.addLast(new WebsocketServerHandler()); } }); Channel channel=b.bind(port).sync().channel(); System.out.println("服務器啟動端口:"+port); channel.closeFuture().sync(); }finally { workGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } }
4.2 netty的業務處理的Handler類
public class WebsocketServerHandler extends SimpleChannelInboundHandler<Object> { private WebSocketServerHandshaker handshaker; @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception { //傳統的http接入 if(o instanceof FullHttpRequest){ handleHttpRequest(channelHandlerContext,(FullHttpRequest) o); } //webSocket接入 else if(o instanceof WebSocketFrame){ handleWebsocketFrame(channelHandlerContext,(WebSocketFrame) o); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req){ //構造握手響應返回 WebSocketServerHandshakerFactory webSocketServerHandshakerFactory=new WebSocketServerHandshakerFactory("ws://localhost:20000/web",null,false); handshaker=webSocketServerHandshakerFactory.newHandshaker(req); handshaker.handshake(ctx.channel(),req); } 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; } //文本消息處理 String request=((TextWebSocketFrame)frame).text(); System.out.println("接受的信息是:"+request); String date=new Date().toString(); //將接收消息寫回給客戶端 ctx.channel().write(new TextWebSocketFrame("現在時刻:"+date+"發送了:"+request)); } }
五、運行成功截圖
參考資料: