美团霸王餐接口长连接推送:Netty自定义协议与Protobuf编解码细节

美团霸王餐接口长连接推送:Netty自定义协议与Protobuf编解码细节

为清晰呈现美团霸王餐接口长连接推送的核心技术,本文将围绕Netty自定义协议设计、Protobuf编解码实现展开,结合Java代码深入技术细节。

一、技术背景与核心需求

美团霸王餐接口需通过长连接用户中奖通知等消息,要求低延迟、高可靠性和数据紧凑性。选用Netty作为长连接通信框架,自定义协议解决TCP粘包/拆包问题,Protobuf作为序列化方案优化数据传输效率,三者协同满足业务需求。
在这里插入图片描述

二、Netty自定义协议设计

2.1 协议格式定义

自定义协议需包含魔数、版本号、消息长度、消息类型、校验码和消息体,结构如下:

字段字节数说明
魔数40xCAFEBABE,用于校验合法性
版本号1协议版本,支持兼容升级
消息长度4消息体字节数(大端序)
消息类型21-活动推送,2-中奖通知等
校验码2CRC16校验消息体
消息体NProtobuf序列化后的字节流
2.2 协议实体类(Java)
package juwatech.cn.meituan.protocol;

import lombok.Data;

@Data
public class CustomProtocol {
    private int magicNumber = 0xCAFEBABE; // 魔数
    private byte version = 1; // 版本号
    private int contentLength; // 消息体长度
    private short messageType; // 消息类型
    private short checkCode; // 校验码
    private byte[] content; // 消息体(Protobuf序列化后)

    // CRC16校验计算
    public void calculateCheckCode() {
        this.checkCode = Crc16Util.calculate(content);
    }
}
2.3 CRC16校验工具类
package juwatech.cn.meituan.util;

public class Crc16Util {
    public static short calculate(byte[] data) {
        int crc = 0xFFFF;
        for (byte b : data) {
            crc ^= (int) b & 0xFF;
            for (int i = 0; i < 8; i++) {
                if ((crc & 0x0001) != 0) {
                    crc = (crc >> 1) ^ 0xA001;
                } else {
                    crc >>= 1;
                }
            }
        }
        return (short) crc;
    }
}

三、Protobuf数据结构定义

3.1 .proto文件编写

创建coupon_message.proto定义消息结构,包含活动推送和中奖通知两种类型:

syntax = "proto3";
package juwatech.cn.meituan.protobuf;

// 活动推送消息
message ActivityPush {
    string activityId = 1; // 活动ID
    string title = 2; // 活动标题
    string description = 3; // 活动描述
    int64 startTime = 4; // 开始时间(时间戳)
    int64 endTime = 5; // 结束时间(时间戳)
}

// 中奖通知消息
message WinNotice {
    string noticeId = 1; // 通知ID
    string userId = 2; // 用户ID
    string couponId = 3; // 优惠券ID
    string couponName = 4; // 优惠券名称
    int64 receiveTime = 5; // 领取时间(时间戳)
}

// 统一消息体
message CouponMessage {
    oneof messageBody {
        ActivityPush activityPush = 1;
        WinNotice winNotice = 2;
    }
}
3.2 生成Java代码

通过Protobuf编译器生成Java类,命令如下:

protoc --java_out=src/main/java coupon_message.proto

生成的Java类位于juwatech.cn.meituan.protobuf包下,包含序列化和反序列化方法。

四、Netty编解码器实现

4.1 编码器(CustomProtocolEncoder)

负责将CustomProtocol对象编码为字节流,写入Channel:

package juwatech.cn.meituan.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import juwatech.cn.meituan.protocol.CustomProtocol;

public class CustomProtocolEncoder extends MessageToByteEncoder<CustomProtocol> {
    @Override
    protected void encode(ChannelHandlerContext ctx, CustomProtocol msg, ByteBuf out) throws Exception {
        // 写入魔数
        out.writeInt(msg.getMagicNumber());
        // 写入版本号
        out.writeByte(msg.getVersion());
        // 写入消息体长度
        out.writeInt(msg.getContentLength());
        // 写入消息类型
        out.writeShort(msg.getMessageType());
        // 写入校验码
        out.writeShort(msg.getCheckCode());
        // 写入消息体
        out.writeBytes(msg.getContent());
    }
}
4.2 解码器(CustomProtocolDecoder)

基于Netty的LengthFieldBasedFrameDecoder解决粘包/拆包,解码字节流为CustomProtocol对象:

package juwatech.cn.meituan.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import juwatech.cn.meituan.protocol.CustomProtocol;
import juwatech.cn.meituan.util.Crc16Util;

public class CustomProtocolDecoder extends LengthFieldBasedFrameDecoder {
    // 长度字段偏移量:魔数(4) + 版本(1) = 5
    private static final int LENGTH_FIELD_OFFSET = 5;
    // 长度字段长度:4字节
    private static final int LENGTH_FIELD_LENGTH = 4;

    public CustomProtocolDecoder() {
        super(Integer.MAX_VALUE, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame = (ByteBuf) super.decode(ctx, in);
        if (frame == null) return null;

        CustomProtocol protocol = new CustomProtocol();
        // 读取魔数
        protocol.setMagicNumber(frame.readInt());
        // 读取版本号
        protocol.setVersion(frame.readByte());
        // 读取消息体长度
        int contentLength = frame.readInt();
        protocol.setContentLength(contentLength);
        // 读取消息类型
        protocol.setMessageType(frame.readShort());
        // 读取校验码
        protocol.setCheckCode(frame.readShort());
        // 读取消息体
        byte[] content = new byte[contentLength];
        frame.readBytes(content);
        protocol.setContent(content);

        // 校验码验证
        short calculateCode = Crc16Util.calculate(content);
        if (protocol.getCheckCode() != calculateCode) {
            throw new IllegalArgumentException("校验码错误,消息可能被篡改");
        }

        return protocol;
    }
}

五、Protobuf编解码集成

5.1 消息序列化(发送端)

将业务对象序列化为Protobuf字节流,封装到CustomProtocol中:

package juwatech.cn.meituan.service;

import juwatech.cn.meituan.protocol.CustomProtocol;
import juwatech.cn.meituan.protobuf.ActivityPush;
import juwatech.cn.meituan.protobuf.CouponMessage;

public class MessageSender {
    public CustomProtocol buildActivityPushProtocol(ActivityPush activityPush) {
        // Protobuf序列化
        byte[] content = CouponMessage.newBuilder()
                .setActivityPush(activityPush)
                .build()
                .toByteArray();

        CustomProtocol protocol = new CustomProtocol();
        protocol.setMessageType((short) 1); // 活动推送类型
        protocol.setContentLength(content.length);
        protocol.setContent(content);
        protocol.calculateCheckCode(); // 计算校验码

        return protocol;
    }
}
5.2 消息反序列化(接收端)

CustomProtocol中提取消息体,反序列化为Protobuf对象:

package juwatech.cn.meituan.handler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import juwatech.cn.meituan.protocol.CustomProtocol;
import juwatech.cn.meituan.protobuf.CouponMessage;

public class MessageHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        CustomProtocol protocol = (CustomProtocol) msg;
        byte[] content = protocol.getContent();

        // Protobuf反序列化
        CouponMessage couponMessage = CouponMessage.parseFrom(content);
        // 根据消息类型处理
        switch (protocol.getMessageType()) {
            case 1:
                handleActivityPush(couponMessage.getActivityPush());
                break;
            case 2:
                handleWinNotice(couponMessage.getWinNotice());
                break;
            default:
                System.err.println("未知消息类型:" + protocol.getMessageType());
        }
    }

    private void handleActivityPush(ActivityPush activityPush) {
        System.out.println("收到活动推送:" + activityPush.getTitle());
        // 业务逻辑处理...
    }

    private void handleWinNotice(CouponMessage.WinNotice winNotice) {
        System.out.println("用户" + winNotice.getUserId() + "中奖:" + winNotice.getCouponName());
        // 业务逻辑处理...
    }
}

六、Netty服务端与客户端配置

6.1 服务端启动类
package juwatech.cn.meituan.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import juwatech.cn.meituan.codec.CustomProtocolDecoder;
import juwatech.cn.meituan.codec.CustomProtocolEncoder;
import juwatech.cn.meituan.handler.MessageHandler;

public class NettyServer {
    public void start(int port) throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast(new CustomProtocolDecoder()) // 解码器
                                    .addLast(new CustomProtocolEncoder()) // 编码器
                                    .addLast(new MessageHandler()); // 消息处理器
                        }
                    });

            bootstrap.bind(port).sync().channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new NettyServer().start(8888);
    }
}

本文著作权归吃喝不愁app开发者团队,转载请注明出处!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值