HarmonyOS PC开发中Socket UDP编程:无连接数据传输
HarmonyOS PC开发中Socket UDP编程:无连接数据传输
当速度比可靠更重要时,UDP是你的不二之选
一、为什么需要UDP?
TCP虽然可靠,但可靠性是有代价的。三次握手建立连接、确认重传机制、拥塞控制算法……这些机制保证了数据可靠到达,但也带来了延迟和开销。
某些场景下,我们更看重速度而非可靠性:
实时音视频:视频会议、直播推流。丢几帧画面影响不大,但延迟会破坏实时体验。UDP可以让数据尽快到达,即使偶尔丢失也优于等待重传。
在线游戏:FPS、MOBA类游戏。玩家的操作需要立即同步,延迟100毫秒和200毫秒的体验天差地别。丢包可以插值补偿,但延迟无法忍受。
DNS查询:域名解析。请求响应都很小,一次UDP往返足够。失败就重试,不需要建立连接的开销。
物联网数据上报:传感器定期上报数据。偶尔丢失一两个数据点影响不大,但设备电量有限,不能承受TCP的心跳开销。
广播/多播:局域网设备发现、视频会议多播。UDP支持一对多传输,TCP做不到。
鸿蒙系统提供了@ohos.net.socket模块中的UDP Socket支持,本文将深入探讨。
二、核心原理:UDP特性与工作流程
2.1 UDP vs TCP 对比
2.2 UDP数据报格式
UDP数据报格式非常简单,只有8字节头部:
| 字段 | 大小 | 说明 |
|---|---|---|
| 源端口 | 2字节 | 发送方端口 |
| 目的端口 | 2字节 | 接收方端口 |
| 长度 | 2字节 | 数据报总长度(含头部) |
| 校验和 | 2字节 | 可选的校验和 |
数据部分最大65507字节(65535 - 8字节头 - 20字节IP头)。
2.3 UDP应用场景与注意事项
| 场景 | 为什么选UDP | 需要注意 |
|---|---|---|
| 实时音视频 | 低延迟优先 | 应用层FEC前向纠错 |
| 在线游戏 | 快速同步 | 状态同步、插值补偿 |
| DNS查询 | 简单高效 | 超时重试机制 |
| 局域网发现 | 广播支持 | TTL设置 |
| 大文件传输 | 可靠性自控 | 应用层分片、确认 |
三、代码实战:三种典型场景
场景一:UDP客户端实现
实现UDP数据发送和接收,无需建立连接。
import socket from '@ohos.net.socket';
import { BusinessError } from '@ohos.base';
// UDP客户端配置
interface UDPClientConfig {
localPort?: number; // 本地端口(可选,自动分配)
}
// UDP客户端
class UDPClient {
private socket: socket.UDPSocket | null = null;
private config: UDPClientConfig;
private isBound: boolean = false;
// 回调
private onMessage: ((data: ArrayBuffer, remoteInfo: socket.SocketRemoteInfo) => void) | null = null;
private onError: ((error: BusinessError) => void) | null = null;
constructor(config: UDPClientConfig = {}) {
this.config = config;
}
// 绑定本地端口
async bind(): Promise<boolean> {
if (this.isBound) {
return true;
}
// 创建UDP Socket
this.socket = socket.constructUDPSocketInstance();
try {
// 绑定本地地址
await this.socket.bind({
address: '0.0.0.0',
port: this.config.localPort || 0, // 0表示自动分配
family: 1 // IPv4
});
this.isBound = true;
// 设置监听
this.setupListeners();
// 获取绑定的端口
let state = await this.socket.getState();
console.info(`UDP绑定成功,本地端口: ${state.localPort}`);
return true;
} catch (error) {
console.error('UDP绑定失败:', error);
return false;
}
}
// 设置监听
private setupListeners(): void {
if (!this.socket) return;
// 接收数据
this.socket.on('message', (value: socket.SocketMessageInfo) => {
let data = value.message;
let remoteInfo = value.remoteInfo;
console.debug(`收到UDP数据: ${data.byteLength} 字节,来自 ${remoteInfo.address}:${remoteInfo.port}`);
this.onMessage?.(data, remoteInfo);
});
// 错误
this.socket.on('error', (err: BusinessError) => {
console.error('UDP错误:', err.message);
this.onError?.(err);
});
}
// 发送数据到指定地址
async send(data: string | ArrayBuffer, address: string, port: number): Promise<boolean> {
if (!this.socket || !this.isBound) {
// 自动绑定
let bound = await this.bind();
if (!bound) {
return false;
}
}
try {
let sendData: socket.UDPSendOptions;
if (typeof data === 'string') {
// 字符串转ArrayBuffer
let encoder = new TextEncoder();
sendData = {
data: encoder.encode(data).buffer,
address: {
address: address,
port: port,
family: 1
}
};
} else {
sendData = {
data: data,
address: {
address: address,
port: port,
family: 1
}
};
}
await this.socket.send(sendData);
console.debug(`发送UDP数据: ${sendData.data.byteLength} 字节,到 ${address}:${port}`);
return true;
} catch (error) {
console.error('UDP发送失败:', error);
return false;
}
}
// 发送广播数据
async sendBroadcast(data: string | ArrayBuffer, port: number): Promise<boolean> {
// 发送到广播地址
return await this.send(data, '255.255.255.255', port);
}
// 获取本地端口
async getLocalPort(): Promise<number> {
if (!this.socket) return 0;
try {
let state = await this.socket.getState();
return state.localPort;
} catch (error) {
return 0;
}
}
// 关闭Socket
async close(): Promise<void> {
if (this.socket) {
try {
await this.socket.close();
console.info('UDP Socket已关闭');
} catch (error) {
console.error('关闭UDP Socket失败:', error);
}
this.socket = null;
this.isBound = false;
}
}
// 设置回调
setOnMessage(callback: (data: ArrayBuffer, remoteInfo: socket.SocketRemoteInfo) => void): void {
this.onMessage = callback;
}
setOnError(callback: (error: BusinessError) => void): void {
this.onError = callback;
}
}
// UDP客户端页面
@Entry
@Component
struct UDPClientPage {
@State localPort: number = 0;
@State receivedMessages: string[] = [];
@State sentCount: number = 0;
@State receivedCount: number = 0;
@State targetAddress: string = '192.168.1.100';
@State targetPort: number = 8888;
@State messageInput: string = '';
private udpClient: UDPClient | null = null;
async aboutToAppear() {
this.udpClient = new UDPClient();
// 设置回调
this.udpClient.setOnMessage((data, remoteInfo) => {
let decoder = new TextDecoder();
let text = decoder.decode(data);
let message = `收到 [${remoteInfo.address}:${remoteInfo.port}]: ${text}`;
this.receivedMessages.unshift(message);
if (this.receivedMessages.length > 20) {
this.receivedMessages.pop();
}
this.receivedCount += data.byteLength;
});
// 绑定
await this.udpClient.bind();
this.localPort = await this.udpClient.getLocalPort();
}
async aboutToDisappear() {
await this.udpClient?.close();
}
// 发送消息
async sendMessage() {
if (!this.messageInput) return;
let success = await this.udpClient?.send(
this.messageInput,
this.targetAddress,
this.targetPort
);
if (success) {
this.sentCount += this.messageInput.length;
this.messageInput = '';
}
}
// 发送广播
async sendBroadcast() {
let message = `Broadcast at ${Date.now()}`;
let success = await this.udpClient?.sendBroadcast(message, this.targetPort);
if (success) {
this.sentCount += message.length;
}
}
build() {
Column() {
// 本地端口显示
Row() {
Text(`本地端口: ${this.localPort}`)
.fontSize(16)
Blank().layoutWeight(1)
Text(`发送: ${this.sentCount} | 接收: ${this.receivedCount}`)
.fontSize(14)
.fontColor('#666')
}
.width('100%')
.padding(15)
// 目标配置
Column() {
Row() {
Text('目标地址:')
.fontSize(14)
.width(80)
TextInput({ text: this.targetAddress })
.layoutWeight(1)
.onChange((value) => this.targetAddress = value)
}
.width('100%')
Row() {
Text('目标端口:')
.fontSize(14)
.width(80)
TextInput({ text: this.targetPort.toString() })
.layoutWeight(1)
.type(InputType.Number)
.onChange((value) => this.targetPort = parseInt(value) || 8888)
}
.width('100%')
.margin({ top: 10 })
}
.width('100%')
.padding(10)
// 消息输入
Row() {
TextInput({ text: this.messageInput, placeholder: '输入消息' })
.layoutWeight(1)
.onChange((value) => this.messageInput = value)
.onSubmit(() => this.sendMessage())
Button('发送')
.onClick(() => this.sendMessage())
.margin({ left: 10 })
Button('广播')
.onClick(() => this.sendBroadcast())
.margin({ left: 10 })
}
.width('100%')
.padding(10)
// 接收消息列表
Column() {
Text('接收消息:')
.fontSize(14)
.width('100%')
List() {
ForEach(this.receivedMessages, (msg: string, index: number) => {
ListItem() {
Text(msg)
.fontSize(12)
.fontFamily('monospace')
}
})
}
.width('100%')
.layoutWeight(1)
.backgroundColor('#F5F5F5')
.padding(10)
}
.width('100%')
.padding(10)
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
场景二:UDP服务端实现
实现UDP服务器,监听端口并响应客户端请求。
import socket from '@ohos.net.socket';
import { BusinessError } from '@ohos.base';
// UDP服务端配置
interface UDPServerConfig {
port: number;
host?: string;
}
// UDP服务端
class UDPServer {
private socket: socket.UDPSocket | null = null;
private config: UDPServerConfig;
private isListening: boolean = false;
// 回调
private onMessage: ((data: ArrayBuffer, remoteInfo: socket.SocketRemoteInfo) => void) | null = null;
private onError: ((error: BusinessError) => void) | null = null;
constructor(config: UDPServerConfig) {
this.config = {
host: '0.0.0.0',
...config
};
}
// 启动服务器
async start(): Promise<boolean> {
if (this.isListening) {
return true;
}
// 创建UDP Socket
this.socket = socket.constructUDPSocketInstance();
try {
// 绑定地址和端口
await this.socket.bind({
address: this.config.host,
port: this.config.port,
family: 1
});
this.isListening = true;
// 设置监听
this.setupListeners();
console.info(`UDP服务器启动: ${this.config.host}:${this.config.port}`);
return true;
} catch (error) {
console.error('UDP服务器启动失败:', error);
return false;
}
}
// 设置监听
private setupListeners(): void {
if (!this.socket) return;
// 接收数据
this.socket.on('message', (value: socket.SocketMessageInfo) => {
let data = value.message;
let remoteInfo = value.remoteInfo;
console.info(`收到UDP请求: ${data.byteLength} 字节,来自 ${remoteInfo.address}:${remoteInfo.port}`);
// 通知上层
this.onMessage?.(data, remoteInfo);
});
// 错误
this.socket.on('error', (err: BusinessError) => {
console.error('UDP服务器错误:', err.message);
this.onError?.(err);
});
}
// 发送响应
async send(data: string | ArrayBuffer, address: string, port: number): Promise<boolean> {
if (!this.socket || !this.isListening) {
return false;
}
try {
let sendData: socket.UDPSendOptions;
if (typeof data === 'string') {
let encoder = new TextEncoder();
sendData = {
data: encoder.encode(data).buffer,
address: {
address: address,
port: port,
family: 1
}
};
} else {
sendData = {
data: data,
address: {
address: address,
port: port,
family: 1
}
};
}
await this.socket.send(sendData);
return true;
} catch (error) {
console.error('UDP响应发送失败:', error);
return false;
}
}
// 停止服务器
async stop(): Promise<void> {
if (this.socket) {
try {
await this.socket.close();
} catch (error) {
// 忽略
}
this.socket = null;
this.isListening = false;
}
console.info('UDP服务器已停止');
}
// 设置回调
setOnMessage(callback: (data: ArrayBuffer, remoteInfo: socket.SocketRemoteInfo) => void): void {
this.onMessage = callback;
}
setOnError(callback: (error: BusinessError) => void): void {
this.onError = callback;
}
// 获取监听状态
isListening_(): boolean {
return this.isListening;
}
}
// UDP服务端页面
@Entry
@Component
struct UDPServerPage {
@State serverStatus: string = '未启动';
@State requestCount: number = 0;
@State logs: string[] = [];
private udpServer: UDPServer | null = null;
async aboutToAppear() {
this.udpServer = new UDPServer({
port: 8888
});
// 设置回调
this.udpServer.setOnMessage(async (data, remoteInfo) => {
this.requestCount++;
// 解析请求
let decoder = new TextDecoder();
let text = decoder.decode(data);
this.addLog(`请求 [${remoteInfo.address}:${remoteInfo.port}]: ${text}`);
// 发送响应
let response = `Echo: ${text}`;
await this.udpServer?.send(response, remoteInfo.address, remoteInfo.port);
this.addLog(`响应 -> [${remoteInfo.address}:${remoteInfo.port}]: ${response}`);
});
}
async aboutToDisappear() {
await this.udpServer?.stop();
}
// 启动服务器
async startServer() {
let success = await this.udpServer?.start();
this.serverStatus = success ? '运行中' : '启动失败';
this.addLog(`服务器${success ? '启动成功' : '启动失败'}`);
}
// 停止服务器
async stopServer() {
await this.udpServer?.stop();
this.serverStatus = '已停止';
this.addLog('服务器已停止');
}
// 添加日志
private addLog(log: string) {
let time = new Date().toLocaleTimeString();
this.logs.unshift(`[${time}] ${log}`);
if (this.logs.length > 50) {
this.logs.pop();
}
}
build() {
Column() {
// 服务器状态
Row() {
Text('服务器状态:')
.fontSize(16)
Text(this.serverStatus)
.fontSize(16)
.fontColor(this.serverStatus === '运行中' ? '#7ED321' : '#666')
.margin({ left: 10 })
Blank().layoutWeight(1)
Text(`请求数: ${this.requestCount}`)
.fontSize(16)
}
.width('100%')
.padding(15)
// 控制按钮
Row() {
Button('启动服务器')
.onClick(() => this.startServer())
.enabled(this.serverStatus !== '运行中')
.layoutWeight(1)
Button('停止服务器')
.onClick(() => this.stopServer())
.enabled(this.serverStatus === '运行中')
.layoutWeight(1)
.margin({ left: 10 })
}
.width('100%')
.padding(10)
// 日志区域
Column() {
Text('服务器日志:')
.fontSize(14)
.width('100%')
List() {
ForEach(this.logs, (log: string, index: number) => {
ListItem() {
Text(log)
.fontSize(12)
.fontFamily('monospace')
}
})
}
.width('100%')
.layoutWeight(1)
.backgroundColor('#F5F5F5')
.padding(10)
}
.width('100%')
.padding(10)
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
场景三:局域网设备发现
使用UDP广播实现局域网设备发现功能。
import socket from '@ohos.net.socket';
// 设备发现协议
interface DiscoveryMessage {
type: 'discover' | 'announce';
deviceId: string;
deviceName: string;
deviceType: string;
ipAddress: string;
port: number;
timestamp: number;
}
// 发现的设备
interface DiscoveredDevice {
deviceId: string;
deviceName: string;
deviceType: string;
ipAddress: string;
port: number;
lastSeen: number;
}
// 设备发现服务
class DeviceDiscoveryService {
private udpSocket: socket.UDPSocket | null = null;
private discoveryPort: number = 38888;
private isRunning: boolean = false;
// 本设备信息
private localDevice: {
deviceId: string;
deviceName: string;
deviceType: string;
port: number;
};
// 发现的设备列表
private discoveredDevices: Map<string, DiscoveredDevice> = new Map();
// 回调
private onDeviceFound: ((device: DiscoveredDevice) => void) | null = null;
private onDeviceLost: ((deviceId: string) => void) | null = null;
// 设备过期时间(毫秒)
private deviceTimeout: number = 30000;
private cleanupTimer: number = -1;
constructor(deviceInfo: {
deviceId: string;
deviceName: string;
deviceType: string;
port: number;
}) {
this.localDevice = deviceInfo;
}
// 启动发现服务
async start(): Promise<boolean> {
if (this.isRunning) {
return true;
}
// 创建UDP Socket
this.udpSocket = socket.constructUDPSocketInstance();
try {
// 绑定到发现端口
await this.udpSocket.bind({
address: '0.0.0.0',
port: this.discoveryPort,
family: 1
});
this.isRunning = true;
// 设置监听
this.setupListeners();
// 启动设备清理定时器
this.startCleanupTimer();
// 发送发现请求
await this.sendDiscoveryRequest();
console.info('设备发现服务已启动');
return true;
} catch (error) {
console.error('启动设备发现服务失败:', error);
return false;
}
}
// 设置监听
private setupListeners(): void {
if (!this.udpSocket) return;
this.udpSocket.on('message', async (value: socket.SocketMessageInfo) => {
try {
// 解析消息
let decoder = new TextDecoder();
let text = decoder.decode(value.message);
let message: DiscoveryMessage = JSON.parse(text);
// 忽略自己的消息
if (message.deviceId === this.localDevice.deviceId) {
return;
}
switch (message.type) {
case 'discover':
// 收到发现请求,回复设备信息
await this.sendAnnounce(value.remoteInfo.address, value.remoteInfo.port);
break;
case 'announce':
// 收到设备公告,更新设备列表
this.handleDeviceAnnounce(message);
break;
}
} catch (error) {
// 忽略解析错误
}
});
}
// 发送发现请求(广播)
private async sendDiscoveryRequest(): Promise<void> {
if (!this.udpSocket) return;
let message: DiscoveryMessage = {
type: 'discover',
deviceId: this.localDevice.deviceId,
deviceName: this.localDevice.deviceName,
deviceType: this.localDevice.deviceType,
ipAddress: '', // 广播消息不需要
port: this.localDevice.port,
timestamp: Date.now()
};
let encoder = new TextEncoder();
let data = encoder.encode(JSON.stringify(message)).buffer;
// 发送广播
try {
await this.udpSocket.send({
data: data,
address: {
address: '255.255.255.255',
port: this.discoveryPort,
family: 1
}
});
console.info('已发送发现请求');
} catch (error) {
console.error('发送发现请求失败:', error);
}
}
// 发送设备公告
private async sendAnnounce(targetAddress: string, targetPort: number): Promise<void> {
if (!this.udpSocket) return;
// 获取本地IP
let localIp = await this.getLocalIp();
let message: DiscoveryMessage = {
type: 'announce',
deviceId: this.localDevice.deviceId,
deviceName: this.localDevice.deviceName,
deviceType: this.localDevice.deviceType,
ipAddress: localIp,
port: this.localDevice.port,
timestamp: Date.now()
};
let encoder = new TextEncoder();
let data = encoder.encode(JSON.stringify(message)).buffer;
try {
await this.udpSocket.send({
data: data,
address: {
address: targetAddress,
port: targetPort,
family: 1
}
});
} catch (error) {
console.error('发送设备公告失败:', error);
}
}
// 处理设备公告
private handleDeviceAnnounce(message: DiscoveryMessage): void {
let device: DiscoveredDevice = {
deviceId: message.deviceId,
deviceName: message.deviceName,
deviceType: message.deviceType,
ipAddress: message.ipAddress,
port: message.port,
lastSeen: Date.now()
};
let isNew = !this.discoveredDevices.has(device.deviceId);
this.discoveredDevices.set(device.deviceId, device);
if (isNew) {
console.info(`发现新设备: ${device.deviceName} (${device.ipAddress}:${device.port})`);
this.onDeviceFound?.(device);
}
}
// 启动设备清理定时器
private startCleanupTimer(): void {
this.cleanupTimer = setInterval(() => {
let now = Date.now();
for (let [deviceId, device] of this.discoveredDevices) {
if (now - device.lastSeen > this.deviceTimeout) {
this.discoveredDevices.delete(deviceId);
this.onDeviceLost?.(deviceId);
console.info(`设备已离线: ${device.deviceName}`);
}
}
}, 5000);
}
// 获取本地IP
private async getLocalIp(): Promise<string> {
// 简化处理,实际需要获取真实IP
return '192.168.1.100';
}
// 停止服务
async stop(): Promise<void> {
if (this.cleanupTimer !== -1) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = -1;
}
if (this.udpSocket) {
try {
await this.udpSocket.close();
} catch (error) {
// 忽略
}
this.udpSocket = null;
}
this.isRunning = false;
this.discoveredDevices.clear();
}
// 获取发现的设备列表
getDiscoveredDevices(): DiscoveredDevice[] {
return Array.from(this.discoveredDevices.values());
}
// 设置回调
setOnDeviceFound(callback: (device: DiscoveredDevice) => void): void {
this.onDeviceFound = callback;
}
setOnDeviceLost(callback: (deviceId: string) => void): void {
this.onDeviceLost = callback;
}
}
// 设备发现页面
@Entry
@Component
struct DeviceDiscoveryPage {
@State isScanning: boolean = false;
@State devices: DiscoveredDevice[] = [];
private discoveryService: DeviceDiscoveryService | null = null;
async aboutToAppear() {
// 初始化发现服务
this.discoveryService = new DeviceDiscoveryService({
deviceId: 'device_' + Math.random().toString(36).substr(2, 9),
deviceName: '我的设备',
deviceType: 'HarmonyOS',
port: 8080
});
// 设置回调
this.discoveryService.setOnDeviceFound((device) => {
this.devices = this.discoveryService?.getDiscoveredDevices() || [];
});
this.discoveryService.setOnDeviceLost((deviceId) => {
this.devices = this.discoveryService?.getDiscoveredDevices() || [];
});
}
async aboutToDisappear() {
await this.discoveryService?.stop();
}
// 开始扫描
async startScan() {
this.isScanning = true;
await this.discoveryService?.start();
// 5秒后停止扫描
setTimeout(() => {
this.isScanning = false;
}, 5000);
}
build() {
Column() {
// 标题
Row() {
Text('局域网设备发现')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank().layoutWeight(1)
Button(this.isScanning ? '扫描中...' : '扫描设备')
.onClick(() => this.startScan())
.enabled(!this.isScanning)
}
.width('100%')
.padding(15)
// 设备列表
if (this.devices.length > 0) {
List() {
ForEach(this.devices, (device: DiscoveredDevice) => {
ListItem() {
this.DeviceCard(device)
}
}, (device: DiscoveredDevice) => device.deviceId)
}
.width('100%')
.layoutWeight(1)
.padding(10)
} else {
Column() {
Text('暂无发现设备')
.fontSize(16)
.fontColor('#999')
Text('点击"扫描设备"开始搜索')
.fontSize(14)
.fontColor('#999')
.margin({ top: 10 })
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
}
.width('100%')
.height('100%')
}
@Builder
DeviceCard(device: DiscoveredDevice) {
Column() {
Row() {
// 设备图标
Column() {
if (device.deviceType === 'HarmonyOS') {
Text('📱')
.fontSize(30)
} else if (device.deviceType === 'IoT') {
Text('🔌')
.fontSize(30)
} else {
Text('💻')
.fontSize(30)
}
}
.width(50)
.height(50)
.justifyContent(FlexAlign.Center)
.backgroundColor('#E8E8E8')
.borderRadius(25)
// 设备信息
Column() {
Text(device.deviceName)
.fontSize(16)
.fontWeight(FontWeight.Medium)
Text(device.deviceType)
.fontSize(12)
.fontColor('#666')
.margin({ top: 4 })
}
.layoutWeight(1)
.margin({ left: 15 })
.alignItems(HorizontalAlign.Start)
// 连接按钮
Button('连接')
.height(36)
.onClick(() => {
console.info(`连接设备: ${device.ipAddress}:${device.port}`);
})
}
.width('100%')
// 地址信息
Row() {
Text(`IP: ${device.ipAddress}`)
.fontSize(12)
.fontColor('#999')
Text(`端口: ${device.port}`)
.fontSize(12)
.fontColor('#999')
.margin({ left: 20 })
}
.width('100%')
.margin({ top: 10 })
}
.width('100%')
.padding(15)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.shadow({ radius: 2, color: '#00000010', offsetX: 0, offsetY: 2 })
}
}
四、踩坑与注意事项
坑点一:数据包大小限制
UDP数据报有大小限制,超过MTU会被IP层分片,增加丢包风险。
// ❌ 错误:发送过大的UDP包
let largeData = new ArrayBuffer(100000); // 100KB
await udpClient.send(largeData, '192.168.1.100', 8888);
// 可能被IP分片,丢包风险高
// ✅ 正确:控制包大小
const MAX_UDP_SIZE = 1400; // 留出IP和UDP头的空间
async function sendLargeData(data: ArrayBuffer, address: string, port: number) {
let offset = 0;
let totalSize = data.byteLength;
while (offset < totalSize) {
let chunkSize = Math.min(MAX_UDP_SIZE, totalSize - offset);
let chunk = data.slice(offset, offset + chunkSize);
await udpClient.send(chunk, address, port);
offset += chunkSize;
// 可选:添加序号用于重组
}
}
坑点二:丢包未处理
UDP不保证可靠传输,需要应用层处理丢包。
// 带确认机制的UDP发送
class ReliableUDP {
private sequence: number = 0;
private pendingAcks: Map<number, { data: ArrayBuffer; timer: number; retries: number }> = new Map();
private maxRetries: number = 3;
private ackTimeout: number = 1000;
// 发送数据并等待确认
async sendReliable(data: ArrayBuffer, address: string, port: number): Promise<boolean> {
let seq = ++this.sequence;
// 添加序号
let packet = this.addSequence(data, seq);
return new Promise(async (resolve) => {
let attempt = () => {
let pending = this.pendingAcks.get(seq);
if (!pending || pending.retries >= this.maxRetries) {
this.pendingAcks.delete(seq);
resolve(false);
return;
}
pending.retries++;
// 发送数据
udpClient.send(packet, address, port);
// 设置超时
pending.timer = setTimeout(attempt, this.ackTimeout);
};
// 初始化
this.pendingAcks.set(seq, {
data: packet,
timer: -1,
retries: 0
});
attempt();
});
}
// 收到确认
handleAck(seq: number): void {
let pending = this.pendingAcks.get(seq);
if (pending) {
clearTimeout(pending.timer);
this.pendingAcks.delete(seq);
}
}
// 添加序号到数据包
private addSequence(data: ArrayBuffer, seq: number): ArrayBuffer {
let packet = new ArrayBuffer(4 + data.byteLength);
let view = new DataView(packet);
view.setUint32(0, seq, false);
let dataView = new Uint8Array(packet, 4);
let sourceView = new Uint8Array(data);
for (let i = 0; i < sourceView.length; i++) {
dataView[i] = sourceView[i];
}
return packet;
}
}
坑点三:端口被占用
绑定端口时可能被其他程序占用。
// ❌ 错误:端口被占用时直接失败
await udpSocket.bind({
address: '0.0.0.0',
port: 8888 // 可能被占用
});
// ✅ 正确:尝试多个端口
async function bindWithFallback(socket: socket.UDPSocket, preferredPort: number): Promise<number> {
let ports = [preferredPort, 8889, 8890, 8891, 0]; // 0表示自动分配
for (let port of ports) {
try {
await socket.bind({
address: '0.0.0.0',
port: port,
family: 1
});
let state = await socket.getState();
return state.localPort;
} catch (error) {
console.warn(`端口 ${port} 绑定失败,尝试下一个`);
}
}
throw new Error('无法绑定任何端口');
}
五、HarmonyOS 6适配指南
5.1 多播支持
HarmonyOS 6支持UDP多播(Multicast)。
import socket from '@ohos.net.socket';
let udpSocket = socket.constructUDPSocketInstance();
await udpSocket.bind({
address: '0.0.0.0',
port: 8888,
family: 1
});
// HarmonyOS 6: 加入多播组
await udpSocket.addMembership({
multicastAddress: '239.255.255.250', // 多播组地址
interface: '192.168.1.100' // 本地接口
});
// 发送多播数据
await udpSocket.send({
data: encoder.encode('Hello Multicast').buffer,
address: {
address: '239.255.255.250',
port: 8888,
family: 1
}
});
// 离开多播组
await udpSocket.dropMembership({
multicastAddress: '239.255.255.250',
interface: '192.168.1.100'
});
5.2 设置Socket选项
HarmonyOS 6提供了更多Socket选项设置。
// HarmonyOS 6: 设置UDP选项
await udpSocket.setOption({
// 设置广播
broadcast: true,
// 设置接收缓冲区大小
receiveBufferSize: 65536,
// 设置发送缓冲区大小
sendBufferSize: 65536,
// 设置TTL(Time To Live)
ttl: 64,
// 设置多播TTL
multicastTtl: 1,
// 设置多播回环
multicastLoopback: false
});
六、总结一下下
UDP是追求速度场景的首选方案。本文从三个场景展开:
UDP客户端:无需建立连接,直接发送数据。注意绑定本地端口、设置接收监听、处理响应。
UDP服务端:监听端口,接收并响应请求。UDP无连接特性使得服务端实现比TCP简单得多。
设备发现:UDP广播的经典应用。发送广播发现请求,接收设备公告,维护设备列表。
三个常见坑点:数据包大小限制、丢包未处理、端口被占用。遇到UDP问题时,先排查这三个方面。
HarmonyOS 6带来了多播支持和丰富的Socket选项,让UDP编程更加强大。但切记:UDP的"不可靠"是特性而非缺陷,在合适的场景下,它比TCP更优秀。
下一篇文章,我们将探讨网络状态监听,实现在线/离线检测功能!
更多推荐



所有评论(0)