应用http协定的问题

场景:客户端的展现随着服务端保护的状态的扭转而实时扭转。

可能会采取的形式:

1 轮询:client按设置好的工夫距离被动拜访server,有可能server返回有用数据,有可能server无有用数据返回,但都是一个建设连贯-request-response-敞开连贯的过程。

2 长轮询:client拜访server,若server有数据返回,则返回,敞开连贯,client持续发申请;若server没有数据返回,连贯放弃,期待直到server有数据返回,敞开连贯,client持续发申请。建设连贯-request-(wait)-response-敞开连贯。

3 推:client和server之间保护长连贯,当server有返回的时候被动推给client。

问题:

http协定是一种无状态的,基于申请响应模式的协定。

半双工协定:能够在客户端和服务端2个方向上传输,然而不能同时传输。同一时刻,只能在一个方向上传输。

响应数据不实时,空轮询对资源的节约。 HTTP音讯简短(轮询中每次http申请携带了大量无用的头信息)。

HTTP1.0 每个申请会关上一个新连贯,个别关上和敞开连贯破费的工夫远大于数据传输的工夫,对于HTTPS更是。

HTTP1.1 服务器不会在发送响应后立刻敞开连贯,能够在同一个socket上期待客户端的新申请

Websocket 协定

WebSocket是一种标准,是Html5标准的一部分。WebSocket通信协议于2011年被IETF定为规范RFC 6455,并被RFC7936所补充标准。WebSocket API也被W3C定为规范。

单个 TCP 连贯上进行全双工通信的协定。

浏览器和服务器只须要实现一次握手,两者之间就间接能够创立持久性的连贯,并进行双向数据传输。websocket是全双工,没有严格的clientserver概念。

opening handshake

request:

GET /chat HTTP/1.1 Host: server.example.comUpgrade: 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依然是httpUpgrade: websocket    //服务端协定已切换到WebSocketConnection: 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
close handshake

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。

websocket简略实现

Java Websocket示例
//注解式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>
netty对websocket的反对
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>

netty-socketio实现,即基于netty的java版socket io Server