一、websocket的基本准备工作和netty的内置类

1、按照maven依赖

<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>5.0.0.Alpha2</version>
        </dependency>

2、websocket的内置数据帧类

数据帧的基类是WebSocketFrame
2.1 BinaryWebSocketFrame代表二进制数据帧
2.2 TextWebSocketFrame代表文本数据的数据帧
2.3 CloseWebSocketFrame代表一个结束请求,属于控制帧
2.4 ContinuationWebSocketFrame:当发送的数据内容多于一个帧时,将消息拆分为多个WebSocketFrame数据帧进行发送,而此类数据帧专用于发送剩余的内容。
2.5 PingWebSocketFrame和PongWebSocketFrame:是心跳帧,服务器通过PingWebSocketFrame发送到客户端,客户端通过PongWebSocketFrame进行响应。

3、netty内置的handler处理器

3.1 WebSocketServerProtocolHandler:websocket服务的协议处理器,用于进行握手升级操作。在通信过程中先通过握手(http协议),然后在进行升级到websocket协议

3.2 WebSocketFrameEncoder和WebSocketFrameDecoder:websocket协议的编码器和解码器。

二、测试代码实战

1、新建WebSocketParamHandler类,用于处理地址栏参数的

package com.example.netty_project.websocketService;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.util.AttributeKey;

import java.util.List;
import java.util.Map;

@ChannelHandler.Sharable
public class WebSocketParamHandler extends ChannelInboundHandlerAdapter {
    // 定义用于在 Channel 属性中存储参数的 Key
    public static final AttributeKey<String> USER_ID_KEY = AttributeKey.valueOf("userId");

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest request = (FullHttpRequest) msg;
            String uri = request.uri();

            // 使用 Netty 的 QueryStringDecoder 解析 URI
            QueryStringDecoder queryDecoder = new QueryStringDecoder(uri);
            Map<String, List<String>> parameters = queryDecoder.parameters();

            // 获取特定参数,例如 token 和 userId
            String userId = getFirstValue(parameters, "userId");

            if (userId != null) {
                ctx.channel().attr(USER_ID_KEY).set(userId);
            }

            // 重要:将消息传递给下一个 Handler
            ctx.fireChannelRead(msg);

            // 可选:此 Handler 仅用于握手阶段,可将其从 pipeline 中移除
            ctx.pipeline().remove(this);
        } else {
            ctx.fireChannelRead(msg);
        }
    }

    private String getFirstValue(Map<String, List<String>> parameters, String key) {
        List<String> values = parameters.get(key);
        if (values != null && !values.isEmpty()) {
            return values.get(0);
        }
        return null;
    }
}

2、新建TextWebSocketFrameHandler,文本数据业务处理器(核心部分)

package com.example.netty_project.websocketService;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import lombok.extern.slf4j.Slf4j;

@ChannelHandler.Sharable
@Slf4j
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<WebSocketFrame> {
    @Override
    protected void messageReceived(ChannelHandlerContext channelHandlerContext, WebSocketFrame webSocketFrame) throws Exception {

    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object frame) throws Exception {
        if(frame instanceof TextWebSocketFrame webSocketFrame){
            String request=webSocketFrame.text();
            log.info("收到消息:{}",request);
            log.warn("获取地址栏参数");
            log.warn(ctx.channel().attr(WebSocketParamHandler.USER_ID_KEY).get());
            ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器收到消息:"+request));
        }else {
            throw new UnsupportedOperationException("不支持的消息类型");
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        WebSocketServerProtocolHandler.ServerHandshakeStateEvent handshakeComplete = WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE;
        if(evt.equals(handshakeComplete)){
            //判断是否是握手事件
            log.error("握手成功");
        }
        super.userEventTriggered(ctx, evt);
    }
}

3、新建引导类进行启动

package com.example.netty_project.websocketService;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
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.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;

public class WebsocketMain {
    public static void main(String[] args) {
        ChannelInitializer initializer = new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new HttpServerCodec())
                        .addLast(new HttpObjectAggregator(65535))
                        .addLast(new WebSocketParamHandler())
                        .addLast(new WebSocketServerProtocolHandler("/ws"))
                        .addLast(new TextWebSocketFrameHandler());
            }
        };
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(initializer);
            Channel ch = serverBootstrap.bind(18899).sync().channel();
            ch.closeFuture().sync();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

更多推荐