这个系列首要解决的是多进程的即时通讯,所以我在上次的文章中将长链接部分直接规划成按业务分层的模式了,这样有一个好处便是不管长链接是按照什么途径完成的,都不影响我这个框架。
接下来最首要的便是要挑选长链接中数据的传输格局,关于数据传输在这个框架中仅仅做一个透传,这得益于我上次规划的长链接分层架构,能够完美的将数据的主张和处理都交给接入SDK的人来管理,而我只担任传输。
以下文章是此系列的前文
Android IM即时通讯多进程中间件规划与完成
IM即时通讯多进程中间件的规划与完成-剥离长衔接,让组件职责更单一
服务端代码 node.js 写的
客户端代码
即时通讯中常见的传输格局
一切规划、长链接等都是为通讯而服务的,所以传输介质的挑选是十分重要的,在客户端的开发过程中常见的传输格局有以下几种:
- JSON(JavaScript Object Notation):JSON是一种轻量级的数据交换格局,易于阅览和编写,并且广泛用于Web应用程序中。在即时通讯中,JSON格局一般用于传输谈天音讯、老友列表、群组信息等数据。
- XML(Extensible Markup Language):XML是一种符号言语,能够用于描绘复杂的数据结构,与JSON相似,但它更合适用于传输文本数据。在即时通讯中,XML格局一般用于传输谈天记录、联系人信息等数据。
- Protocol Buffers:Protocol Buffers是一种高效的数据序列化格局,能够将结构化数据编码为紧凑且高效的二进制格局,比JSON和XML更小,更快,更简略。在即时通讯中,Protocol Buffers格局一般用于传输通讯协议、音讯结构等数据。
- BSON(Binary JSON):BSON是一种二进制表明格局,与JSON相似,但更合适用于处理二进制数据,由于它支撑更多的数据类型,如日期、正则表达式、二进制数据等。在即时通讯中,BSON格局一般用于传输二进制数据、图片、音频、视频等多媒体数据。
第一种和第二种我们应该是耳熟能详了,毕竟入行以来的一切数据传输都离不开这两个格局,特别是json格局,关于第三种而言,近几年较火,现在用的人也越来越多,最终一个不是很了解,仅仅知道有这么一个东西。
常见格局的比照
格局 | 优点 | 缺点 | 运用场景 |
---|---|---|---|
JSON | 1. 可读性强,易于调试和开发。2. 支撑多种编程言语。3. 合适传输文本数据和结构化数据。4. 可扩展性好,能够增加自界说的数据类型和字段。 | 1. 不支撑二进制数据传输,比方图片、音频、视频等多媒体数据。2. 关于很多数据,JSON格局比较冗长,占用网络带宽。 | 合适传输文本数据和结构化数据,如谈天音讯、老友列表、群组信息等。 |
Protocol Buffers | 1. 二进制格局,比JSON和XML更小、更快、更简略。2. 支撑多种编程言语。3. 合适传输通讯协议、音讯结构等数据。 | 1. 不支撑自界说数据类型,需求提早界说好音讯结构。2. 可读性较差,不易于调试。 | 合适传输通讯协议、音讯结构等数据,比方登录认证、音讯传输等。 |
XML | 1. 支撑自界说数据类型和结构。2. 支撑多种编程言语。3. 合适传输文本数据和结构化数据。 | 1. 与JSON和Protocol Buffers比较,XML格局比较冗长,占用网络带宽。2. 可读性较差,不易于调试。 | 合适传输谈天记录、联系人信息等数据。 |
BSON | 1. 支撑多种数据类型,如日期、正则表达式、二进制数据等。2. 支撑二进制数据传输,比JSON和XML更合适传输多媒体数据。3. 合适传输很多数据。 | 1. 不支撑自界说数据类型,需求运用预界说的数据类型。2. 可读性较差,不易于调试。 | 合适传输图片、音频、视频等多媒体数据。 |
即时通讯应该选哪个
经过上述比照,在即时通讯方面应该现已很明显了,针对即时通讯场景,应该挑选 Protocol Buffers 或 BSON 格局。这是由于即时通讯需求实时性,传输速度较快,而 Protocol Buffers 和 BSON 格局都是二进制格局,比 JSON 和 XML 更小、更快、更简略,能够更快地传输数据。此外,BSON 格局支撑二进制数据传输,合适传输多媒体数据,因此更合适传输图片、音频、视频等多媒体数据。假如需求支撑多种编程言语,Protocol Buffers 是更好的挑选,由于它支撑多种编程言语,包括Java、C++、Python、JavaScript等
在即时通讯的业务中应该没有人传输一张图片或者一个文件的,这太大了,不小心会触发长链接的传输约束,会带来不用要的费事。
比方WebSocket。
虽然WebSocket 的数据传输巨细并没有一个固定的上限,由于 WebSocket 是基于 TCP 的,而 TCP 协议自身没有数据巨细的约束,只受限于网络带宽、网络拥塞等因素。但是,WebSocket 在传输数据时会把数据分片(fragmentation)后发送,每个数据帧(frame)的巨细受限于 WebSocket 协议规范和浏览器的完成。
依据 WebSocket 协议规范,WebSocket 数据帧的巨细是没有约束的,但浏览器在完成时一般会设置一个默许的巨细约束。例如,在 Chrome 浏览器中,单个 WebSocket 数据帧巨细的默许上限是 256KB,假如超过这个巨细,浏览器会将数据分割成多个数据帧来发送。而在 Firefox 浏览器中,单个 WebSocket 数据帧巨细的默许上限为 4MB。
虽然 WebSocket 的数据传输巨细没有明确的上限,但是在实际运用中,主张操控单个数据帧的巨细,以免占用过多的网络带宽和资源,影响体系功用。一般主张将单个数据帧的巨细操控在几百KB以内,依据具体情况来决定。
所以仍是选用Protocol Buffers 传输更为真实。
Protocol Buffers
Protocol Buffers(简称 Protobuf)是由 Google 开发的一种轻量级、高效、可扩展的序列化数据交换格局。它能够被用于数据序列化、通讯协议规划、装备文件等多个领域。
与其他序列化格局(如 JSON 和 XML)比较,Protocol Buffers 具有更小的数据体积、更快的解析速度和更高的兼容性。它支撑多种言语(包括 C++、Java、Python、Go、C# 等)的生成和解析,能够方便地进行跨言语数据交换。
Protocol Buffers 界说数据结构和音讯格局时运用的是 .proto 文件,经过编译器生成不同言语的代码。音讯格局能够经过增加或删除字段、修正数据类型等方法进行晋级,一起保持向前和向后兼容性。这种特性在大规模分布式体系中具有重要意义,由于它能够避免因晋级导致的兼容性问题,减少体系维护的难度和风险。
protobuf 在 Android 上的步骤
一般情况下,都是服务端定好pb, 客户端只需求运用就能够了,但是这个系列中,我自己做一个默许完成,所以有了这个模块
装备
- 根项目build.gradle 中增加:
id 'com.google.protobuf' version '0.8.17' apply false
- 在module中增加(首要在首要运用module中增加(编译pb的module)其他module引入pb依赖即可)
apply plugin: 'com.google.protobuf'
protobuf{
protoc{
artifact = 'com.google.protobuf:protoc:3.7.0'
}
plugins {
javalite {
// The codegen for lite comes as a separate artifact
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
// In most cases you don't need the full Java output
// if you use the lite output.
remove java
}
task.builtins {
java {}
}
}
}
}
depends {
implementation 'com.google.protobuf:protobuf-java:3.18.1'
}
留意这个位置: :protobuf-java 和 :protobuf-lite 的差异
com.google.protobuf:protobuf-lite 和 com.google.protobuf:protobuf-java 都是 Google Protocol Buffers 的库,不同之处在于它们的功用和巨细。
protobuf-java 是完整的 Protocol Buffers 库,支撑运用 .proto 文件生成代码和运转时解析和序列化音讯。它提供了许多高档特性,例如支撑多种言语、自界说选项和扩展等。
protobuf-lite 是一个轻量级库,能够运用 .proto 文件生成代码,但不支撑运转时解析和序列化音讯。比较于protobuf-java,它更小巧,运转速度更快,适用于移动设备或带宽有限的环境下运用。它不支撑一些高档特性,例如扩展、自界说选项、反射等。
假如您需求支撑完整的 Protocol Buffers 功用,主张运用 protobuf-java。假如您只需求在移动设备或网络带宽有限的环境下序列化和反序列化简略的音讯,能够挑选 protobuf-lite。
运用步骤 (这是一个十分简略的步骤)
- 界说音讯格局:在.proto 文件中界说音讯的结构和字段。
syntax = "proto3";
message Person {
string name = 1;
int32 age = 2;
repeated string hobbies = 3;
}
- 编译.proto 文件:运用 protobuf 编译器将 .proto 文件编译成 Java 类。
能够自动编译。
- 运用音讯类:运用生成的 Java 类创立音讯目标,并设置和获取字段的值。
Person person = Person.newBuilder()
.setName("John")
.setAge(30)
.addHobbies("reading")
.addHobbies("swimming")
.build();
- 序列化和反序列化:将音讯目标序列化成字节数组或将字节数组反序列化成音讯目标。
// 序列化
byte[] bytes = person.toByteArray();
// 反序列化
Person person2 = Person.parseFrom(bytes);
常见的语法
syntax = "proto3";
message Person {
string name = 1;
int32 age = 2;
repeated string email = 3;
}
- syntax = “proto3”;:界说了运用的Proto语法版别。
- message:界说了一个音讯类型。
- string和int32:界说了两个字段,分别是字符串类型和32位整数类型。
- repeated:界说了一个重复的字段,能够包括多个值。
- name、age和email:界说了三个字段的称号,用于标识数据中的不同部分。
- = 1、= 2和= 3:界说了每个字段的仅有标识符,用于在二进制格局中标识该字段。
当然还有更多的语法,能够去官方
里边有Protocol Buffers的具体介绍、运用指南、语法参考、常见问题解答等内容。
IMClient SDK 种发送音讯的结构规划
我的主意是这样的,既然我现在现已完成了长链接的分离,那我直接将发送的音讯做贯穿,在即时通讯项目中老旧的项目或许用到json格局传输,新式的基本都是pb,二者都能够转换为bytes[] 数组,我在SDK种透传改bytes[],这样就能够做到SDK不侵入业务了,其他人运用SDK时不用考虑传输介质。
默许结构规划与完成
syntax = "proto3";
package com.example.mylibrary;
message IMClientParams {
// 发送人id
string sendId = 1;
// 接纳人id
string chatWithId = 2;
// 发送时刻
int64 sendTime = 3;
// 音讯版别 服务端做音讯仅有处理
int64 version = 4;
// 客户端音讯仅有标识
int64 msgId = 6;
// 区别音讯的所属类型 比方体系音讯仍是用户音讯
int32 type = 7;
// 音讯类型 依据此类型
int32 cmd = 8;
// 包括的音讯体 type 音讯和cmd 一起效果可过滤仅有音讯
bytes body = 8;
}
// 文本
message TextMessage {
string content = 1;
}
// 图片
message ImageMessage {
string url = 1;
//图片后缀
string prefix = 2;
//原图宽
int32 origWight = 3;
//原图高
int32 origHeight = 4;
//原图巨细
int64 origSize = 5;
//中等缩略图片url
tring midUrl = 6;
//含糊图片地址
string blurryImUrl = 7;
}
此音讯体界说为发送接纳一体,运用时能够运用指定type,决定是那种音讯,共
- 体系音讯
- 用户音讯
- 客服音讯
接着用cmd 决定音讯类型
- 文本
- 图片
- 视频
- 等等
界说大局的枚举,使得前后端一致
syntax = "proto3";
package com.example.mylibrary;
enum IMClientCMDEnum {
NONE_CMD = 0;
//体系音讯
SYS_MSG_CMD = 2000;
//文本单聊
CHAT_TXT_CMD = 2001;
//图片单聊
CHAT_IMG_CMD = 2003;
//重视音讯
FOLLOW_MSG_CMD = 2004;
}
//渠道音讯类型
enum PlatformMsgType {
//用户音讯
USER_MSG_TYPE = 0;
//体系音讯
SYS_MSG_TYPE = 1;
//客服音讯
CUSTOMER_MSG_TYPE = 2;
}
运用发送
// 构建文本音讯
val textMessage = TextMessage.newBuilder().setContent("我是文本音讯").build()
val params = IMClientParams.newBuilder()
//发送的是用户音讯
.setType(IMClientEnum.PlatformMsgType.USER_MSG_TYPE_VALUE)
// 发送的是文本音讯
.setCmd(IMClientEnum.IMClientCMDEnum.CHAT_TXT_CMD_VALUE)
//发送者ID
.setSendId("1011011")
//接纳者
.setChatWithId("102094")
//发送的内容
.setBody(textMessage.toByteString())
//音讯仅有ID
.setMsgId(100)
//发送时刻
.setSendTime(System.currentTimeMillis())
.build()
IMClient.with().send(params.toByteArray())
此例为构建了一条用户纬度的文本音讯
修正接纳的代码
interface IMMessageReceiver {
// 传pb
void onMessageReceived(in byte[] receiveMessage);
}
例如,用node.js 发送数据:
const WebSocket = require('ws');
const protobuf = require('protobufjs');
const imFile = "../public/IMClientParams.proto";
(async () => {
const imContent = await protobuf.load(imFile);
const imContentParser = imContent.lookupType("com.example.mylibrary.IMClientParams");
const ws = new WebSocket('ws://localhost:8080');
ws.on('open', () => {
console.log('Connected to server');
// 创立一个音讯目标
const message = {
from: 'Alice',
to: 'Bob',
content: 'Hello, Bob!'
};
// 将音讯目标序列化为二进制数据
const buffer = imContentParser.encode(imContentParser.create(message)).finish();
// 发送二进制数据
ws.send(buffer);
});
ws.on('message', (message) => {
console.log('Received message:', message);
});
})();