场景:客户端的展现随着服务端维护的状态的改变而实时改变。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>