Netty源码分析-Websocket之WebSocket08FrameEncoder

 

 

package io.netty.handler.codec.http.websocketx;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;

//WebSocketFrame编码器,负责把WebSocketFrame的子类转换为bytebuf
public class WebSocket08FrameEncoder extends MessageToMessageEncoder<WebSocketFrame> implements WebSocketFrameEncoder {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocket08FrameEncoder.class);

    private static final byte OPCODE_CONT = 0x0; //延续帧  0000 0000
    private static final byte OPCODE_TEXT = 0x1; //文本帧  0000 0001
    private static final byte OPCODE_BINARY = 0x2; //二进制帧 0000 0010
    private static final byte OPCODE_CLOSE = 0x8; //关闭   0000 1000
    private static final byte OPCODE_PING = 0x9; //心跳检测帧 0000 1001
    private static final byte OPCODE_PONG = 0xA; //心跳应答帧 0000 1010

    
    //阈值,发送的字节超过此长度将不会合并到一个bytebuf中
    private static final int GATHERING_WRITE_THRESHOLD = 1024;

    //表示websocket是否需要对数据进行掩码运算
    //掩码运算也叫XOR加密,详情可以在http://www.ruanyifeng.com/blog/2017/05/xor.html了解。
    //那么websocket客户端发送到服务器端的数据需要进行XOR运算是为了防止攻击
    //因为websocket发送的数据,黑客很有可能在数据字节码中加入http请求的关键字,比如getxx \r\n,
    //如果不加以限制,那么某些代理服务器会以为这是一个http请求导致错误转发。
    //那么通过对原生字节进行XOP计算后,http关键字会被转化为其它字节,从而避免攻击。
    private final boolean maskPayload;


    public WebSocket08FrameEncoder(boolean maskPayload) {
        this.maskPayload = maskPayload;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
        //要发送的数据
        final ByteBuf data = msg.content();
        //掩码XOR计算需要的KEY
        byte[] mask;

        //根据帧的类型确定opcode的值
        byte opcode;
        if (msg instanceof TextWebSocketFrame) {
            opcode = OPCODE_TEXT;
        } else if (msg instanceof PingWebSocketFrame) {
            opcode = OPCODE_PING;
        } else if (msg instanceof PongWebSocketFrame) {
            opcode = OPCODE_PONG;
        } else if (msg instanceof CloseWebSocketFrame) {
            opcode = OPCODE_CLOSE;
        } else if (msg instanceof BinaryWebSocketFrame) {
            opcode = OPCODE_BINARY;
        } else if (msg instanceof ContinuationWebSocketFrame) {
            opcode = OPCODE_CONT;
        } else {
            throw new UnsupportedOperationException("Cannot encode frame of type: " + msg.getClass().getName());
        }

        //要发送数据的长度
        int length = data.readableBytes();

        int b0 = 0;
        //判断消息是否是最后一个分片,如果是最后一个分片 那么FIN要设置为1
        if (msg.isFinalFragment()) {
            //1 << 7 左移7位  1000 0000  把FIN比特为设为1
            //bo = 0 | 128 (当两边操作数的位有一边为1时,结果为1,否则为0),值不变。
            b0 |= 1 << 7;
            //计算完 b0=128  【1000 0000】
        }

        //RSV1, RSV2, RSV3:各占1个比特 正常全为0,属于扩展字段

        //msg.rsv() % 8 任何int摸8都返回小于8的数 二进制位<=[0000 0111]
        //<< 4 左移4位得到 [0111 0000],这里假设的是rsv不为0的情况。
        //实际情况rsv是0,那么得到【0000 0000]
        b0 |= msg.rsv() % 8 << 4; //b0 |= 0  值没变还是128[1000 0000]

        //opcode % 128 值不变
        //我们假设opcode= 0x1; //文本帧  0000 0001
        b0 |= opcode % 128; //那么  bo |= 0x1 得到 [1000 0001]

        //                                   Fin    RSV  opcode
        //所以websocket第一个比特位已经得到 = 【 1     000    0001  】

        if (opcode == OPCODE_PING && length > 125) {
            throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was "
                    + length);
        }

        //是否释放bytebuf的标记位
        boolean release = true;


        ByteBuf buf = null;
        try {

            //是否需要掩码,如果需要则需要4个字节的位置
            int maskLength = maskPayload ? 4 : 0;

            //数据的长度125之内
            if (length <= 125) {

                //size= 2+掩码的长度(如果有掩码,没有为0)
                //数据长度<=125,ws头2个字节+掩码长度即可
                int size = 2 + maskLength;

                //如果需要掩码 或者length<=1024
                if (maskPayload || length <= GATHERING_WRITE_THRESHOLD) {
                    //把size的值增大
                    size += length;
                }
                //分配缓冲区(如果maskPayload=true或length<=125,那么size就是websocket的头部长度+数据长度)
                buf = ctx.alloc().buffer(size);
                //写入websocket头的第一个字节:假设[10000001]
                buf.writeByte(b0);

                //websocket头第二个字节: 需要掩码为0x80 | (byte) length,假设长度120,那么得到 [1(需要掩码) 111 1000]
                //如果不需要掩码则得到 [0(不需要掩码)111 1000], 8个比特第一位表示是否需要掩码,其余7位表示长度。
                byte b = (byte) (maskPayload ? 0x80 | (byte) length : (byte) length);
                //写入第二个字节
                buf.writeByte(b);


                //数据长度65535之内
            } else if (length <= 0xFFFF) {

                //size= 4+掩码的长度(如果有掩码,没有为0)
                //数据长度 x>125 ,x<=65535,ws头需要4个字节+掩码长度
                int size = 4 + maskLength;

                //需要掩码 或 长度小于1024
                if (maskPayload || length <= GATHERING_WRITE_THRESHOLD) {
                    size += length;
                }
                //分配缓冲区
                buf = ctx.alloc().buffer(size);
                //写入第一个字节
                buf.writeByte(b0);
                //需要掩码写入【1111 1110】,不需要掩码写入【0111 1110】
                //第一个比特代表掩码,后面7个字节代表长度,写死126表示后续俩个字节为数据的真实长度。
                buf.writeByte(maskPayload ? 0xFE : 126);

                //假设length=3520 二进制为【00000000 00000000 00001101 11000000】
                //length分为俩个字节写入,先右移8位,把高位写入
                //右移8位:length >>> 8 = [00000000 00000000 00000000 00001101] & [11111111] = [00001101]
                buf.writeByte(length >>> 8 & 0xFF);
                
                //length & 0xFF = [00000000 00000000 00001101 11000000]  & [11111111]  = [11000000]
                //写入低8位
                buf.writeByte(length & 0xFF);
            } else {

                //size= 10+掩码的长度(如果有掩码,没有为0)
                //数据长度x>65535,ws头需要10个字节+掩码长度
                int size = 10 + maskLength;
                if (maskPayload || length <= GATHERING_WRITE_THRESHOLD) {
                    size += length;
                }
                //分配缓冲区
                buf = ctx.alloc().buffer(size);
                //写入第一个ws头字节
                buf.writeByte(b0);
                //写入第二个ws头字节
                //如果需要掩码为[1 1111111],否则为[0 1111111]
                //第一个比特表示掩码,后续7个字全都是1=127固定,表示后续8个字节为数据长度
                buf.writeByte(maskPayload ? 0xFF : 127);
                //写入8个字节为数据长度
                buf.writeLong(length);
            }

            // 需要掩码的逻辑
            if (maskPayload) {
                //生成随机数作为XOR的KEY
                int random = (int) (Math.random() * Integer.MAX_VALUE);
                //返回字节数组
                mask = ByteBuffer.allocate(4).putInt(random).array();

                //把掩码写入到buf中
                buf.writeBytes(mask);

                //获得字符序列
                ByteOrder srcOrder = data.order();
                ByteOrder dstOrder = buf.order();

                int counter = 0;
                int i = data.readerIndex();
                int end = data.writerIndex();

                //如果字符序列相同
                if (srcOrder == dstOrder) {
                    
                    //把数组拼接为32位的int形式
                    int intMask = ((mask[0] & 0xFF) << 24)
                                | ((mask[1] & 0xFF) << 16)
                                | ((mask[2] & 0xFF) << 8)
                                | (mask[3] & 0xFF);

                    //小端序列转换掩码
                    if (srcOrder == ByteOrder.LITTLE_ENDIAN) {
                        intMask = Integer.reverseBytes(intMask);
                    }

                    //每4个字节一组与掩码Key进行XOR运算
                    for (; i + 3 < end; i += 4) {
                        int intData = data.getInt(i);
                        //将结果写入buf
                        buf.writeInt(intData ^ intMask);
                    }
                }

                //不需要掩码才会走这个循环,如果上面需要掩码i的值已经被增加,这里不会循环
                for (; i < end; i++) {
                    //XOR计算
                    byte byteData = data.getByte(i);
                    buf.writeByte(byteData ^ mask[counter++ % 4]);
                }

                //返回buf到底层channel中输出
                out.add(buf);
            } 
            //不需要掩码的逻辑
            else {
                //如果buf缓冲区可写的空间 >=data数据可读的长度,说明buf在创建时size已经包括了length
                if (buf.writableBytes() >= data.readableBytes()) {
                    //把data写入到buf中
                    buf.writeBytes(data);
                    //返回buf写入到底channel中
                    out.add(buf);
                } else {
                    //返回buf写入到底channel中
                    out.add(buf);
                    //返回data写入到底层channel中
                    //计数器必须要增加+,因为在父类中对data进行了释放ReferenceCountUtil.release(cast);
                    //计数器+1后,相当于变成了2,那么在父类中释放一次,在channel用完后会在释放一次。
                    out.add(data.retain());
                }
            }

            //正在情况不释放
            release = false;
        } finally {
            //不出异常的情况不释放buf,由底层使用完毕后释放
            if (release && buf != null) {
                buf.release();
            }
        }
    }
}

 

Netty是一个基于Java的异步事件驱动的网络应用框架,它提供了一系列易于使用的API,用于创建高性能、可扩展的网络服务器和客户端应用程序。 WebSocket是一种在单个TCP连接上进行全双工通信的协议,它允许服务器主动向客户端推送数据,而不需要客户端先发起请求。在Java中,Netty提供了对WebSocket的支持,可以通过Netty来实现WebSocket服务器和客户端。 当使用Netty实现WebSocket时,Sec-WebSocket-Protocol是WebSocket握手过程中的一个HTTP头部字段,它用于指定客户端和服务器之间通信所使用的子协议。子协议是在WebSocket连接建立后,用于在双方之间传输数据的协议。 在Netty中,可以通过设置`WebSocketServerProtocolHandler`的`subprotocols`属性来指定支持的子协议。例如,可以使用以下代码来设置子协议为"chat": ```java ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new WebSocketServerCompressionHandler()); pipeline.addLast(new WebSocketServerProtocolHandler("/websocket", "chat")); pipeline.addLast(new MyWebSocketHandler()); ``` 在上面的代码中,`WebSocketServerProtocolHandler`的第二个参数就是Sec-WebSocket-Protocol的值,这里设置为"chat"。这样,当客户端与服务器进行握手时,可以指定"chat"作为子协议,在连接建立后双方就可以使用该子协议来进行通信。 希望这个回答对你有帮助!如果你还有其他问题,请继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值