Harmony os Socket 编程实战:TCP / UDP / 多播 / TLS 一锅炖学习笔记

Harmony os Socket 编程实战:TCP / UDP / 多播 / TLS 一锅炖学习笔记


这篇就当是我给自己写的 HarmonyOS Socket 总结笔记

从最基础的 TCP/UDP,一路到 Multicast、LocalSocket、TLS、以及"把 TCP 升级成 TLS"的骚操作,都按流程 + 代码 + 心里话的方式过一遍。

后面我要写博客、做 Demo、或者考试复习,直接翻这篇就够用了。


一、先把几个关键词说清楚

HarmonyOS 里的 Socket 能力是通过 @kit.NetworkKit 提供的,支持这些场景:

  • TCP:面向连接、可靠、字节流,适合"聊天、请求响应、长连接"
  • UDP:无连接、尽力而为、面向报文,适合"丢一点没关系"的场景
  • Multicast:UDP 多播,"发一份,组内全员收"
  • LocalSocket:设备内进程间通信(IPC),不走真正的网络
  • TLS / TLSSocket / TLSSocketServer:加密传输(HTTPS 那味儿),支持单向/双向认证
  • TCP 升级 TLS:老项目用的 TCP,可以后面再套一层 TLS,而不是重写所有逻辑

⚠️ 官方还提醒了一句很关键的:

应用退到后台后,Socket 可能会断开,回到前台后如果通信失败,需要根据错误码重新创建新的 TCP/UDP Socket。

翻译成人话:

不要指望连一次能用一辈子,前后台切换后,你要做好"发现断了就重连"的准备。


二、TCP / UDP 客户端统一流程(以 TCP 为例)

UDP 跟 TCP 整体流程差不多,只是没"建立连接"这一步,这里用 TCP 把整个链路先理干净。

1. 导入模块 & 创建 TCPSocket

复制代码
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 创建一个 TCP Socket 实例
let tcp: socket.TCPSocket = socket.constructTCPSocketInstance();

2. (可选)订阅事件:message / connect / close

复制代码
class SocketInfo {
  message: ArrayBuffer = new ArrayBuffer(1);
  remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}

tcp.on('message', (value: SocketInfo) => {
  console.info('on message');
  const buffer = value.message;
  const dataView = new DataView(buffer);
  let str = '';
  for (let i = 0; i < dataView.byteLength; ++i) {
    str += String.fromCharCode(dataView.getUint8(i));
  }
  console.info('received: ' + str);
});

tcp.on('connect', () => {
  console.info('on connect');
});

tcp.on('close', () => {
  console.info('on close');
});

心里话:只要看到 message 回调里一堆 ArrayBuffer/Uint8Array 的转换,就提醒自己------数据到底是文本还是二进制?编码怎么约定?

别在这一步糊涂了,不然两端都怀疑对方有问题。

3. bind + connect + send:一条直线走完

复制代码
// 先绑定本地 IP 和端口
let ipAddress: socket.NetAddress = {} as socket.NetAddress;
ipAddress.address = '192.168.xxx.xxx'; // 本机 IP
ipAddress.port = 1234;                 // 本地端口,可写 0 让系统随机分配

tcp.bind(ipAddress, (err: BusinessError) => {
  if (err) {
    console.error('bind fail');
    return;
  }
  console.info('bind success');

  // bind 成功后,配置目标服务器地址
  ipAddress.address = '192.168.xxx.xxx'; // 服务器 IP
  ipAddress.port = 5678;                 // 服务器端口

  const tcpConnect: socket.TCPConnectOptions = {
    address: ipAddress,
    timeout: 6000,
  };

  // 发起连接
  tcp.connect(tcpConnect).then(() => {
    console.info('connect success');

    const tcpSendOptions: socket.TCPSendOptions = {
      data: 'Hello, server!',
    };

    // 连接成功后发送数据
    tcp.send(tcpSendOptions).then(() => {
      console.info('send success');
    }).catch((err: BusinessError) => {
      console.error('send fail: ' + JSON.stringify(err));
    });
  }).catch((err: BusinessError) => {
    console.error('connect fail: ' + JSON.stringify(err));
  });
});

4. 用完记得 close + off

复制代码
setTimeout(() => {
  tcp.close().then(() => {
    console.info('close success');
  }).catch((err: BusinessError) => {
    console.error('close fail: ' + JSON.stringify(err));
  });

  tcp.off('message');
  tcp.off('connect');
  tcp.off('close');
}, 30 * 1000);

小记:一个 socket 的标准生命周期
construct → on(...) → bind → connect → send/recv → close → off(...)

写多了可以自己包一层管理类,统一处理重连、心跳这些。


三、TCP 服务器端:TCPSocketServer

客户端是"主动拨号"的那一头,服务器则是"监听 + 等待别人连上来"。

1. 创建 Server & listen

复制代码
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

let tcpServer: socket.TCPSocketServer = socket.constructTCPSocketServerInstance();

let ipAddress: socket.NetAddress = {} as socket.NetAddress;
ipAddress.address = '192.168.xxx.xxx';
ipAddress.port = 4651;

tcpServer.listen(ipAddress).then(() => {
  console.info('listen success');
}).catch((err: BusinessError) => {
  console.error('listen fail: ' + JSON.stringify(err));
});

2. on('connect') 拿到每个客户端连接

复制代码
class SocketInfo {
  message: ArrayBuffer = new ArrayBuffer(1);
  remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}

tcpServer.on('connect', (client: socket.TCPSocketConnection) => {
  // 监听与这个客户端的连接状态
  client.on('close', () => {
    console.info('client closed');
  });

  client.on('message', (value: SocketInfo) => {
    const buffer = value.message;
    const dataView = new DataView(buffer);
    let str = '';
    for (let i = 0; i < dataView.byteLength; ++i) {
      str += String.fromCharCode(dataView.getUint8(i));
    }
    console.info('received msg: ' + str);
    console.info('from: ' + value.remoteInfo.address + ':' + value.remoteInfo.port);
  });

  // 给客户端发一条"欢迎光临"
  const tcpSendOptions: socket.TCPSendOptions = {
    data: 'Hello, client!',
  };
  client.send(tcpSendOptions).then(() => {
    console.info('send success');
  }).catch((err) => {
    console.error('send fail: ' + JSON.stringify(err));
  });

  // 这里示例是直接关连接,真实业务别这么干 😂
  client.close().then(() => {
    console.info('close client success');
  }).catch((err: BusinessError) => {
    console.error('close client fail: ' + JSON.stringify(err));
  });

  setTimeout(() => {
    client.off('message');
    client.off('close');
  }, 10 * 1000);
});

3. 取消 Server 事件

复制代码
setTimeout(() => {
  tcpServer.off('connect');
}, 30 * 1000);

四、Multicast 多播:一发多收

场景:局域网内一堆设备,谁都不用知道谁在线,往多播地址扔数据,组里所有人能听到。

1. 创建多播 Socket & 加入多播组

复制代码
import { socket } from '@kit.NetworkKit';

let multicast: socket.MulticastSocket = socket.constructMulticastSocketInstance();

let addr: socket.NetAddress = {
  address: '239.255.0.1', // 典型多播地址段
  port: 32123,
  family: 1,
};

multicast.addMembership(addr).then(() => {
  console.info('addMembership success');
}).catch((err) => {
  console.error('addMembership fail: ' + JSON.stringify(err));
});

2. 监听 message

复制代码
class SocketInfo {
  message: ArrayBuffer = new ArrayBuffer(1);
  remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}

multicast.on('message', (data: SocketInfo) => {
  console.info('raw data: ' + JSON.stringify(data));
  const uintArray = new Uint8Array(data.message);
  let str = '';
  for (let i = 0; i < uintArray.length; ++i) {
    str += String.fromCharCode(uintArray[i]);
  }
  console.info('received: ' + str);
});

3. 发送广播数据

复制代码
multicast.send({ data: 'Hello12345', address: addr }).then(() => {
  console.info('send success');
}).catch((err) => {
  console.error('send fail: ' + JSON.stringify(err));
});

4. 停止监听 & 退组

复制代码
multicast.off('message');

multicast.dropMembership(addr).then(() => {
  console.info('drop membership success');
}).catch((err) => {
  console.error('drop membership fail: ' + JSON.stringify(err));
});

我的记忆点:多播 = UDP + 多播地址 + 加组 / 退组 API


五、LocalSocket / LocalSocketServer:本机进程间通信

场景:同一台设备里,自己的两个进程之间通信,不需要真的走网络端口。

1. LocalSocket 客户端

复制代码
import { socket } from '@kit.NetworkKit';
import { common } from '@kit.AbilityKit';

// 创建 LocalSocket
let client: socket.LocalSocket = socket.constructLocalSocketInstance();

// 订阅事件
client.on('message', (value: socket.LocalSocketMessageInfo) => {
  const uintArray = new Uint8Array(value.message);
  let msg = '';
  for (let i = 0; i < uintArray.length; i++) {
    msg += String.fromCharCode(uintArray[i]);
  }
  console.info('message: ' + msg);
});
client.on('connect', () => console.info('local connect'));
client.on('close', () => console.info('local close'));

// 连接本地 socket 文件路径
let context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
let sandboxPath: string = context.filesDir + '/testSocket';

let localAddress: socket.LocalAddress = { address: sandboxPath };
let connectOpt: socket.LocalConnectOptions = {
  address: localAddress,
  timeout: 6000,
};

let sendOpt: socket.LocalSendOptions = {
  data: 'Hello world!',
};

client.connect(connectOpt).then(() => {
  console.info('connect success');
  return client.send(sendOpt);
}).then(() => {
  console.info('send success');
}).catch((err) => {
  console.error('local socket error: ' + JSON.stringify(err));
});

// 用完关闭
client.off('message');
client.off('connect');
client.off('close');
client.close();

2. LocalSocketServer 服务端

复制代码
import { socket } from '@kit.NetworkKit';
import { common } from '@kit.AbilityKit';

let server: socket.LocalSocketServer = socket.constructLocalSocketServerInstance();

let context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
let sandboxPath: string = context.filesDir + '/testSocket';

let listenAddr: socket.LocalAddress = { address: sandboxPath };

server.listen(listenAddr).then(() => {
  console.info('listen success');
}).catch((err) => {
  console.error('listen fail: ' + JSON.stringify(err));
});

// 订阅 connect 事件
server.on('connect', (connection: socket.LocalSocketConnection) => {
  connection.on('message', (value: socket.LocalSocketMessageInfo) => {
    const uintArray = new Uint8Array(value.message);
    let msg = '';
    for (let i = 0; i < uintArray.length; i++) {
      msg += String.fromCharCode(uintArray[i]);
    }
    console.info('server recv: ' + msg);
  });

  let sendOpt: socket.LocalSendOptions = { data: 'Hello world!' };
  connection.send(sendOpt);

  connection.close();
  connection.off('message');
  connection.off('error');
});

// 关闭 server 时
server.off('connect');
server.off('error');

六、TLS Socket:加密版 Socket(单向 / 双向认证)

这一块就很贴近 HTTPS 了,只不过我们是手动玩。

1. 双向认证 TLSSocket 客户端流程

大致步骤:

  1. constructTLSSocketInstance()
  2. bind 本地地址
  3. 配置 TLSSecureOptions:key / cert / ca / cipher / protocol 等
  4. connect 建立 TLS 连接
  5. send / message
  6. 用完 close + off

代码简化版:

复制代码
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

let tlsTwoWay: socket.TLSSocket = socket.constructTLSSocketInstance();

class SocketInfo {
  message: ArrayBuffer = new ArrayBuffer(1);
  remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}

let ipAddress: socket.NetAddress = {} as socket.NetAddress;
ipAddress.address = '192.168.xxx.xxx';
ipAddress.port = 4512;

tlsTwoWay.bind(ipAddress, (err: BusinessError) => {
  if (err) {
    console.error('bind fail');
    return;
  }
  console.info('bind success');

  tlsTwoWay.on('message', (value: SocketInfo) => {
    const dataView = new DataView(value.message);
    let str = '';
    for (let i = 0; i < dataView.byteLength; ++i) {
      str += String.fromCharCode(dataView.getUint8(i));
    }
    console.info('tls recv: ' + str);
  });

  tlsTwoWay.on('connect', () => console.info('tls connect'));
  tlsTwoWay.on('close', () => console.info('tls close'));
});

// 配置证书
ipAddress.address = '192.168.xxx.xxx';
ipAddress.port = 1234;

let tlsSecureOption: socket.TLSSecureOptions = {
  key: 'xxxx',
  cert: 'xxxx',
  ca: ['xxxx'],
  password: 'xxxx',
  protocols: [socket.Protocol.TLSv12],
  useRemoteCipherPrefer: true,
  signatureAlgorithms: 'rsa_pss_rsae_sha256:ECDSA+SHA256',
  cipherSuite: 'AES256-SHA256',
};

let tlsTwoWayConnectOption: socket.TLSConnectOptions = {
  address: ipAddress,
  secureOptions: tlsSecureOption,
  ALPNProtocols: ['spdy/1', 'http/1.1'],
};

tlsTwoWay.connect(tlsTwoWayConnectOption).then(() => {
  console.info('tls connect success');
  return tlsTwoWay.send('xxxx');
}).then(() => {
  console.info('send success');
}).catch((err: BusinessError) => {
  console.error('tls error: ' + JSON.stringify(err));
});

// 用完关闭
tlsTwoWay.close((err: BusinessError) => {
  if (err) console.error('close error: ' + JSON.stringify(err));
  tlsTwoWay.off('message');
  tlsTwoWay.off('connect');
  tlsTwoWay.off('close');
});

2. 单向认证 TLSSocket 客户端

区别是:只配 ca,不上传客户端证书。


七、把 TCP 升级成 TLS:平滑改造老逻辑

这个设计挺友好:以前你写的是纯 TCP,现在不想全改,可以在 connect 成功后把这个 TCP 套接字"升级"为 TLS。

简化流程:

  1. 普通 TCP 流程 constructTCPSocketInstance → bind → connect
  2. connect 成功后:
    • let tls = socket.constructTLSSocketInstance(tcp);
    • tls 做事件订阅、证书配置、connect()(TLS 层)
  3. 之后的数据就走 TLS 了

优点就是:旧的 TCP 流程不用推倒重来,只是在上面加了一层。


八、TLS Socket Server:服务端加密通信

TLSSocketServer 的套路和 TCP Server 很像,只是 listen 的时候直接传入 TLS 配置。

复制代码
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

let tlsServer: socket.TLSSocketServer = socket.constructTLSSocketServerInstance();

let netAddress: socket.NetAddress = {
  address: '192.168.xx.xxx',
  port: 8080,
};

let tlsSecureOptions: socket.TLSSecureOptions = {
  key: 'xxxx',
  cert: 'xxxx',
  ca: ['xxxx'],
  password: 'xxxx',
  protocols: socket.Protocol.TLSv12,
  useRemoteCipherPrefer: true,
  signatureAlgorithms: 'rsa_pss_rsae_sha256:ECDSA+SHA256',
  cipherSuite: 'AES256-SHA256',
};

let tlsConnectOptions: socket.TLSConnectOptions = {
  address: netAddress,
  secureOptions: tlsSecureOptions,
  ALPNProtocols: ['spdy/1', 'http/1.1'],
};

tlsServer.listen(tlsConnectOptions).then(() => {
  console.info('tls server listen success');
}).catch((err: BusinessError) => {
  console.error('listen failed: ' + JSON.stringify(err));
});

// 订阅 connect
class SocketInfo {
  message: ArrayBuffer = new ArrayBuffer(1);
  remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}

let callback = (value: SocketInfo) => {
  const arr = new Uint8Array(value.message);
  let msg = '';
  for (let i = 0; i < arr.byteLength; i++) {
    msg += String.fromCharCode(arr[i]);
  }
  console.info('server recv: ' + msg);
};

tlsServer.on('connect', (client: socket.TLSSocketConnection) => {
  client.on('message', callback);

  client.send('Hello, client!').then(() => {
    console.info('send success');
  });

  client.close().then(() => {
    console.info('close success');
  });

  client.off('message', callback);
  client.off('message');
});

tlsServer.off('connect');

九、最后给自己留几个"防踩坑提示"

  1. 前后台切换 = 高危操作
    App 退后台后,Socket 很可能断,回来再发发现各种超时、错误码。要做:
    • 检测错误码(官方会给一套 error code);
    • 发现失败就:重新 construct → bind → connect,不要死撑老对象。
  2. ArrayBuffer 只是一块内存,不是"字符串"
    • 文本数据务必和对端约定编码(UTF-8 最常见);
    • 二进制数据(图片、文件)就当 byte 流处理,别乱用 String.fromCharCode
  3. 事件订阅记得清理
    • 不管是 TCP / Multicast / LocalSocket / TLS,用完要 off + close
    • 不然长期跑着的服务,很容易一堆残留回调堆在那。
  4. TLS 配置很细,改之前先画图
    • 单向认证:只配 CA;
    • 双向认证:还要配客户端证书/私钥;
    • cipher / protocol / ALPN 都要和服务器那边对得上。
  5. 调试阶段可以简单,线上要收紧
    • 开发的时候可以先不强制 TLS、也可以不做复杂校验;
    • 真上生产时,把 TLS、证书锁定、明文 HTTP 限制配好。
相关推荐
原野-1 小时前
PHP女程序猿学习Java的Day-5
java·开发语言·学习
hmbbcsm1 小时前
nginx学习笔记
笔记·学习·nginx
im_AMBER1 小时前
Leetcode 64 大小为 K 且平均值大于等于阈值的子数组数目
笔记·学习·算法·leetcode
知识分享小能手1 小时前
CentOS Stream 9入门学习教程,从入门到精通,Linux操作系统概述 —全面知识点详解(1)
linux·学习·centos
重生之我是Java开发战士1 小时前
【Java SE】TCP/IP协议栈:从分层架构到核心机制
java·tcp/ip·架构
遇到困难睡大觉哈哈1 小时前
Harmony os HTTP 网络访问(Network Kit 版)
网络·http·iphone·harmonyos·鸿蒙
ccnnlxc1 小时前
go语言学习
学习
勇气要爆发1 小时前
问:TCP/UDP的区别及应用场景
网络协议·tcp/ip·udp
Freshman小白1 小时前
《项目管理》学堂在线2025网课答案
学习·答案