Netty learning (5): WebSocket protocol

Overview

In Netty's application scenarios, socket communication accounts for the majority, and WebSocket, as an important feature of the content launched in H5, Netty has also done a good support. When learning WebSocket, we must first clarify a few issues, what is WebSocket, why it is used to launch WebSocket, what is its application scenario, and what problems are mainly solved.

What is WebSocket

WebSocket is a protocol for full-duplex communication on a single TCP connection . The WebSocket communication protocol was defined as the standard RFC 6455 by the IETF in 2011, and was supplemented by RFC7936. The WebSocket  API is also standardized by W3C .

WebSocket makes the data exchange between the client and the server easier, allowing the server to actively push data to the client. In the WebSocket API, the browser and the server only need to complete a handshake, and a persistent connection can be created directly between the two, and two-way data transmission can be carried out.

Excerpted from Baidu Encyclopedia

Simply put, WebSocket is an application layer protocol that allows two-way communication between the client and the server. In a technique for between any Web browser and the server bi-directional data transfer . WebSocket protocol based on TCP protocol implemented, comprising the early beginning of the handshake , and subsequent data frames multiple bidirectional transmission process. WebSocket currently supports two uniform resource identifier wsand wss, similar to HTTP and HTTPS.

  1. Send a GET request, the key: Upgrade: websocket & Connection: Upgrade, these two tell the server that I want to initiate the websocket protocol, I am not HTTP
  2. The server receives the protocol and returns a Switching Protocol, so the connection is successful, and http is upgraded to WebSocket
  3. The next communication is websocket, so you can connect well

What problem to solve & its application scenarios

In the early programs, requests based on Http1.0 can only be initiated by the client, the server is in a "passive" state, and the protocol is stateless, there is no connection between the request and the request, and it needs to rely on cookie or session To maintain user information. When the server needs to initiate a request (push data) to the client, it is very troublesome. The common practice is that every client makes a polling request to the server, and the disadvantages of polling are obvious. Many polling requests are meaningless, and because of the Http protocol rules, each request requires a request header. And the request body, the requested data text is very large. In many cases, the requested data is not as long as the request header, which causes the pressure on the server bandwidth, even if the Http1.1 launched later has the keeplive capability, because The state is maintained for a short time, and it can only be regarded as a no-brainer. Common "two-way communication" scenarios include message push and instant messaging applications.

WebSocket advantages

  • Less control overhead. After the connection is established, when data is exchanged between the server and the client, the data packet header used for protocol control is relatively small. Without the extension, for the content from the server to the client, the header size is only 2 to 10 bytes (depending on the packet length); for the content from the client to the server, this header needs to be added with additional 4-byte mask . Compared with the HTTP request carrying a complete header each time, this overhead is significantly reduced.

  • Stronger real-time. Since the protocol is full-duplex, the server can actively send data to the client at any time. Compared with HTTP requests, the server needs to wait for the client to initiate the request before responding, and the delay is significantly less; even compared with long polling such as Comet, it can transmit data more times in a short time.

  • Stay connected. Unlike HTTP, Websocket needs to create a connection first, which makes it a stateful protocol, and part of the state information can be omitted when communicating later. The HTTP request may need to carry status information (such as identity authentication, etc.) in each request.

  • Better binary support. Websocket defines a binary frame, which can handle binary content more easily than HTTP.

  • Can support expansion. Websocket defines extensions, and users can extend the protocol and implement some custom sub-protocols. For example, some browsers support compression .

  • Better compression effect. Compared with HTTP compression , Websocket can use the context of the previous content with proper extension support, and can significantly improve the compression rate when transferring similar data.

Example

Server:

package com.leolee.netty.fifthExample;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

import java.net.InetSocketAddress;

/**
 * @ClassName MyServer
 * @Description: webSocket
 * @Author LeoLee
 * @Date 2020/8/30
 * @Version V1.0
 **/
public class MyServer {

    public static void main(String[] args) throws InterruptedException {
        //定义线程组 EventLoopGroup为死循环
        //boss线程组一直在接收客户端发起的请求,但是不对请求做处理,boss会将接收到的请i交给worker线程组来处理
        //实际可以用一个线程组来做客户端的请求接收和处理两件事,但是不推荐
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //启动类定义
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    //子处理器,自定义处理器,服务端可以使用childHandler或者handler,handlerr对应接收线程组(bossGroup),childHandler对应处理线程组(workerGroup)
                    .handler(new LoggingHandler(LogLevel.INFO))//日志处理器
                    .childHandler(new WebSocketChannelInitializer());

            //绑定监听端口
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8899)).sync();
            //定义关闭监听
            channelFuture.channel().closeFuture().sync();
        } finally {
            //Netty提供的优雅关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}
package com.leolee.netty.fifthExample;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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.stream.ChunkedWriteHandler;

/**
 * @ClassName WebSocketChannelInitializer
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/8/30
 * @Version V1.0
 **/
public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ChannelPipeline pipeline = ch.pipeline();

        //Http处理
        pipeline.addLast("httpServerCodec", new HttpServerCodec());
        pipeline.addLast("chunkedWriteHandler", new ChunkedWriteHandler());//之后再学习
        //Netty对于http请求是分块或者分段的方式,比如一个请求发送的数据长度是1000,被切成了10段,该处理器就按照8192最大长度,去聚合这些请求数据
        pipeline.addLast("httpObjectAggregator", new HttpObjectAggregator(8192));

        //websocket处理
        //负责websocket的连接,以及控制frames(close Ping Pong)的处理,文字和二进制数据传递给下一个处理器处理,websocket的数据基于各种frames
        pipeline.addLast("webSocketServerProtocolHandler", new WebSocketServerProtocolHandler("/ws"));
        pipeline.addLast(new TextWebSocketFrameHandler());
    }
}
package com.leolee.netty.fifthExample;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.time.LocalDateTime;

/**
 * @ClassName TextWebSocketFrameHandler
 * @Description: 处理websocket文本数据
 * @Author LeoLee
 * @Date 2020/8/30
 * @Version V1.0
 **/
public class TextWebSocketFrameHandler 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("handlerAdded:" + ctx.channel().id().asLongText());//channel的全局唯一id
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

        System.out.println("handlerRemoved:" + ctx.channel().id().asLongText());//channel的全局唯一id
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

        System.out.println("发生异常");
        ctx.close();
    }
}

The server-side code is basically the same as the previous study. It should be noted that when SimpleChannelInboundHandler is inherited in T extWebSocketFrameInitializer, the generic type is passed TextWebSocketFrame , and TextWebSocketFrame is inherited from WebSocketFrame . Why is it not like String type when communicating with socket before? Because of the relevant provisions of the WebSocket protocol, there are 6 types of data that can be transmitted by WebSocket , which can be viewed in the WebSocketFrame subclass.

Client:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket-client</title>
</head>
<body>

    <form onsubmit="return false;">
        <textarea name="message" style="width: 300px;height: 150px"></textarea>
        <input type="button" value="发送" onclick="sendMessage(this.form.message.value)"/>
        <h3>服务端输出:</h3>
        <textarea id="responseText" style="width: 300px;height: 150px"></textarea>
        <input type="button" onclick="javascript: document.getElementById('responseText').value=''" value="clear"/>
    </form>

</body>
<script type="text/javascript">

    var socket;

    if (window.WebSocket) {
        //建立于服务端的连接
        socket = new WebSocket("ws://127.0.0.1:8899/ws")
        var serverTextArea = document.getElementById("responseText");
        //收到服务端消息的时候的回调
        socket.onmessage = function (event) {
            serverTextArea.value = serverTextArea.value + "\n" + event.data;
        }
        //连接建立成功回调
        socket.onopen = function (event) {
            serverTextArea.value = "连接建立成功";
        }
        //连接断开
        socket.onclose = function (event) {
            serverTextArea.value = serverTextArea.value + "\n" + "连接断开";
        }
    } else {
        alert("浏览器不支持WebSocket")
    }

    function sendMessage(message) {
        if (!window.WebSocket) {
            return;
        } else {
            if (socket.readyState == WebSocket.OPEN) {
                socket.send(message);
            } else {
                alert("连接尚未建立");
            }
        }
    }


</script>
</html>

Start the server, the following output:

八月 30, 2020 2:51:54 下午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x1e87326c] REGISTERED
八月 30, 2020 2:51:54 下午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0x1e87326c] BIND: 0.0.0.0/0.0.0.0:8899
八月 30, 2020 2:51:54 下午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x1e87326c, L:/0:0:0:0:0:0:0:0:8899] ACTIVE

Start the client, right-click the html file in idea, run! , Idea will help us start a service accessible in the browser:

The client will automatically establish a connection with the server after the browser is initialized, and the handlerAdded of the server will trigger:

八月 30, 2020 2:51:54 下午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x1e87326c] REGISTERED
八月 30, 2020 2:51:54 下午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0x1e87326c] BIND: 0.0.0.0/0.0.0.0:8899
八月 30, 2020 2:51:54 下午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x1e87326c, L:/0:0:0:0:0:0:0:0:8899] ACTIVE
八月 30, 2020 3:00:06 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x1e87326c, L:/0:0:0:0:0:0:0:0:8899] READ: [id: 0xf219f5ab, L:/127.0.0.1:8899 - R:/127.0.0.1:52817]
八月 30, 2020 3:00:06 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0x1e87326c, L:/0:0:0:0:0:0:0:0:8899] READ COMPLETE
handlerAdded:9cb6d0fffedebcb5-00006ec0-00000001-0822684926b0801c-f219f5ab

Try to send a message to the server:

收到客户端消息:hello server

The server receives the message from the client and writes the information to the client.

Close the client page server to trigger handlerRemove:

handlerRemoved:9cb6d0fffedebcb5-00006ec0-00000001-0822684926b0801c-f219f5ab

Close the server and the client triggers the socket.onclose callback:

Since the connection is not disconnected due to the network, the client's callback will be triggered, and the server can also receive the client's disconnect callback. If the disconnection is caused by the network, a heartbeat mechanism is needed.

analysis

Status Code: 101 Switching Protocols means protocol switching, which proves that when WebSocket establishes a server connection, http handshake is required. After the establishment is successful, the protocol is switched to WebSocket.

Upgrade: websocket , which means protocol upgrade

The chrome network console will monitor the sending and receiving of services in real time

Those who need code come here to get it: demo project address

Guess you like

Origin blog.csdn.net/qq_25805331/article/details/108305562