場景:客戶端的展現隨着服務端維護的狀態的改變而實時改變。javascript
可能會採起的方式:html
1 輪詢:client按設置好的時間間隔主動訪問server,有可能server返回有用數據,有可能server無有用數據返回,但都是一個創建鏈接-request-response-關閉鏈接的過程。java
2 長輪詢:client訪問server,若server有數據返回,則返回,關閉鏈接,client繼續發請求;若server沒有數據返回,鏈接保持,等待直到server有數據返回,關閉鏈接,client繼續發請求。創建鏈接-request-(wait)-response-關閉鏈接。web
3 推:client和server之間維護長鏈接,當server有返回的時候主動推給client。編程
問題:bootstrap
http協議是一種無狀態的,基於請求響應模式的協議。瀏覽器
半雙工協議:能夠在客戶端和服務端2個方向上傳輸,可是不能同時傳輸。同一時刻,只能在一個方向上傳輸。服務器
響應數據不實時,空輪詢對資源的浪費。 HTTP消息冗長(輪詢中每次http請求攜帶了大量無用的頭信息)。websocket
HTTP1.0 每一個請求會打開一個新鏈接,通常打開和關閉鏈接花費的時間遠大於數據傳輸的時間,對於HTTPS更是。session
HTTP1.1 服務器不會在發送響應後當即關閉鏈接,能夠在同一個socket上等待客戶端的新請求
WebSocket是一種規範,是Html5規範的一部分。WebSocket通訊協議於2011年被IETF定爲標準RFC 6455,並被RFC7936所補充規範。WebSocket API也被W3C定爲標準。
單個 TCP 鏈接上進行全雙工通信的協議。
瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。websocket是全雙工,沒有嚴格的clientserver概念。
request:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket //請求升級到WebSocket 協議 Connection: Upgrade //通道類型,keep-alive:通道長連,close:請求完畢後通道斷開,Upgrade:升級協議 Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //客戶端隨機生成的Key,校驗服務器合法性,生成方式:隨機16字節再被base64編碼 Sec-WebSocket-Protocol: chat, superchat //子協議,特定於具體應用的消息格式或編排 Sec-WebSocket-Version: 13 Origin: http://example.com
response:
HTTP/1.1 101 Switching Protocols //非101仍然是http Upgrade: websocket //服務端協議已切換到WebSocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= //indicates whether the server is willing to accept the connection. //用於校驗WebSocket服務端是否合法,生成方式:客戶端請求參數中的 Sec-WebSocket-Key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11(GUID), //SHA-1 hash 再進行base64 //GUID:which is unlikely to be used by network endpoints that do not understand the WebSocket protocol. Sec-WebSocket-Protocol: chat
fin:0,後續還有幀;1 本條消息的最後一幀
rsv1,rsv2,rsv3:不實用擴展協議,爲0
Opcode:
websocket協議:https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 https://tools.ietf.org/html/rfc6455#section-5
4個生命週期事件:
打開事件 OnOpen;消息事件 OnMessage;錯誤事件 OnError;關閉事件 OnClose。
//註解式 package echo; import javax.websocket.OnMessage; import javax.websocket.server.ServerEndpoint; @ServerEndpoint(value = "/echo") public class EchoServer { @OnMessage public String echo(String message){ return "I get this ("+message +") so I am sending it back"; } } //編程式 package echo; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; import java.io.IOException; /** * 編程式websocket */ public class ProgrammaticEchoServer extends Endpoint { @Override public void onOpen(Session session, EndpointConfig endpointConfig) { final Session mySession = session; mySession.addMessageHandler( new MessageHandler.Whole<String>() { @Override public void onMessage(String s) { try { mySession.getBasicRemote().sendText("I got this by prommmatic method ("+ s+") so I am sending it back "); }catch (IOException io){ io.printStackTrace(); } } }); } } package echo; import javax.websocket.Endpoint; import javax.websocket.server.ServerApplicationConfig; import javax.websocket.server.ServerEndpointConfig; import java.util.HashSet; import java.util.Set; public class ProgrammaticEchoServerAppConfig implements ServerApplicationConfig { @Override public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> set) { Set configs = new HashSet<ServerEndpointConfig>(); ServerEndpointConfig curSec = ServerEndpointConfig.Builder.create(ProgrammaticEchoServer.class,"/programmaticecho").build(); configs.add(curSec); return configs; } /** * 獲取全部經過註解註冊的endpoint * @param set * @return */ @Override public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> set) { return null; } } //客戶端 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title> new document </title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Web Socket Echo CLient</title> <script language="javascript" type="text/javascript"> var echo_websocket; function init() { output = document.getElementById("output"); } function send_echo() { var wsUri = "ws://localhost:8080/programmaticecho";//註解式爲ws://localhost:8080/echo writeToScreen("Connecting to "+wsUri); echo_websocket = new WebSocket(wsUri); echo_websocket.onopen = function (evt) { writeToScreen("Connected!"); doSend(textID.value); }; echo_websocket.onmessage = function (evt) { writeToScreen("Received message: "+evt.data); echo_websocket.close(); }; echo_websocket.onerror = function (evt) { writeToScreen('<span style= "color"red;">ERROR:<span> '+evt.data); echo_websocket.close(); }; } function doSend(message) { echo_websocket.send(message); writeToScreen("Sent message: "+message); } function writeToScreen(message) { var pre = document.createElement("p"); pre.style.wordWrap = "break-word"; pre.innerHTML = message; output.appendChild(pre); } window.addEventListener("load",init,false); </script> </head> <body> <h1>Echo Server</h1> <div style="text-align: left;"> <form action=""\> <input onclick="send_echo()" value="Press to send" type = "button"/> <input id="textID" name = "message" value="Hello Web Sockets" type = "text"/> </form> </div> <br/> <div id="output"></div> </body> </html>
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoop; import io.netty.channel.nio.NioEventLoopGroup; 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.InetSocketAddress; public class WebsocketNettyServer { public static void main(String[] args) throws Exception{ EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup wokerGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,wokerGroup).channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>(){ protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); //websocket協議自己是基於http協議的,因此這邊也要使用http解編碼器 pipeline.addLast(new HttpServerCodec()); //以塊的方式來寫的處理器 pipeline.addLast(new ChunkedWriteHandler()); //netty是基於分段請求的,HttpObjectAggregator的做用是將請求分段再聚合,參數是聚合字節的最大長度 pipeline.addLast(new HttpObjectAggregator(8192)); //ws://server:port/context_path //參數指的是contex_path pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); //websocket定義了傳遞數據的6中frame類型 pipeline.addLast(new TextWebSocketFrameHandler()); } }); ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8899)).sync(); channelFuture.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); wokerGroup.shutdownGracefully(); } } } import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{ @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception { System.out.println("收到消息"+textWebSocketFrame.text()); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { System.out.println("有新的鏈接加入"); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { System.out.println("鏈接關閉"); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("啊哦,出錯了"); ctx.fireExceptionCaught(cause); } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>WebSocket客戶端</title> </head> <body> <script type="text/javascript"> var socket; //若是瀏覽器支持WebSocket if(window.WebSocket){ //參數就是與服務器鏈接的地址 socket = new WebSocket("ws://localhost:8899/ws"); //客戶端收到服務器消息的時候就會執行這個回調方法 socket.onmessage = function (event) { var ta = document.getElementById("responseText"); ta.value = ta.value + "\n"+event.data; } //鏈接創建的回調函數 socket.onopen = function(event){ var ta = document.getElementById("responseText"); ta.value = "鏈接開啓"; } //鏈接斷掉的回調函數 socket.onclose = function (event) { var ta = document.getElementById("responseText"); ta.value = ta.value +"\n"+"鏈接關閉"; } }else{ alert("瀏覽器不支持WebSocket!"); } //發送數據 function send(message){ if(!window.WebSocket){ return; } //當websocket狀態打開 if(socket.readyState == WebSocket.OPEN){ socket.send(message); }else{ alert("鏈接沒有開啓"); } } </script> <form onsubmit="return false"> <textarea name = "message" style="width: 400px;height: 200px"></textarea> <input type ="button" value="發送數據" onclick="send(this.form.message.value);"> <h3>服務器輸出:</h3> <textarea id ="responseText" style="width: 400px;height: 300px;"></textarea> <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空數據"> </form> </body> </html>