netty achieve transmit text and binary data websocket

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/fu_huo_1993/article/details/88224986

    In the recent study netty-related knowledge, see netty can be achieved websoket, thus recording what steps websocket implemented in netty, the main achievement of passing text messages and transfer binary messages (only consider here is the picture), and if the message can other consider using custom protocols.

 

demand:

    1, netty achieve websocket server

    2, to achieve a text message delivery

    3, to achieve binary transmission of information, by default only consider transfer pictures, if you need to pass anything else, consider using a custom protocol.

    4, only need to consider websocket agreement, do not handle http request

 

Implementation details:

    1, netty on the increased processor websocket

          WebSocketServerProtocolHandler 

              >> This processor may process the webSocket protocol handshake request processing , and the Close , the Ping , Pong control processing frame. For text and binary data frames we need to deal with their own .

              >> If we need to intercept webSocket protocol handshake after treatment is completed, ChannelInboundHandler # userEventTriggered method can be achieved, and determine whether it is HandshakeComplete event.

              >> parameters: websocketPath represents the path of webSocket

              >> parameters: MaxFrameSize indicates the maximum frames need this value if uploading large files transfer large

    Process 2, a text message

               Client: sent directly to a string

               Services: the server to the client response text data, we need to return TextWebSocketFrame objects , otherwise the client does not receive.

    3, the processing binary message

               Client: Pass a blob objects to the background, if we need to pass additional information , you can add the blob object

               Server: processing BinaryWebSocketFrame frame, then return BinaryWebSocketFrame object to the foreground.

    4, custom protocol for binary message code as follows :( Deleted)

          First four bytes of the file type , the following byte indicates the specific data.

          In java an int is four bytes, indicating js used in Int32

          This protocol is mainly to determine whether the front-end transfer of pictures, if the picture is passed on directly to the background, and then return the binary data in the background to the foreground of this picture is displayed directly. Non-image not manipulate.

     5, js the binary data

           See webSocket.html processing file.

 

Implementation steps:

1, the main dependence

<dependency>
     <groupId>io.netty</groupId>
     <artifactId>netty-all</artifactId>
     <version>4.1.38.Final</version>
</dependency>

 

2, webSocket write server

package com.huan.netty.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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.WebSocketFrameAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * netty 整合 websocket
 *
 * @author huan.fu
 * @date 2018/11/7 - 17:25
 */
public class WebSocketServer {

    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGroup)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new LoggingHandler(LogLevel.TRACE))
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast(new LoggingHandler(LogLevel.TRACE))
                                    // HttpRequestDecoder和HttpResponseEncoder的一个组合,针对http协议进行编解码
                                    .addLast(new HttpServerCodec())
                                    // 分块向客户端写数据,防止发送大文件时导致内存溢出, channel.write(new ChunkedFile(new File("bigFile.mkv")))
                                    .addLast(new ChunkedWriteHandler())
                                    // 将HttpMessage和HttpContents聚合到一个完成的 FullHttpRequest或FullHttpResponse中,具体是FullHttpRequest对象还是FullHttpResponse对象取决于是请求还是响应
                                    // 需要放到HttpServerCodec这个处理器后面
                                    .addLast(new HttpObjectAggregator(10240))
                                    // webSocket 数据压缩扩展,当添加这个的时候WebSocketServerProtocolHandler的第三个参数需要设置成true
                                    .addLast(new WebSocketServerCompressionHandler())
                                    // 聚合 websocket 的数据帧,因为客户端可能分段向服务器端发送数据
                                    // https://github.com/netty/netty/issues/1112 https://github.com/netty/netty/pull/1207
                                    .addLast(new WebSocketFrameAggregator(10 * 1024 * 1024))
                                    // 服务器端向外暴露的 web socket 端点,当客户端传递比较大的对象时,maxFrameSize参数的值需要调大
                                    .addLast(new WebSocketServerProtocolHandler("/chat", null, true, 10485760))
                                    // 自定义处理器 - 处理 web socket 文本消息
                                    .addLast(new TextWebSocketHandler())
                                    // 自定义处理器 - 处理 web socket 二进制消息
                                    .addLast(new BinaryWebSocketFrameHandler());
                        }
                    });
            ChannelFuture channelFuture = bootstrap.bind(9898).sync();
            log.info("webSocket server listen on port : [{}]", 9898);
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

   note:

          1, look at the top followed by the introduction of which processors

          2, for the treatment webSocket handshake, Close, Ping, Pong, etc. , have been dealt with by the WebSocketServerProtocolHandler, we own only need to deal with Text and Binary data such as frame processing.

          3, for the transfer of large files , you need to modify maxFrameSize parameters.

 

3, and custom text message handshake processor

@Slf4j
public class TextWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
		log.info("接收到客户端的消息:[{}]", msg.text());
		// 如果是向客户端发送文本消息,则需要发送 TextWebSocketFrame 消息
		InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
		String ip = inetSocketAddress.getHostName();
		String txtMsg = "[" + ip + "][" + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "] ==> " + msg.text();
		ctx.channel().writeAndFlush(new TextWebSocketFrame(txtMsg));
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		ctx.close();
		log.error("服务器发生了异常:", cause);
	}

	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
			log.info("web socket 握手成功。");
			WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
			String requestUri = handshakeComplete.requestUri();
			log.info("requestUri:[{}]", requestUri);
			String subproTocol = handshakeComplete.selectedSubprotocol();
			log.info("subproTocol:[{}]", subproTocol);
			handshakeComplete.requestHeaders().forEach(entry -> log.info("header key:[{}] value:[{}]", entry.getKey(), entry.getValue()));
		} else {
			super.userEventTriggered(ctx, evt);
		}
	}
}

    note:

           1, where only handle text messages , so SimpleChannelInboundHandler the paradigm write TextWebSocketFrame

           2, send a text message to the client, need to send TextWebSocketFrame objects , otherwise the client does not receive.

           3, treatment after the handshake process to determine whether the HandshakeComplete event.

 

4, the processing of the binary message

package com.huan.netty.websocket;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 处理二进制消息
 *
 * @author huan.fu
 * @date 2018/11/8 - 14:37
 */
public class BinaryWebSocketFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> {
    private static final Logger log = LoggerFactory.getLogger(BinaryWebSocketFrameHandler.class);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws InterruptedException {
        log.info("服务器接收到二进制消息,消息长度:[{}]", msg.content().capacity());
        ByteBuf byteBuf = Unpooled.directBuffer(msg.content().capacity());
        byteBuf.writeBytes(msg.content());
        ctx.writeAndFlush(new BinaryWebSocketFrame(byteBuf));
    }
}

   note:

        1, where only process binary messages , so write a generic BinaryWebSocketFrame

 

5, the client wording

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>web socket 测试</title>
</head>
<body>

<div style="width: 600px;height: 400px;">
    <p>服务器输出:</p>
    <div style="border: 1px solid #CCC;height: 300px;overflow: scroll" id="server-msg-container">

    </div>
    <p>
        <textarea id="inp-msg" style="height: 50px;width: 500px"></textarea><input type="button" value="发送"
                                                                                   id="send"><br/>
        选择图片: <input type="file" id="send-pic">
    </p>
</div>

<script type="application/javascript">
    var ws = new WebSocket("ws://127.0.0.1:9898/chat");
    ws.onopen = function (ev) {

    };
    ws.onmessage = function (ev) {
        console.info("onmessage", ev);
        var inpMsg = document.getElementById("server-msg-container");
        if (typeof ev.data === "string") {
            inpMsg.innerHTML += ev.data + "<br/>";
        } else {
            var result = ev.data;
            var flagReader = new FileReader();
            flagReader.readAsArrayBuffer(result);
            flagReader.onload = function () {
                var imageReader = new FileReader();
                imageReader.readAsDataURL(result);
                console.info("服务器返回的数据大小:", result.size);
                imageReader.onload = function (img) {
                    var imgHtml = "<img src='" + img.target.result + "' style='width: 100px;height: 100px;'>";
                    inpMsg.innerHTML += imgHtml.replace("data:application/octet-stream;", "data:image/png;") + "<br />";
                    inpMsg.scroll(inpMsg.scrollWidth,inpMsg.scrollHeight);
                };
            }
        }
    };
    ws.onerror = function () {
        var inpMsg = document.getElementById("server-msg-container");
        inpMsg.innerHTML += "发生异常" + "<br/>";
    };
    ws.onclose = function () {
        var inpMsg = document.getElementById("server-msg-container");
        inpMsg.innerHTML += "webSocket 关闭" + "<br/>";
    };

    // 发送文字消息
    document.getElementById("send").addEventListener("click", function () {
        ws.send(document.getElementById("inp-msg").value);
    }, false);

    // 发送图片
    document.querySelector('#send-pic').addEventListener('change', function () {
        var files = this.files;
        if (files && files.length) {
            var file = files[0];
            var fileReader = new FileReader();
            fileReader.readAsArrayBuffer(file);
            fileReader.onload = function (e) {
                // 获取到文件对象
                var result = e.target.result;
                // 发送数据到服务器端
                ws.send(result)
            }
        }
    }, false);
</script>
</body>
</html>

   note:

          1 , how to deal with binary data returned by the backend.

 

6, achieve results


 

Completion code:

 Code is as follows: https://gitee.com/huan1993/netty-study/tree/master/src/main/java/com/huan/netty/websocket

 

 

Guess you like

Origin blog.csdn.net/fu_huo_1993/article/details/88224986
Recommended