Netty-6 Http protocol support -websocket

A, websockt Profile

All along, the network revolves around http protocol development. However, http there will be some inherent problems, so websocket was introduced.

(A) http shortcomings

  1. HTTP protocol is half-duplex, i.e., the data may be transmitted on the client and server in both directions, however, it can not be transmitted simultaneously.
  2. Long and cumbersome HTTP message, a request message including HTTP header, the message body, newline.
  3. In order to achieve a push message, send a request need in rotation by the browser to the client, the server acquires information, inefficient.

(B) websocket

Since some of the shortcomings http introduced websocket, to solve some of the problems the browser and server interaction, websocket has the following characteristics:

  1. Single tcp connection, full-duplex communication mode;
  2. Proxy, firewall, router and transparent;
  3. No header information, Cookie and authentication;
  4. No security overhead;
  5. Activated by holding link "ping / pong" frame;
  6. The server can send unsolicited messages to the client, the server no longer need to obtain the information through the rotation training;

Two, Netty development websocket

As used herein netty develop a web group chat function, to show support for Netty websocket agreement.

(A) server

package com.firewolf.java.io.http.websocket;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;

import com.firewolf.java.io.http.utils.NettyHttpUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
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.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.handler.stream.ChunkedWriteHandler;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;

/**
 * 作者:刘兴 时间:2019-05-30
 **/
public final class NettyWebChatServer {

  private String chatHtml = System.getProperty("user.dir")
      + "/netty-04-http/src/main/java/com/firewolf/java/io/http/websocket/chat.html";

  //创建websocket连接的工厂
  private WebSocketServerHandshakerFactory factory = null;

  //保存所有的连接
  private Map<Channel, WebSocketServerHandshaker> webConnections = new HashMap<>();

  private int port;

  public NettyWebChatServer(int port) {
    this.port = port;
  }

  public void startUp() {
    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    try {
      ServerBootstrap b = new ServerBootstrap();
      b.option(ChannelOption.SO_BACKLOG, 1024);
      b.group(bossGroup, workerGroup)
          .channel(NioServerSocketChannel.class)
          .childHandler(
              new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                  socketChannel.pipeline()
                      .addLast(new HttpServerCodec()) //请求解码器和响应编码器,等价于下面两行
                      .addLast(new HttpObjectAggregator(65535))
                      .addLast(new ChunkedWriteHandler())
                      .addLast(new NettyWebChatServerHandler());
                }
              }

          );
      Channel ch = b.bind(port).sync().channel();
      System.out.println(String.format("聊天服务已经启动,端口号为:%s", port));
      ch.closeFuture().sync();
    } catch (Exception e) {
      e.printStackTrace();

    } finally {
      //关闭服务
      bossGroup.shutdownGracefully();
      workerGroup.shutdownGracefully();
    }
  }


  class NettyWebChatServerHandler extends SimpleChannelInboundHandler<Object> {

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) {
      //传统的Http介入,
      if (msg instanceof FullHttpRequest) {
        handleHttpRequest(ctx, (FullHttpRequest) msg);
      } else if (msg instanceof WebSocketFrame) {
        //WebSocket信息接入
        handleWebMessage(ctx, (WebSocketFrame) msg);
      }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
      cause.printStackTrace();
      ctx.close();
    }

    /**
     * 处理http请求
     */
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
      if ("/".equals(request.uri())) {//如果请求的是主页:localhost:8888/,那么就返回聊天页面,
        responseChatPage(ctx, request);
      } else {
        //其他情况认为是前端发起了websocket连接
        handWebConnect(ctx, request);
      }
    }

    private void responseChatPage(ChannelHandlerContext ctx, HttpRequest req) {
      try {
        boolean keepAlive = HttpUtil.isKeepAlive(req);
        BufferedReader br = new BufferedReader(new FileReader(new File(chatHtml)));
        StringBuffer result = new StringBuffer();
        String line = null;
        while ((line = br.readLine()) != null) {
          result.append(line);
        }
        //构造响应数据
        FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK,
            Unpooled.wrappedBuffer(result.toString().getBytes()));
        response.headers()
            .set(CONTENT_TYPE, "text/html;charset=UTF-8"); //设置响应类型
        //给客户端响应信息
        NettyHttpUtils.sendResponse(ctx, req, keepAlive, response);
      } catch (Exception e) {
        e.printStackTrace();
      }

    }

    /**
     * 处理web连接
     */
    private void handWebConnect(ChannelHandlerContext ctx, FullHttpRequest request) {
      if (!"websocket".equals(request.headers().get("Upgrade"))) { //不是websock握手请求,不建立连接
        return;
      }
      if (factory == null) {
        factory = new WebSocketServerHandshakerFactory("ws://localhost:8888/websocket",
            null, false);
      }
      //建立websocket连接
      WebSocketServerHandshaker handlerShaker = factory.newHandshaker(request);
      if (handlerShaker == null) {
        WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
      } else {
        handlerShaker.handshake(ctx.channel(), request);
        webConnections.put(ctx.channel(), handlerShaker);
      }
    }

    /**
     * 处理webSocket信息
     */
    private void handleWebMessage(ChannelHandlerContext ctx, WebSocketFrame frame) {
      if (frame instanceof CloseWebSocketFrame) { //关闭连接
        webConnections.get(ctx.channel()).close(ctx.channel(), ((CloseWebSocketFrame) frame).retain());
        webConnections.remove(ctx.channel());
      } else if (frame instanceof PingWebSocketFrame) { //Ping信息,返回Pong
        ctx.channel().writeAndFlush(new PongWebSocketFrame());
      } else if (frame instanceof TextWebSocketFrame) { //文本信息,进行转发
        TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
        webConnections.keySet().forEach(x -> {
          String prefix = "有人说: ";
          if (x.id().asShortText().equals(ctx.channel().id().asShortText())) {
            prefix = "我说: ";
          }
          x.writeAndFlush(new TextWebSocketFrame(prefix + textFrame.text()));
        });
      } else { //其他的类型,不进行支持
        System.out.println("信息暂时不支持");
        ctx.channel().writeAndFlush(new TextWebSocketFrame("您的消息内容暂时不支持!!!"));
      }

    }

  }
  public static void main(String[] args) throws Exception {
    new NettyWebChatServer(8888).startUp();
  }
}

(B) page

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset = utf-8"/>
  <title>WebSocket客户端</title>
  <script type="text/javascript">
    var socket;
    if (!window.WebSocket) {
      window.WebSocket = window.MozWebSocket;
    }

    if (window.WebSocket) {
      socket = new WebSocket("ws://127.0.0.1:8888/websocket");
      socket.onmessage = function (event) {
        var ta = document.getElementById('responseContent');
        ta.value += event.data + "\r\n";
      };

      socket.onopen = function (event) {
        var ta = document.getElementById('responseContent');
        ta.value = "你当前的浏览器支持WebSocket,请进行后续操作\r\n";
      };

      socket.onclose = function (event) {
        var ta = document.getElementById('responseContent');
        ta.value = "";
        ta.value = "WebSocket连接已经关闭\r\n";
      };
    } else {
      alert("您的浏览器不支持WebSocket");
    }

    function send(message) {
      if (!window.WebSocket) {
        return;
      }
      if (socket.readyState == WebSocket.OPEN) {
        socket.send(message);
        document.getElementById("inputMessage").value = "";
      } else {
        alert("WebSocket连接没有建立成功!!");
      }
    }
  </script>
</head>
<body style="padding-left: 300px;padding-top: 80px;">
<form onSubmit="return false;">
  <h2 style="color: red;">Web群聊聊天大厅!!!</h2><br>
  <textarea id="responseContent" style="width:500px; height:300px" readonly></textarea>
  <br/><br/>
  <input type="text" id="inputMessage" name="message" value="" style="width: 390px; height: 30px;"/>
  <button style="width: 100px; height: 30px;margin-left: 10px;" onClick="send(this.form.message.value)"> 发送消息</button>
</form>
</body>
</html>

(C) test

After starting the project browser enter: HTTP: // localhost: 8888 , will demonstrate the chat page:
Here Insert Picture Description
we open two chat window, you can chat up:
Here Insert Picture Description
there is, for simplicity, pages written directly to the front, in fact, we can meet springboot, or nginx to specifically separate management chat.html.

Guess you like

Origin blog.csdn.net/mail_liuxing/article/details/90713164