Netty通过WebSocket实现服务器与客户端长连接

WebSocket长连接是基于处理器 HttpObjectAggregator与WebSocketServerProtocolHandler的

HttpObjectAggregator

1.http数据在传输过程中是分段,HttpObjectAggregator就是可以将多个段聚合。
2.这就是为什么当浏览器发送大量数据时,就会发出多次http请求。

WebSocketServerProtocolHandler

1.对于webSocket是以帧的形式传递
2.可以看到webSocketFrame,下面有六个子类
3.浏览器请求时 ws: //localhost:7000/hello 表示请求得uri
4.WebSocketServerProtocolHandler核心功能,将http协议升级为ws协议,保持长连接
5.通过状态码101

具体实现代码:

服务端:

package com.jym.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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;

/**
 * @program: NettyPro
 * @description:
 * @author: jym
 * @create: 2020/02/10
 */
public class MyServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
                    // 在bossGroup增加日志处理器
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .option(ChannelOption.SO_BACKLOG,128)
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // 因为基于http协议,使用http的编码和解码器
                            pipeline.addLast("JymHttpSeverCodec",new HttpServerCodec());
                            // 是以块方式写,添加ChunkedWriteHandler处理器
                            pipeline.addLast("JymChunkedWriteHandler",new ChunkedWriteHandler());
                            /**
                             说明:
                             1.http数据在传输过程中是分段,HttpObjectAggregator就是可以将多个段聚合
                             2.这就是为什么当浏览器发送大量数据时,就会发出多次http请求
                             */
                            pipeline.addLast(new HttpObjectAggregator(8192));
                            /**
                             说明:
                             1.对于webSocket是以帧的形式传递
                             2.可以看到webSocketFrame,下面有六个子类
                             3.浏览器请求时 ws: //localhost:7000/hello 表示请求得uri
                             4.WebSocketServerProtocolHandler核心功能,将http协议升级为ws协议,保持长连接
                             5.通过状态码101
                             */
                            pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
                            // 自定义的handler,处理业务逻辑
                            pipeline.addLast(new MyServerHandler());
                        }
                    });

            System.out.println("服务器已启动");
            ChannelFuture sync = serverBootstrap.bind(9000).sync();
            sync.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

处理器:

package com.jym.websocket;

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

import java.time.LocalDateTime;

/**
 * @program: NettyPro
 * @description: TextWebSocketFrame 表示一个文本帧(frame)
 * @author: jym
 * @create: 2020/02/10
 */
public class MyServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
        System.out.println("服务器端收到消息" + textWebSocketFrame.text());
        // 回复消息
        Channel channel = channelHandlerContext.channel();
        channel.writeAndFlush(
                new TextWebSocketFrame("服务器时间:"+ LocalDateTime.now()+""+textWebSocketFrame.text()));
    }

    /**
     * web客户连接,触发此方法
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // id 表示唯一的一个值,LongText 是唯一的,shortText 不是唯一的
        System.out.println("handlerAdded被调用"+ctx.channel().id().asLongText());
    }


    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded被调用"+ctx.channel().id().asLongText());
    }

    /**
     * 处理异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常发生"+cause.getMessage());
    }
}

客户端代码(通过html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script>
    var socket;
    // 判断当前浏览器是否支持webSocket编程
    if(window.WebSocket){
        debugger;
        socket = new WebSocket("ws://localhost:9000/hello");
        // 相当于channelRead0,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");
            alert(rt);
            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){
            return;
        }
        if(socket.readyState == WebSocket.OPEN) {
            socket.send(message);
        } else {
            alert("连接未开启");
        }
    }
</script>
<body>
    <form onsubmit="return false">
        <label>
            <textarea name="message" style="height: 300px; width: 300px" ></textarea>
        </label>
        <input type="button" value="发送消息" onclick="send(this.form.message.value)">
        <label>
            <textarea id="responseText" style="height: 300px; width: 300px" ></textarea>
        </label>
        <input type="button" value="清空内容"
               onclick="document.getElementById('responseText').value=''">
    </form>


</body>


</html>
启动客户端,并启动html后:

在这里插入图片描述

注意:

new WebSocketServerProtocolHandler("/hello")的/hello,必须与页面的url一致
在这里插入图片描述

学习年限不足,知识过浅,说的不对请见谅。

世界上有10种人,一种是懂二进制的,一种是不懂二进制的。

发布了78 篇原创文章 · 获赞 54 · 访问量 46万+

猜你喜欢

转载自blog.csdn.net/weixin_43326401/article/details/104259048