Netty入门级应用实例_02_WebSocket发送聊天信息


Netty入门级应用实例_02_WebSocket发送聊天信息

0. 准备工作

引入pom依赖

<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.31.Final</version>
</dependency>

1. 实时通信

  • Ajax轮询
    使用ajax异步定时向服务端请求数据,服务端有数据或者状态结果返回的话,前端进行结果渲染。
    主要特点:需要不停的发出ajax请求,页面不需要刷新,耗资源
  • Long pull
    类似于Ajax轮询策略,只是在前台发出请求之后,如果后台没有响应,则会处于一直等待状态,直到后台返回response为止,收到响应后,才能够再次发出请求。
    主要特点:需要不停的建立HTTP连接,耗资源,效率差
  • websocket
    基于HTTP协议,能够建立长连接,服务端能够主动推送消息给客户端
    主要特点:持久化协议,一旦连接建立成功,服务端可以不停的主动向客户端推送消息,即只需要建立一次HTTP请求,就能获得源源不断的数据信息传输

2. 后台代码

2.1 WebSocketServer

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;

/** * @program: study_netty * @description: WebSocketServer * @author: Mr.superbeyone * @create: 2018-11-20 13:37 **/
public class WSServer {
    public static void main(String[] args) throws Exception{
// 定义主线程池
        EventLoopGroup boss = new NioEventLoopGroup();
// 定义从线程池
        EventLoopGroup work = new NioEventLoopGroup();

        try {
// 定义Server服务器
            ServerBootstrap bootstrap = new ServerBootstrap();

            bootstrap.group(boss, work)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new WSServerInitializer());

            ChannelFuture channelFuture = bootstrap.bind(8888).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            //优雅关闭
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }
    }
}

2.2 WebSocket初始化器

import com.superbeyone.netty.websocket.handler.ChatHandler;
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;

/** * @program: study_netty * @description: WebSocket初始化器 * @author: Mr.superbeyone * @create: 2018-11-20 13:44 **/
public class WSServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

// WebSocket是基于HTTP协议,所以要有HTTP编解码器
        pipeline.addLast("HttpServerCodec", new HttpServerCodec());
// 提供对写大数据流的支持
        pipeline.addLast("ChunkedWriteHandler", new ChunkedWriteHandler());
// 对HttpMessage进行聚合,聚合成FullHttpRequest或者FullHttpResponse 该handler使用率极高
        pipeline.addLast(new HttpObjectAggregator(1024 * 64));  //1024 *64 为最大消息长度

//========================================以上用于支持HTTP协议===================================================

//======================================以下用于支持WebSocket协议================================================

        /** * WebSocket 服务器处理的协议,用于指定给客户端连接访问的路由:/ws * 本handler会帮你处理一些繁重的复杂工作,比如:握手动作 handshaking (close,ping,pong) ping + pong = 心跳检测 * 对于WebSocket来讲,都是以frames进行传输的,不同的数据类型对应的frames也不同 */
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));

// 添加自定义handler
        pipeline.addLast("ChatHandler", new ChatHandler());


    }
}

2.3 自定义处理消息的Handler

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.time.LocalDateTime;

/** * @program: study_netty * @description: 自定义处理消息的Handler * @author: Mr.superbeyone * @create: 2018-11-20 14:03 **/

/** * TextWebSocketFrame: 在Netty中,是用于为WebSocket专门处理文本的对象,frame是消息的载体 */
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    // 用于管理和记录所有客户端的Channel
    private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 获取从客户端传输过来的消息
        String content = msg.text();
        System.out.println("接收到的消息:\t" + content);
        for (Channel channel : clients) {
            channel.writeAndFlush(new TextWebSocketFrame("服务器在[" + LocalDateTime.now() + "]接收到消息,内容为:\t" + content));
        }

// 等价于for循环
// clients.writeAndFlush(new TextWebSocketFrame("[服务器在]" + LocalDateTime.now()+ "接收到消息,内容为" + content));
    }

    /** * 当客户端连接服务端之后(打开连接) * 获取客户端的channel,并且放到ChannelGroup中进行管理 * * @param ctx * @throws Exception */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        clients.add(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// 当触发handlerRemoved,ChannelGroup会自动移除对应客户端的Channel
// clients.remove(ctx.channel());
        System.out.println("客户端断开,channel对应的长id为:\t" +
                ctx.channel().id().asLongText());
        System.out.println("客户端断开,channel对应的短id为:\t" +
                ctx.channel().id().asShortText());
        super.handlerRemoved(ctx);
    }
}

3. 页面代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket</title>
</head>
<body>
发送消息:
<input type="text" id="msgContent"> <input type="button" onclick="CHAT.chat()" value="点我发送">
<br>
<hr>
接收消息:
<div id="receiveMsg" style="background-color: lightgray"></div>

<script> window.CHAT = { socket: null, init: function () { //判断浏览器是否支持WebSocket if (window.WebSocket) { CHAT.socket = new WebSocket("ws://localhost:8888/ws"); CHAT.socket.onopen = function () { console.log("连接建立成功"); }, CHAT.socket.onclose = function () { console.log("连接关闭"); }, CHAT.socket.onerror = function () { console.log("发生错误"); }, CHAT.socket.onmessage = function (e) { console.log("接收到消息: " + e.data); var receiveMsg = document.getElementById("receiveMsg"); var html = receiveMsg.innerHTML; receiveMsg.innerHTML = html + "<br/>" + e.data; } } else { alert("浏览器不支持WebSocket协议"); } }, chat: function () { var msg = document.getElementById("msgContent"); CHAT.socket.send(msg.value); } } CHAT.init(); </script>
</body>
</html>

4. 测试结果

4.1 页面

在这里插入图片描述

4.2 控制台

在这里插入图片描述