当前位置: 首页 > news >正文

初学Protobuf

Protobuf

什么是 Protobuf?

Protocol Buffers (Protobuf) 是一种轻便高效的结构化数据序列化协议,可用于结构化数据串行化,也可用于 RPC 接口定义。相比 JSON 和 XML,Protobuf 更小、更快、更简单。

前端为什么需要 Protobuf?

  1. 性能优化:传输数据量更小,加载速度更快
  2. 类型安全:强类型定义,减少运行时错误
  3. 自动代码生成:基于 .proto 文件自动生成 JavaScript/TypeScript 代码
  4. 前后端一致性:统一的数据结构定义

前端环境搭建

1. 安装依赖

# 安装 protobufjs 库
npm install protobufjs# 全局安装 protobuf 编译工具(可选)
npm install -g protobufjs-cli

2. 准备 .proto 文件

创建一个简单的 user.proto 文件:

syntax = "proto3";// 定义包名
package user;// 用户信息消息
message UserInfo {int32 id = 1;string name = 2;string email = 3;int32 age = 4;
}// 用户列表响应
message UserListResponse {repeated UserInfo users = 1;int32 total = 2;
}

编译 Protobuf 文件

方法一:运行时方式(推荐用于开发)

# 加载 .proto 文件并动态编译
npx pbjs -t json user.proto > user.json

方法二:静态代码生成(推荐用于生产)

# 生成 JavaScript 代码
npx pbjs -t static-module -w commonjs -o user_pb.js user.proto# 生成 TypeScript 声明文件
npx pbts -o user_pb.d.ts user_pb.js

前端实际应用

1. 动态加载方式(开发阶段)

import protobuf from 'protobufjs';// 异步加载 .proto 文件
const root = await protobuf.load('user.proto');// 获取消息类型
const UserInfo = root.lookupType('user.UserInfo');// 创建消息实例
const userInfo = UserInfo.create({id: 1,name: '张三',email: 'zhangsan@example.com',age: 25
});// 编码为二进制数据
const buffer = UserInfo.encode(userInfo).finish();// 解码二进制数据
const decoded = UserInfo.decode(buffer);
console.log(decoded.name); // 输出: 张三

2. 静态导入方式(生产阶段)

首先生成代码:

npx pbjs -t static-module -w commonjs -o user_pb.js user.proto
npx pbts -o user_pb.d.ts user_pb.js

然后在代码中使用:

// 导入生成的代码
import * as user_pb from './user_pb';// 创建用户信息
const userInfo = new user_pb.UserInfo();
userInfo.setId(1);
userInfo.setName('张三');
userInfo.setEmail('zhangsan@example.com');
userInfo.setAge(25);// 编码为二进制
const buffer = userInfo.serializeBinary();// 解码二进制数据
const decodedUser = user_pb.UserInfo.deserializeBinary(buffer);
console.log(decodedUser.getName()); // 输出: 张三

与 HTTP API 集成

// 发送 Protobuf 数据到服务器
async function createUser(userData) {const UserInfo = root.lookupType('user.UserInfo');const message = UserInfo.create(userData);const buffer = UserInfo.encode(message).finish();const response = await fetch('/api/users', {method: 'POST',headers: {'Content-Type': 'application/x-protobuf'},body: buffer});return response;
}// 从服务器接收 Protobuf 数据
async function getUsers() {const UserListResponse = root.lookupType('user.UserListResponse');const response = await fetch('/api/users', {headers: {'Accept': 'application/x-protobuf'}});const arrayBuffer = await response.arrayBuffer();const message = UserListResponse.decode(new Uint8Array(arrayBuffer));return message.users; // 返回用户列表
}

TypeScript 支持

当使用静态代码生成时,会得到完整的类型支持:

import * as user_pb from './user_pb';// TypeScript 会提供完整的类型提示
const userInfo: user_pb.UserInfo = new user_pb.UserInfo();
userInfo.setName('张三'); // IDE 会提示 setName 方法// 解码后的类型也是明确的
const decodedUser: user_pb.UserInfo = user_pb.UserInfo.deserializeBinary(buffer);
const userName: string = decodedUser.getName(); // 明确的返回类型

实用技巧

1. 错误处理

const UserInfo = root.lookupType('user.UserInfo');// 验证消息
const errMsg = UserInfo.verify({id: 1,name: '张三'
});if (errMsg) {throw Error(errMsg);
}// 安全创建消息
try {const message = UserInfo.create({id: 1,name: '张三'});
} catch (error) {console.error('创建消息失败:', error);
}

2. 处理枚举类型

// 在 .proto 文件中定义枚举
enum UserRole {USER = 0;ADMIN = 1;SUPER_ADMIN = 2;
}message UserInfo {int32 id = 1;string name = 2;UserRole role = 3;
}
// 使用枚举
const userInfo = new user_pb.UserInfo();
userInfo.setRole(user_pb.UserRole.ADMIN);

3. 处理可选字段

message UserInfo {int32 id = 1;string name = 2;optional string nickname = 3; // 可选字段
}

与其他技术栈集成

与 React 结合

import React, { useEffect, useState } from 'react';
import * as user_pb from './user_pb';function UserList() {const [users, setUsers] = useState([]);useEffect(() => {fetchUsers();}, []);async function fetchUsers() {try {const response = await fetch('/api/users', {headers: { 'Accept': 'application/x-protobuf' }});const arrayBuffer = await response.arrayBuffer();const userListResponse = user_pb.UserListResponse.deserializeBinary(new Uint8Array(arrayBuffer));setUsers(userListResponse.getUsersList());} catch (error) {console.error('获取用户列表失败:', error);}}return (<div>{users.map(user => (<div key={user.getId()}><h3>{user.getName()}</h3><p>{user.getEmail()}</p></div>))}</div>);
}

性能对比示例

// JSON 数据示例 (~100 bytes)
const jsonData = {id: 1,name: "张三",email: "zhangsan@example.com",age: 25
};// Protobuf 编码后 (~30 bytes)
const UserInfo = root.lookupType('user.UserInfo');
const message = UserInfo.create(jsonData);
const buffer = UserInfo.encode(message).finish();console.log('JSON size:', JSON.stringify(jsonData).length); // 约 100 字节
console.log('Protobuf size:', buffer.length); // 约 30 字节

Protobuf 协议实际使用例子

以下是一些常见的 Protobuf 实际应用场景及示例:

1. 用户管理系统示例

定义 .proto 文件 (user.proto)

syntax = "proto3";package user;// 用户基本信息
message UserInfo {int32 user_id = 1;string username = 2;string email = 3;string phone = 4;int32 age = 5;Gender gender = 6;repeated string tags = 7;
}// 性别枚举
enum Gender {UNKNOWN = 0;MALE = 1;FEMALE = 2;
}// 地址信息
message Address {string country = 1;string province = 2;string city = 3;string street = 4;string postal_code = 5;
}// 用户详情
message UserDetail {UserInfo basic_info = 1;Address home_address = 2;Address work_address = 3;repeated string hobbies = 4;
}// 请求参数
message GetUserRequest {int32 user_id = 1;
}// 响应结果
message GetUserResponse {bool success = 1;string message = 2;UserDetail user_detail = 3;
}// 用户列表请求
message ListUsersRequest {int32 page = 1;int32 page_size = 2;string keyword = 3;
}// 用户列表响应
message ListUsersResponse {bool success = 1;string message = 2;repeated UserDetail users = 3;int32 total = 4;
}

编译生成代码

# 生成 JavaScript 代码
npx pbjs -t static-module -w commonjs -o user_pb.js user.proto# 生成 TypeScript 声明文件
npx pbts -o user_pb.d.ts user_pb.js

前端使用示例

import * as user_pb from './user_pb';// 创建用户详情数据
const userDetail = new user_pb.UserDetail();
const userInfo = new user_pb.UserInfo();
userInfo.setUserId(1001);
userInfo.setUsername('张三');
userInfo.setEmail('zhangsan@example.com');
userInfo.setPhone('13800138000');
userInfo.setAge(28);
userInfo.setGender(user_pb.Gender.MALE);
userInfo.setTagsList(['VIP', '活跃用户']);const homeAddress = new user_pb.Address();
homeAddress.setCountry('中国');
homeAddress.setProvince('北京市');
homeAddress.setCity('北京市');
homeAddress.setStreet('朝阳区某某街道123号');
homeAddress.setPostalCode('100000');userDetail.setBasicInfo(userInfo);
userDetail.setHomeAddress(homeAddress);
userDetail.setHobbiesList(['阅读', '游泳', '旅行']);// 编码为二进制数据
const binaryData = userDetail.serializeBinary();
console.log('序列化后的二进制数据长度:', binaryData.length);// 解码二进制数据
const decodedUserDetail = user_pb.UserDetail.deserializeBinary(binaryData);
console.log('用户名:', decodedUserDetail.getBasicInfo().getUsername());
console.log('邮箱:', decodedUserDetail.getBasicInfo().getEmail());
console.log('家庭地址:', decodedUserDetail.getHomeAddress().getCity());

后端 API 示例 (Node.js)

// Express + Protobuf API 示例
const express = require('express');
const app = express();
const * as user_pb from './user_pb';// 中间件:解析 Protobuf 请求体
app.use('/api/*', express.raw({ type: 'application/x-protobuf' }));// 获取用户详情 API
app.post('/api/user/get', (req, res) => {try {// 解析请求数据const request = user_pb.GetUserRequest.deserializeBinary(req.body);const userId = request.getUserId();// 模拟数据库查询const userData = {user_id: userId,username: '张三',email: 'zhangsan@example.com',phone: '13800138000',age: 28,gender: user_pb.Gender.MALE};// 构造响应数据const response = new user_pb.GetUserResponse();response.setSuccess(true);response.setMessage('获取成功');const userDetail = new user_pb.UserDetail();const userInfo = user_pb.UserInfo.create(userData);userDetail.setBasicInfo(userInfo);response.setUserDetail(userDetail);// 设置响应头并发送 Protobuf 数据res.set('Content-Type', 'application/x-protobuf');res.send(Buffer.from(response.serializeBinary()));} catch (error) {const response = new user_pb.GetUserResponse();response.setSuccess(false);response.setMessage('获取失败: ' + error.message);res.set('Content-Type', 'application/x-protobuf');res.status(500).send(Buffer.from(response.serializeBinary()));}
});// 用户列表 API
app.post('/api/user/list', async (req, res) => {try {// 解析请求参数const request = user_pb.ListUsersRequest.deserializeBinary(req.body);const page = request.getPage();const pageSize = request.getPageSize();const keyword = request.getKeyword();// 模拟数据库查询const mockUsers = [{basic_info: {user_id: 1001,username: '张三',email: 'zhangsan@example.com',age: 28,gender: user_pb.Gender.MALE},hobbies: ['阅读', '游泳']},{basic_info: {user_id: 1002,username: '李四',email: 'lisi@example.com',age: 32,gender: user_pb.Gender.FEMALE},hobbies: ['音乐', '电影']}];// 构造响应const response = new user_pb.ListUsersResponse();response.setSuccess(true);response.setMessage('获取成功');response.setTotal(100);// 添加用户列表const usersList = mockUsers.map(userObj => {const userDetail = new user_pb.UserDetail();const userInfo = user_pb.UserInfo.create(userObj.basic_info);userDetail.setBasicInfo(userInfo);userDetail.setHobbiesList(userObj.hobbies);return userDetail;});response.setUsersList(usersList);// 发送响应res.set('Content-Type', 'application/x-protobuf');res.send(Buffer.from(response.serializeBinary()));} catch (error) {const response = new user_pb.ListUsersResponse();response.setSuccess(false);response.setMessage('获取失败: ' + error.message);res.set('Content-Type', 'application/x-protobuf');res.status(500).send(Buffer.from(response.serializeBinary()));}
});

2. 实时聊天系统示例

定义聊天协议 (chat.proto)

syntax = "proto3";package chat;// 消息类型枚举
enum MessageType {TEXT = 0;IMAGE = 1;FILE = 2;SYSTEM = 3;
}// 用户信息
message UserInfo {int32 user_id = 1;string username = 2;string avatar = 3;bool online = 4;
}// 聊天消息
message ChatMessage {int32 message_id = 1;int32 sender_id = 2;int32 receiver_id = 3;MessageType type = 4;string content = 5;int64 timestamp = 6;string extra_data = 7; // 扩展数据,如图片URL、文件信息等
}// 消息列表
message MessageList {repeated ChatMessage messages = 1;int32 unread_count = 2;
}// 聊天室信息
message ChatRoom {int32 room_id = 1;string room_name = 2;repeated UserInfo members = 3;ChatMessage last_message = 4;int32 unread_count = 5;
}// WebSocket 消息
message WebSocketMessage {string action = 1; // "send", "receive", "join", "leave" 等oneof payload {ChatMessage chat_message = 2;UserInfo user_info = 3;ChatRoom chat_room = 4;}
}

前端 WebSocket 使用示例

import * as chat_pb from './chat_pb';class ChatService {constructor() {this.ws = null;this.userId = null;}connect(userId) {this.userId = userId;this.ws = new WebSocket('ws://localhost:8080/chat');this.ws.binaryType = 'arraybuffer';this.ws.onopen = () => {console.log('连接已建立');// 发送加入房间消息this.sendJoinMessage();};this.ws.onmessage = (event) => {if (event.data instanceof ArrayBuffer) {// 处理 Protobuf 消息this.handleProtobufMessage(event.data);}};this.ws.onerror = (error) => {console.error('WebSocket 错误:', error);};}sendJoinMessage() {const wsMessage = new chat_pb.WebSocketMessage();wsMessage.setAction('join');const userInfo = new chat_pb.UserInfo();userInfo.setUserId(this.userId);userInfo.setUsername('用户' + this.userId);userInfo.setOnline(true);wsMessage.setUserInfo(userInfo);this.sendMessage(wsMessage);}sendTextMessage(receiverId, content) {const wsMessage = new chat_pb.WebSocketMessage();wsMessage.setAction('send');const chatMessage = new chat_pb.ChatMessage();chatMessage.setMessageId(Date.now());chatMessage.setSenderId(this.userId);chatMessage.setReceiverId(receiverId);chatMessage.setType(chat_pb.MessageType.TEXT);chatMessage.setContent(content);chatMessage.setTimestamp(Date.now());wsMessage.setChatMessage(chatMessage);this.sendMessage(wsMessage);}sendMessage(wsMessage) {if (this.ws && this.ws.readyState === WebSocket.OPEN) {const binaryData = wsMessage.serializeBinary();this.ws.send(binaryData);}}handleProtobufMessage(data) {try {const wsMessage = chat_pb.WebSocketMessage.deserializeBinary(new Uint8Array(data));const action = wsMessage.getAction();switch (action) {case 'receive':const chatMessage = wsMessage.getChatMessage();this.displayMessage(chatMessage);break;case 'join':const userInfo = wsMessage.getUserInfo();this.updateUserStatus(userInfo);break;default:console.log('未知消息类型:', action);}} catch (error) {console.error('解析 Protobuf 消息失败:', error);}}displayMessage(chatMessage) {const messageType = chatMessage.getType();const content = chatMessage.getContent();const timestamp = new Date(chatMessage.getTimestamp());console.log(`[${timestamp.toLocaleString()}] 新消息:`, content);// 在界面上显示消息...}updateUserStatus(userInfo) {const username = userInfo.getUsername();const isOnline = userInfo.getOnline();console.log(`${username} ${isOnline ? '上线了' : '下线了'}`);// 更新用户状态显示...}
}// 使用示例
const chatService = new ChatService();
chatService.connect(1001); // 用户ID为1001// 发送文本消息
setTimeout(() => {chatService.sendTextMessage(1002, '你好,这是一条测试消息!');
}, 2000);

3. 性能对比演示

// 对比 JSON 和 Protobuf 的大小和性能
import * as user_pb from './user_pb';// 模拟大量用户数据
const generateMockUsers = (count) => {const users = [];for (let i = 0; i < count; i++) {users.push({user_id: 1000 + i,username: `用户${1000 + i}`,email: `user${1000 + i}@example.com`,phone: `1380013${String(1000 + i).padStart(4, '0')}`,age: 20 + (i % 30),gender: i % 2 === 0 ? 1 : 2,tags: ['普通用户', i % 5 === 0 ? 'VIP' : '']});}return users;
};// JSON 方式
const jsonData = generateMockUsers(1000);
const jsonString = JSON.stringify(jsonData);
console.log('JSON 数据大小:', jsonString.length, '字节');// Protobuf 方式
const userList = new user_pb.ListUsersResponse();
const usersList = jsonData.map(userData => {const userDetail = new user_pb.UserDetail();const userInfo = user_pb.UserInfo.create(userData);userDetail.setBasicInfo(userInfo);return userDetail;
});userList.setUsersList(usersList);
userList.setTotal(1000);
userList.setSuccess(true);
userList.setMessage('获取成功');const protobufData = userList.serializeBinary();
console.log('Protobuf 数据大小:', protobufData.length, '字节');console.log('压缩率:', ((jsonString.length - protobufData.length) / jsonString.length * 100).toFixed(2) + '%');// 性能测试
console.time('JSON 序列化');
for (let i = 0; i < 1000; i++) {JSON.stringify(jsonData);
}
console.timeEnd('JSON 序列化');console.time('Protobuf 序列化');
for (let i = 0; i < 1000; i++) {userList.serializeBinary();
}
console.timeEnd('Protobuf 序列化');

4. 错误处理最佳实践

import * as user_pb from './user_pb';class ProtobufHandler {static encodeMessage(message, messageType) {try {// 验证消息格式const errMsg = messageType.verify(message);if (errMsg) {throw new Error(`消息验证失败: ${errMsg}`);}// 创建并编码消息const msgInstance = messageType.create(message);return msgInstance.serializeBinary();} catch (error) {console.error('编码消息失败:', error);throw error;}}static decodeMessage(buffer, messageType) {try {// 检查输入数据if (!buffer || buffer.byteLength === 0) {throw new Error('无效的输入数据');}// 解码消息return messageType.deserializeBinary(new Uint8Array(buffer));} catch (error) {console.error('解码消息失败:', error);throw new Error(`解码失败: ${error.message}`);}}// 使用示例static async sendUserData(userData) {try {const binaryData = this.encodeMessage(userData, user_pb.UserInfo);const response = await fetch('/api/user', {method: 'POST',headers: {'Content-Type': 'application/x-protobuf'},body: binaryData});if (!response.ok) {throw new Error(`HTTP ${response.status}: ${response.statusText}`);}const arrayBuffer = await response.arrayBuffer();const result = this.decodeMessage(arrayBuffer, user_pb.GetUserResponse);if (!result.getSuccess()) {throw new Error(result.getMessage());}return result.getUserDetail();} catch (error) {console.error('处理用户数据失败:', error);throw error;}}
}// 使用示例
ProtobufHandler.sendUserData({user_id: 1001,username: '张三',email: 'zhangsan@example.com'
}).then(userDetail => {console.log('用户详情:', userDetail);
}).catch(error => {console.error('操作失败:', error.message);
});

这些示例展示了 Protobuf 在实际项目中的各种应用场景,包括用户管理、实时通信、性能优化和错误处理等方面。通过这些例子,你可以更好地理解如何在自己的项目中使用 Protobuf 技术。

http://www.dtcms.com/a/466561.html

相关文章:

  • K230基础-录放视频
  • 衡水市住房和城乡规划建设网站带着购物系统回到80年代
  • microsoft免费网站那些网站做的非常好看
  • gta5网站正在建设网站基本流程
  • C++学习过程中的一个问题
  • 外贸一般上什么网站动漫制作专业可以升大专吗
  • 构建外贸智能决策大脑
  • 网站建设费税收分类好的做网站的公司
  • Ubuntu 查看内存大小的多种方法
  • 淄博网站外包wordpress设置缓存
  • ProVerif: 形式化证明工具
  • 卷积神经网络CNN(三):三维卷积与多核卷积
  • AI大事记11:从 AlphaGo 到 AlphaGo Zero(下)
  • HTB:Artificial[WriteUP]
  • 网站开发ppt模板免费字体设计
  • openharmony 4.1r ota升级包制作笔记
  • STM32F103RCT6+STM32CubeMX+keil5(MDK-ARM)+Flymcu实现串口重定向
  • 软件设计师——12 案例分析专题-数据流图
  • redis字符串命令
  • 做平面设计的网站wordpress app开发
  • ANSI A1860.1-2017 刨花板地板检测
  • 天津网站seo设计新乡市工程建设信息网
  • iOS 26 崩溃日志解析,新版系统下崩溃获取与诊断策略
  • 成都 网站建设 公司wordpress写模版
  • 经销商城建站网站页头
  • jvm中程序计数器
  • 网站建设代理公司网站评估内容 优帮云
  • 宁波做网站的公司找摄影作品的网站
  • 企业AI化转型的核心抓手:企业智脑如何推动技术与业务深度融合
  • 基于STM32的智能台灯 / WIFI智能台灯 / 智能无极调光台灯