关于websocket:websocket协议

4次阅读

共计 8693 个字符,预计需要花费 22 分钟才能阅读完成。

应用 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.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
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
正文完
 0