Skip to content

5 Websocket

BladeCode edited this page Nov 23, 2019 · 1 revision

WebSocket 是HTML5 规范的一部分,也是基于 HTTP 协议之上的一种协议,WebSocket主要是解决 HTTP 上存在的一些问题;

  1. HTTP 一种无状态(同一客户端发出的第一次请求接收到响应后,客户端发送第二次请求,这两次请求之间没有任何关联)的协议,HTTP 无法追踪某一请求来自哪一个客户端,客户端之前在服务器上存在一些信息(常见的解决方式:cookie,session)
  2. HTTP 是基于请求响应模式的协议,请求的发起方一定是客户端,服务器将响应返回给客户端后连接就断掉了(HTTP 1.0),在 1.0 的基础上连接可以短时间的保持,一种 keep-alive 机制(HTTP 1.1)

通常我们所使用的长连接技术

  • 早期采用轮询的方式保持与服务器的连接
  • 目前通常采用 Websocket 的连接方式保持与服务器的连接
    • 客户端(浏览器)与服务器建立连接后,没有其他因素干扰,连接是不会断,一直存在,客户端与服务器双方是对等的,不再区分谁是客户端,谁是服务端,客户端可以发送数据给服务端,服务端也可以发送数据给客户端,在真正意义上实现了服务端的推技术
    • 长连接在建立初期会发送带有 header 头信息的网络请求,在连接建立后,在长连接之上只需要发送需要传递的数据(真正的数据)即可
    • Websocket 是基于 HTTP 协议
    • Websocket 也可以用于非浏览器的场景,只要你的库支持 Websocket 即可

本篇编写一个用 Netty 实现的长连接应用

服务端

public static void main(String[] args) throws InterruptedException {
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();

    try {
        // handler() 与 childHandler() 的区别
        // 服务端可以使用 handler() 或者 childHandler(),而对于客户端,一般只使用handler()
        // handler()对于处理的 bossGroup 相关的信息,比如链接后,输出日志
        // childHandler() 是指连接丢给 workerGroup 之后,对 workerGroup 的 NIO 线程作用
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new WebsocketInitializer());
        ChannelFuture channelFuture = serverBootstrap.bind(4096).sync();
        channelFuture.channel().closeFuture().sync();
    } finally {
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}
public class WebsocketInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new ChunkedWriteHandler());
        // 聚合成完整的请求
        pipeline.addLast(new HttpObjectAggregator(8192));
        // websocket 协议的地址,这里 context_path 是 websocket
        // ws://localhost:port/context_path
        pipeline.addLast(new WebSocketServerProtocolHandler("/websocket"));
        pipeline.addLast(new WebsocketTextHandler());
    }
}
public class WebsocketTextHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("收到消息:" + msg.text());

        ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间:" + LocalDateTime.now()));
    }


    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("与客户端连接建立" + ctx.channel().id().asLongText());
    }


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连接关闭:" + ctx.channel().id().asLongText());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常发生");
        ctx.close();
    }
}

客户端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>websocket demo</title>
</head>
<body>
<form onsubmit="return false;">
    <label>
        <textarea name="message" style="width: 400px; height:200px"></textarea>
    </label>

    <input type="button" value="发送数据" onclick="send(this.form.message.value)">

    <h3>服务器返回</h3>
    <label>
        <textarea id="responseText" style="width: 400px; height: 200px"></textarea>
    </label>
    <input type="button" value="清空内容" onclick="document.getElementById(responseText).value()=''">
</form>
</body>
<script type="text/javascript">
    let socket;
    if (window.WebSocket) {
        // 连接 websocket
        socket = new WebSocket("ws://localhost:4096/websocket");
        socket.onmessage = function (event) {
            const ta = document.getElementById("responseText");
            ta.value = ta.value + "\n" + event.data;
        };
        socket.onopen = function (event) {
            const ta = document.getElementById("responseText");
            ta.value = "连接开启!\n";
        };
        socket.onclose = function (event) {
            var ta = document.getElementById("responseText");
            ta.value = "连接关闭!\n";
        }
    } else {
        alert("浏览器不支持WebSocket!");
    }
    function send(message) {
        if (!window.WebSocket) {
            alert("浏览器不支持WebSocket!");
            return;
        }
        if (socket.readyState === WebSocket.OPEN) {
            socket.send(message);
        } else {
            alert("WebSocket连接尚未开启!");
        }
    }
</script>

</html>
Clone this wiki locally