初识Netty系列——Netty实战Websocket

856 阅读3分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

之前我们实战过Netty实现Http的小案例,今天我们开始Websocket的实现,实战之前先来了解下Websocetk的入门!

什么是Websocket?

WebSocket 通过“Upgrade handshake(升级握手)”从标准的 HTTP 或HTTPS 协议转为 WebSocket。因此,使用 WebSocket 的应用程序将始终以 HTTP/S 开始,然后进行升级。在什么时候发生这种情况取决于具体的应用;它可以是在启动时,或当一个特定的 URL 被请求时。

在我们的应用中,当 URL 请求以“/ws”结束时,我们才升级协议为WebSocket。否则,服务器将使用基本的 HTTP/S。一旦升级连接将使用的WebSocket 传输所有数据。

Websocket的处理过程?

  1. 客户端连接到服务器
  2. HTTP请求页面或Websocket"Upgrade handshake"
  3. 服务端处理发起连接的客户端
  4. 响应客户端对应URL的请求,当访问的URL有"/ws",处理Websocket"Upgrade handshake"
  5. 升级握手完成后 ,通过 WebSocket 发送聊天消息

image.png

实战:

四个页面:

image.png

第一步,创建WWSServer页面

/**
 * @version 1.0
 * @date 2021/8/31 19:44
 */
public class WWSServer {
    int port = 8081;
    public void start() {
        // 定义一对线程组
        // 主线程组, 用于接受客户端的连接,
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 从线程组, 负责IO交互工作
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //netty服务器的创建, 辅助工具类,用于服务器通道的一系列配置
            ServerBootstrap server = new ServerBootstrap();
            //绑定两个线程组
            server.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new WSServerInitialzer());

            // 启动server,并且设置8088为启动的端口号,同时启动方式为同步
            ChannelFuture future = server.bind(port).sync();
            System.out.println(" server start up on port : " + port);
            //等待服务端口关闭
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 优雅退出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

第二步:创建WSServerInitializer页面

/**
 * @version 1.0
 * @date 2021/8/31 19:46
 */
public class WSServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //处理http消息的编解码
        pipeline.addLast("httpServerCodec", new HttpServerCodec());
        pipeline.addLast("aggregator", new HttpObjectAggregator(65536));

        // 添加对httpWebsocket的支持
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        // 自定义的handler
        pipeline.addLast(new ChatHandler());
    }

}

第三步,创建ChatHandler页面

/**
 * @Description: 处理消息的handler
 * @version 1.0
 * @date 2021/8/31 19:56
 */
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("接受到id为 "+ ctx.channel().id() +" 的数据:" + content);
        // 向客户端发送数据
        clients.writeAndFlush(new TextWebSocketFrame("我是Websocket Server服务器,我收到你的消息为:" + content));
    }

    //获取客户端的channel,并且放到ChannelGroup中去进行管理
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        clients.add(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // 当触发handlerRemoved,ChannelGroup会自动移除对应客户端的channel,所以下面的remove不用我们再手写
        System.out.println("客户端断开,channel对应的长id为:" + ctx.channel().id().asLongText());
        System.out.println("客户端断开,channel对应的短id为:" + ctx.channel().id().asShortText());
    }
}

第四部调用start方法

image.png

测试:

直接找个在线测试websocket进行测试,并成功反馈结果

image.png