各位观众老爷,大家好!我是你们的老朋友,今天咱们不聊风花雪月,来聊聊WebUSB里那些“妖艳贱货”——USB传输类型。
话说这WebUSB啊,让浏览器直接跟USB设备“勾搭”上了,想想都刺激。但想玩转它,就得先摸清USB传输类型的脾气,不然,小心你的数据像脱缰的野马,不知跑哪儿去了。
USB传输类型,简单来说,就是USB设备跟主机(比如你的电脑)之间数据交流的方式。一共四种,各有千秋,咱们一个一个来扒皮。
一、Control Transfer(控制传输):USB界的“外交官”
Control Transfer,翻译过来就是“控制传输”,听着就感觉很正式。它就像USB世界的“外交官”,负责设备配置、状态查询、命令下达等重要任务。
- 特点: 可靠性高,速度慢,双向。
- 用途: 设备枚举、配置、状态查询、控制命令。
- 比喻: 就像给设备发“圣旨”,必须确保对方收到,并按旨意办事。
Control Transfer的结构:
Control Transfer 通常由三个阶段组成:
- Setup Stage (设置阶段): 主机发送一个包含请求类型、请求代码、索引和长度的Setup包。这个包就像是“外交信函的信封”,告诉设备要做什么。
- Data Stage (数据阶段): 主机或设备根据Setup包中的指示,发送或接收数据。这就像是“信函的正文”。
- Status Stage (状态阶段): 主机或设备发送一个零长度的数据包(ZLP),表示传输完成。这就像是“盖章确认”,表示对方已收到并处理完毕。
代码示例:
async function controlTransferOut(device, requestType, request, value, index, data) {
try {
const result = await device.controlTransferOut({
requestType: requestType,
recipient: "device", // 或 "interface", "endpoint", "other"
request: request,
value: value,
index: index
}, data);
if (result.status === "ok") {
console.log("Control Transfer Out successful!");
} else {
console.error("Control Transfer Out failed:", result.status);
}
} catch (error) {
console.error("Control Transfer Out error:", error);
}
}
async function controlTransferIn(device, requestType, request, value, index, length) {
try {
const result = await device.controlTransferIn({
requestType: requestType,
recipient: "device", // 或 "interface", "endpoint", "other"
request: request,
value: value,
index: index
}, length);
if (result.status === "ok") {
console.log("Control Transfer In successful!", result.data);
return result.data;
} else {
console.error("Control Transfer In failed:", result.status);
return null;
}
} catch (error) {
console.error("Control Transfer In error:", error);
return null;
}
}
// 使用示例:获取设备描述符
async function getDeviceDescriptor(device) {
const requestType = "standard"; // 标准请求
const request = 6; // GET_DESCRIPTOR 请求
const value = 0x0100; // 设备描述符类型和索引
const index = 0;
const length = 18; // 设备描述符长度
const descriptor = await controlTransferIn(device, requestType, request, value, index, length);
if (descriptor) {
// 将 DataView 转换为 Uint8Array
const descriptorArray = new Uint8Array(descriptor.buffer);
console.log("Device Descriptor:", descriptorArray);
// 在这里解析设备描述符
}
}
// 使用示例:设置配置
async function setConfiguration(device, configurationValue) {
const requestType = "standard";
const request = 9; // SET_CONFIGURATION 请求
const value = configurationValue; // 配置值
const index = 0;
const data = new Uint8Array([]); // 无数据
await controlTransferOut(device, requestType, request, value, index, data);
}
// 假设 device 已经连接
// 举例 调用
// await getDeviceDescriptor(device);
// await setConfiguration(device, 1);
注意事项:
requestType
、recipient
、request
、value
、index
这些参数要仔细设置,搞错了,设备可不认账。- Control Transfer通常用于短数据传输,不适合大量数据。
controlTransferOut
用于主机向设备发送数据,controlTransferIn
用于主机从设备接收数据。requestType
可以是 "standard" (标准请求), "class" (类请求), 或 "vendor" (厂商请求)。recipient
可以是 "device" (设备), "interface" (接口), "endpoint" (端点), 或 "other" (其他)。
二、Bulk Transfer(批量传输):USB界的“搬运工”
Bulk Transfer,翻译过来就是“批量传输”,顾名思义,它适合传输大量数据,但对实时性要求不高。
- 特点: 速度快,可靠性高,尽力而为,流量控制。
- 用途: 大容量存储设备、打印机、扫描仪。
- 比喻: 就像“搬运工”,一次搬很多东西,但可以慢点,只要保证送到就行。
Bulk Transfer的结构:
Bulk Transfer 比较简单,就是主机或设备将数据分成若干个数据包,然后依次发送。如果数据包出错,会进行重传,直到成功或放弃。
代码示例:
async function bulkTransferOut(endpoint, data) {
try {
const result = await endpoint.transferOut(data);
console.log("Bulk Transfer Out successful!", result);
} catch (error) {
console.error("Bulk Transfer Out error:", error);
}
}
async function bulkTransferIn(endpoint, length) {
try {
const result = await endpoint.transferIn(length);
console.log("Bulk Transfer In successful!", result);
return result.data;
} catch (error) {
console.error("Bulk Transfer In error:", error);
return null;
}
}
// 使用示例:向Bulk Out端点发送数据
async function sendDataToDevice(device, endpointNumber, data) {
const endpoint = device.configuration.interfaces[0].alternate.endpoints.find(e => e.endpointNumber === endpointNumber && e.direction === "out" && e.type === "bulk");
if (!endpoint) {
console.error("Bulk Out endpoint not found!");
return;
}
await bulkTransferOut(endpoint, data);
}
// 使用示例:从Bulk In端点接收数据
async function receiveDataFromDevice(device, endpointNumber, length) {
const endpoint = device.configuration.interfaces[0].alternate.endpoints.find(e => e.endpointNumber === endpointNumber && e.direction === "in" && e.type === "bulk");
if (!endpoint) {
console.error("Bulk In endpoint not found!");
return null;
}
return await bulkTransferIn(endpoint, length);
}
// 假设 device 已经连接,endpointNumber 为端点号
// 举例 调用
// const dataToSend = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
// await sendDataToDevice(device, 2, dataToSend);
// const receivedData = await receiveDataFromDevice(device, 1, 64);
// if (receivedData) {
// const receivedArray = new Uint8Array(receivedData.buffer);
// console.log("Received data:", receivedArray);
// }
注意事项:
- Bulk Transfer 依赖于USB总线的可用带宽,如果总线繁忙,速度可能会下降。
- Bulk Transfer 有流量控制机制,防止设备或主机 overwhelmed。
transferOut
用于主机向设备发送数据,transferIn
用于主机从设备接收数据。- 要确保找到正确的Bulk In和Bulk Out端点。
三、Interrupt Transfer(中断传输):USB界的“快递小哥”
Interrupt Transfer,翻译过来就是“中断传输”,它适合传输少量、频繁的数据,对实时性有一定要求。
- 特点: 速度较慢,可靠性高,周期性传输,延迟敏感。
- 用途: 键盘、鼠标、游戏手柄。
- 比喻: 就像“快递小哥”,定期送一些小包裹,虽然不多,但要及时送到。
Interrupt Transfer的结构:
Interrupt Transfer 也是将数据分成数据包,然后周期性地发送。如果数据包出错,会进行重传。
代码示例:
async function interruptTransferIn(endpoint, length) {
try {
const result = await endpoint.transferIn(length);
console.log("Interrupt Transfer In successful!", result);
return result.data;
} catch (error) {
console.error("Interrupt Transfer In error:", error);
return null;
}
}
// 使用示例:从 Interrupt In 端点接收数据
async function receiveDataFromInterruptEndpoint(device, endpointNumber, length) {
const endpoint = device.configuration.interfaces[0].alternate.endpoints.find(e => e.endpointNumber === endpointNumber && e.direction === "in" && e.type === "interrupt");
if (!endpoint) {
console.error("Interrupt In endpoint not found!");
return null;
}
return await interruptTransferIn(endpoint, length);
}
// 假设 device 已经连接,endpointNumber 为端点号
// 举例 调用
// const receivedData = await receiveDataFromInterruptEndpoint(device, 1, 8);
// if (receivedData) {
// const receivedArray = new Uint8Array(receivedData.buffer);
// console.log("Received data:", receivedArray);
// }
注意事项:
- Interrupt Transfer 有固定的轮询间隔,设备会按照这个间隔发送数据。
- Interrupt Transfer 适合传输少量、频繁的数据,比如鼠标的移动、键盘的按键。
transferIn
用于主机从设备接收数据。- 要确保找到正确的Interrupt In端点。
四、Isochronous Transfer(同步传输):USB界的“直播达人”
Isochronous Transfer,翻译过来就是“同步传输”,它适合传输大量、实时性要求极高的数据,但允许少量错误。
- 特点: 速度快,实时性高,允许少量错误,带宽预留。
- 用途: 音频、视频设备。
- 比喻: 就像“直播达人”,必须保证画面流畅,声音清晰,就算偶尔卡顿一下,观众也能接受。
Isochronous Transfer的结构:
Isochronous Transfer 在USB总线上预留一定的带宽,保证数据能够及时传输。如果数据包出错,不会进行重传,而是直接丢弃。
代码示例:
很遗憾,WebUSB API 不支持 Isochronous Transfer。因为 Isochronous Transfer 需要对USB总线进行更底层的控制,而WebUSB API 为了安全性和易用性,屏蔽了这些底层细节。
总结:
咱们来用一张表格总结一下这四种传输类型的特点:
传输类型 | 特点 | 用途 | 可靠性 | 速度 | 实时性 | WebUSB支持 |
---|---|---|---|---|---|---|
Control Transfer | 可靠性高,速度慢,双向 | 设备枚举、配置、状态查询、控制命令 | 高 | 慢 | 低 | 支持 |
Bulk Transfer | 速度快,可靠性高,尽力而为,流量控制 | 大容量存储设备、打印机、扫描仪 | 高 | 快 | 低 | 支持 |
Interrupt Transfer | 速度较慢,可靠性高,周期性传输,延迟敏感 | 键盘、鼠标、游戏手柄 | 高 | 较慢 | 中 | 支持 |
Isochronous Transfer | 速度快,实时性高,允许少量错误,带宽预留 | 音频、视频设备 | 低 | 快 | 高 | 不支持 |
选择合适的传输类型:
选择哪种传输类型,取决于你的设备和应用的需求:
- 如果需要可靠地传输少量控制信息,选择 Control Transfer。
- 如果需要传输大量数据,但对实时性要求不高,选择 Bulk Transfer。
- 如果需要传输少量、频繁的数据,且对实时性有一定要求,选择 Interrupt Transfer。
- 如果需要传输大量、实时性要求极高的数据,但允许少量错误,很遗憾,WebUSB不支持,你得考虑其他方案了。
最后,再啰嗦几句:
- WebUSB虽然方便,但也要注意安全问题,不要连接来源不明的USB设备。
- 在开发WebUSB应用时,要仔细阅读设备文档,了解设备的通信协议。
- 多做实验,多调试,才能真正掌握WebUSB的精髓。
好了,今天的讲座就到这里,希望大家有所收获。下次再见!