实例要求

  1. Http 协议是无状态的,浏览器和服务器间的请求响应一次,下一次会重新创建连接。
  2. 要求:实现基于 WebSocket 的长连接的全双工的交互
  3. 改变 Http 协议多次请求的约束,实现长连接了,服务器可以发送消息给浏览器
  4. 客户端浏览器和服务器端会相互感知,比如服务器关闭了,浏览器会感知,同样浏览器关闭了,服务器会感知

    代码演示

    MyServer服务端

    ```java import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; 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.WebSocketServerProtocolHandler; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.stream.ChunkedWriteHandler;

public class MyServer {

  1. public static void main(String[] args) throws Exception {
  2. //创建两个线程组
  3. EventLoopGroup bossGroup = new NioEventLoopGroup(1);
  4. EventLoopGroup workerGroup = new NioEventLoopGroup(); //8个NioEventLoop
  5. try {
  6. ServerBootstrap serverBootstrap = new ServerBootstrap();
  7. serverBootstrap.group(bossGroup, workerGroup);
  8. serverBootstrap.channel(NioServerSocketChannel.class);
  9. serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
  10. serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
  11. @Override
  12. protected void initChannel(SocketChannel ch) throws Exception {
  13. ChannelPipeline pipeline = ch.pipeline();
  14. //因为基于http协议,使用http的编码和解码器
  15. pipeline.addLast(new HttpServerCodec());
  16. //是以块方式写,添加ChunkedWriteHandler处理器
  17. pipeline.addLast(new ChunkedWriteHandler());
  18. /*
  19. 说明
  20. 1. http数据在传输过程中是分段, HttpObjectAggregator ,就是可以将多个段聚合
  21. 2. 这就就是为什么,当浏览器发送大量数据时,就会发出多次http请求
  22. */
  23. pipeline.addLast(new HttpObjectAggregator(8192));
  24. /*
  25. 说明
  26. 1. 对应websocket ,它的数据是以 帧(frame) 形式传递
  27. 2. 可以看到WebSocketFrame 下面有六个子类
  28. 3. 浏览器请求时 ws://localhost:7000/hello 表示请求的uri
  29. 4. WebSocketServerProtocolHandler 核心功能是将 http协议升级为 ws协议 , 保持长连接
  30. 5. 是通过一个 状态码 101
  31. */
  32. pipeline.addLast(new WebSocketServerProtocolHandler("/hello2"));
  33. //自定义的handler ,处理业务逻辑
  34. pipeline.addLast(new MyTextWebSocketFrameHandler());
  35. }
  36. });
  37. //启动服务器
  38. ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
  39. channelFuture.channel().closeFuture().sync();
  40. } finally {
  41. bossGroup.shutdownGracefully();
  42. workerGroup.shutdownGracefully();
  43. }
  44. }

}

  1. <a name="OmlZe"></a>
  2. ### MyTextWebSocketFrameHandler 处理器
  3. ```java
  4. import io.netty.channel.ChannelHandlerContext;
  5. import io.netty.channel.SimpleChannelInboundHandler;
  6. import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
  7. import java.time.LocalDateTime;
  8. //这里 TextWebSocketFrame 类型,表示一个文本帧(frame)
  9. public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
  10. @Override
  11. protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
  12. System.out.println("服务器收到消息 " + msg.text());
  13. //回复消息
  14. ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间" + LocalDateTime.now() + " " + msg.text()));
  15. }
  16. //当web客户端连接后, 触发方法
  17. @Override
  18. public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
  19. //id 表示唯一的值,LongText 是唯一的 ShortText 不是唯一
  20. System.out.println("handlerAdded 被调用" + ctx.channel().id().asLongText());
  21. System.out.println("handlerAdded 被调用" + ctx.channel().id().asShortText());
  22. }
  23. @Override
  24. public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
  25. System.out.println("handlerRemoved 被调用" + ctx.channel().id().asLongText());
  26. }
  27. @Override
  28. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  29. System.out.println("异常发生 " + cause.getMessage());
  30. ctx.close(); //关闭连接
  31. }
  32. }

hello HTML页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    var socket;
    //判断当前浏览器是否支持websocket
    if(window.WebSocket) {
        //go on
        socket = new WebSocket("ws://localhost:7000/hello2");
        //相当于channelReado, ev 收到服务器端回送的消息
        socket.onmessage = function (ev) {
            var rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + ev.data;
        }

        //相当于连接开启(感知到连接开启)
        socket.onopen = function (ev) {
            var rt = document.getElementById("responseText");
            rt.value = "连接开启了.."
        }

        //相当于连接关闭(感知到连接关闭)
        socket.onclose = function (ev) {

            var rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + "连接关闭了.."
        }
    } else {
        alert("当前浏览器不支持websocket")
    }

    //发送消息到服务器
    function send(message) {
        if(!window.socket) { //先判断socket是否创建好
            return;
        }
        if(socket.readyState == WebSocket.OPEN) {
            //通过socket 发送消息
            socket.send(message)
        } else {
            alert("连接没有开启");
        }
    }
</script>
    <form onsubmit="return false">
        <textarea name="message" style="height: 300px; width: 300px"></textarea>
        <input type="button" value="发生消息" onclick="send(this.form.message.value)">
        <textarea id="responseText" style="height: 300px; width: 300px"></textarea>
        <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
    </form>
</body>
</html>

运行页面

image.png