鸿蒙与Java跨平台Socket通信实战
跨语言通信关键:统一 UTF-8 编码 + 换行符做消息边界,保证 ArkTS 与 Java 的字节流解析一致。TCP 服务端架构监听 + 线程池管理 +WorkThread处理单客户端。资源管理:Java 用自动释放 IO 流,鸿蒙用异步 API 避免阻塞主线程。UI 状态联动:鸿蒙客户端用@State变量控制按钮可用状态,实现【绑定→连接→发送】的流程化交互。
目录
本篇博客将从零实现一个鸿蒙 ArkTS TCP 客户端与Java 多线程 TCP 服务器的双向聊天功能,涵盖【绑定端口→建立连接→持续收发→资源释放】全流程,代码可直接运行,适配鸿蒙 5.0 + 与 Java 8 + 环境。
1.整体通信架构
鸿蒙客户端(ArkTS)
绑定本地端口 → 连接Java服务端 → 发送消息(带换行符做边界)
Java服务端(Java)
监听端口(ServerSocket)→ 线程池分配工作线程 → 读取客户端消息(按行解析) → 控制台输入回发消息 → 客户端接收并展示
2.鸿蒙 ArkTS TCP 客户端实现
2.1 完整代码
import { socket } from "@kit.NetworkKit"
import { BusinessError } from "@kit.BasicServicesKit"
// 导入鸿蒙工具库,这里主要用其编解码能力(TextDecoder)
import util from "@ohos.util"
// 构建TCP套接字实例,这是整个TCP通信的核心对象,所有TCP操作都基于该实例
let tcpSocket: socket.TCPSocket = socket.constructTCPSocketInstance()
@Entry
@Component
struct Index {
// 本地绑定的端口号,默认9990
@State localPort: number = 9990
// 消息历史记录,拼接所有收发消息
@State msgHistory: string = ""
scroller: Scroller = new Scroller()
// 远程TCP服务器的IP地址,默认局域网IP
@State serverIP: string = "192.168.247.1"
// 远程TCP服务器的端口号
@State serverPort: number = 9980
// 控制“连接服务器”按钮的可用状态:绑定本地端口成功后才启用
@State visibleFlag: boolean = false
// 控制“发送消息”按钮的可用状态:连接服务器成功后才启用
@State sendFlag: boolean = false
// 输入框中待发送的消息内容
@State sendMsg: string = ""
// 绑定本地IP和端口
async bindPort() {
// 定义本地地址对象:address为0.0.0.0表示绑定本机“所有网卡”的该端口
let localAddress: socket.NetAddress = { address: "0.0.0.0", port: this.localPort }
await tcpSocket.bind(localAddress).then(() => {
this.msgHistory = '绑定服务成功' + "\r\n"
this.visibleFlag = true
}).catch((e: BusinessError) => {
this.msgHistory = "绑定服务失败" + "\r\n"
})
// 注册TCP的message事件监听:服务器发送消息时,触发该回调
tcpSocket.on("message", async (value) => {
console.log("鸿蒙接受到服务器传递的消息")
// value.message是服务器发送的“二进制缓冲区”,鸿蒙的TCPSocket接收的消息是ArrayBuffer类型,必须通过util.TextDecoder解码为字符串才能展示
let buffer = value.message
// 创建UTF-8解码器(鸿蒙标准编解码API)
let textDecoder = util.TextDecoder.create("UTF-8")
// 将二进制缓冲区转为Uint8Array,再解码为UTF-8字符串
let str = textDecoder.decodeToString(new Uint8Array(buffer))
// 拼接消息历史:服务器消息+时间戳+换行,实现日志式展示
this.msgHistory +="服务器发送的消息为:["+this.getCurrentTimeString()+"]:"+str+"\r\n"
// 滚动器自动滚到底部,显示最新的服务器消息
this.scroller.scrollEdge(Edge.Bottom)
})
}
// 连接远程 TCP 服务器
async connServer() {
// 封装服务器地址对象:由UI输入框的serverIP/serverPort赋值
let serverAddress: socket.NetAddress = { address: this.serverIP, port: this.serverPort }
// 异步连接服务器:TCP的三次握手过程,IO操作需异步处理
await tcpSocket.connect({ address: serverAddress }).then(() => {
this.msgHistory = "连接服务器成功" + "\r\n"
this.sendFlag = true
}).catch(() => {
this.msgHistory = "连接服务器失败" + "\r\n"
})
}
// 补零工具函数
padZero = (n: number) => n < 10 ? "0" + n : n
// 获取当前时间戳
getCurrentTimeString(): string {
let time = ""
let date = new Date()
// time = date.getHours().toString() + ":" + date.getMinutes().toString() + ":" + date.getSeconds().toString()
// 加上补零处理后的时间戳
time = this.padZero(date.getHours()) + ":" + this.padZero(date.getMinutes()) + ":" + this.padZero(date.getSeconds())
return time
}
// 向服务器发送消息
sendMessageServer() {
// TCP是面向字节流的协议,没有“消息边界”,服务器无法区分连续发送的多条消息,因此添加换行符作为消息分隔符,是 TCP 字节流通信的通用解决方案。
tcpSocket.send({ data: this.sendMsg + "\r\n" })
.then(() => {
this.msgHistory += "我:[" + this.getCurrentTimeString() + "]:" + this.sendMsg + "\r\n"
}).catch(() => {
this.msgHistory += "我:发送失败" + "\r\n"
})
}
build() {
Column({ space: 20 }) {
Text("鸿蒙套接字通信示例").width("100%").textAlign(TextAlign.Center).fontWeight(FontWeight.Bold)
// 本地端口绑定区
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("本地IP和端口:").width("30%").fontSize(12)
// 数字类型输入框,绑定localPort,输入变化时更新变量
TextInput({ text: this.localPort.toString() }).type(InputType.Number).width("40%").onChange((value) => {
this.localPort = Number(value)
console.log("输入本地的端口为:" + this.localPort)
})
Button("绑定IP和端口").onClick(() => {
this.bindPort()
}).width("30%").fontSize(12)
}
// 服务器连接区
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("服务器地址:").width("20%").fontSize(12)
TextInput({ text: this.serverIP }).width("25%").onChange((value) => {
this.serverIP = value
console.log("输入服务器IP地址为:" + this.serverIP)
})
TextInput({ text: this.serverPort.toString() }).width("25%").onChange((value) => {
this.serverPort = Number(value)
console.log("输入服务器PORT为:" + this.serverPort)
})
// 连接按钮:仅visibleFlag为true时可用,点击触发connServer()
Button("连接服务器").enabled(this.visibleFlag).width("30%").onClick(() => {
this.connServer()
})
}
// 消息发送区
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
TextInput({ placeholder: "请输入要发送的消息:" }).onChange((value) => {
this.sendMsg = value
})
// 发送按钮:仅sendFlag为true时可用,点击触发sendMessageServer()
Button("发送消息").enabled(this.sendFlag).width("30%").onClick(() => {
this.sendMessageServer()
})
}
// 消息历史展示区
Scroll(this.scroller) {
Text(this.msgHistory)
.textAlign(TextAlign.Start)
.padding(10).width("100%")
}
.align(Alignment.Top)
.height(300)
.backgroundColor(0xeeeeee)
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.On)
.scrollBarWidth(20)
}.width("100%").height("100%")
}
}
2.2 核心代码解析
(1)TCP 套接字初始化
鸿蒙提供的TCPSocket是核心通信对象,所有 TCP 操作(绑定、连接、收发)都基于该实例。
let tcpSocket: socket.TCPSocket = socket.constructTCPSocketInstance()
(2)绑定本地端口
0.0.0.0表示绑定本机所有网卡,确保局域网内其他设备可连接。绑定成功后启用【连接服务器】按钮。
let localAddress: socket.NetAddress = { address: "0.0.0.0", port: this.localPort }
await tcpSocket.bind(localAddress)
(3)消息监听与解码
服务器消息是二进制ArrayBuffer,需用TextDecoder解码为 UTF-8 字符串,自动滚动到底部保证最新消息可见。
tcpSocket.on("message", async (value) => {
let buffer = value.message
let textDecoder = util.TextDecoder.create("UTF-8")
let str = textDecoder.decodeToString(new Uint8Array(buffer))
this.msgHistory += "服务器发送的消息为:[" + this.getCurrentTimeString() + "]:" + str + "\r\n"
this.scroller.scrollEdge(Edge.Bottom)
})
(4)发送消息(带边界)
TCP 是字节流协议,无天然消息边界,添加\r\n作为分隔符,与 Java 服务端的readLine()完美匹配。
tcpSocket.send({ data: this.sendMsg + "\r\n" })
3.Java 多线程 TCP 服务器实现
3.1 主服务类 Server.java
package com.pp.chapter1;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
// 服务器端的核心套接字对象,用于监听客户端的TCP连接请求
private ServerSocket serverSocket;
// 固定线程池:管理工作线程,避免线程泛滥
private ExecutorService threadPool;
// 服务器绑定端口(与鸿蒙客户端默认端口一致)
private static final int SERVER_PORT = 9980;
// 线程池核心线程数
private static final int THREAD_POOL_SIZE = 10;
// 构造方法:初始化服务、启动监听、线程池
public Server()
{
try {
// 初始化固定线程池
threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
// 创建ServerSocket并绑定端口
serverSocket = new ServerSocket(SERVER_PORT);
// 避免端口被占用
serverSocket.setReuseAddress(true);
System.out.println("=== TCP服务器启动成功 ===");
System.out.println("监听端口:" + SERVER_PORT);
System.out.println("线程池初始化完成,核心线程数:" + THREAD_POOL_SIZE);
// 死循环:持续监听客户端连接
while(true)
{
// 阻塞方法:调用后程序会暂停执行,直到有客户端发起 TCP连接请求并完成三次握手,才会返回Socket对象并继续执行
Socket socket = serverSocket.accept();
// 获取客户端IP+端口并打印
String clientInfo = socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
System.out.println("【新客户端连接】" + clientInfo);
// 将通信任务提交到线程池
threadPool.execute(new WorkThread(socket));
}
} catch (IOException e) {
System.err.println("=== TCP服务器启动失败 ===");
System.err.println("失败原因:端口" + SERVER_PORT + "被占用/权限不足," + e.getMessage());
System.exit(1); // 启动失败直接退出程序
}
}
public static void main(String[] args) {
new Server();
}
}
3.2 工作线程类 WorkThread.java
package com.pp.chapter1;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class WorkThread implements Runnable {
// 与单个客户端通信的Socket对象(由Server的accept()返回)
private final Socket socket;
// 客户端IP+端口(用于日志打印)
private String clientInfo;
// 构造方法:接收客户端Socket
public WorkThread(Socket socket) {
this.socket = socket;
this.clientInfo = socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
}
@Override
public void run() {
// try-with-resources语法:自动关闭所有实现AutoCloseable的资源
// 一次性创建流,循环复用,避免重复创建;统一指定UTF-8编码,解决跨语言乱码
try (
// 客户端消息输入流:字节流→字符流(UTF-8)→缓冲流,一次创建持续使用
BufferedReader clientReader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8")
);
// 服务器向客户端输出流:字节流→打印流(UTF-8+自动刷新),无需手动flush
PrintWriter serverWriter = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),
true // 自动刷新
);
// 服务器控制台输入流:读取控制台回发消息,UTF-8编码
BufferedReader consoleReader = new BufferedReader(
new InputStreamReader(System.in, "UTF-8")
)
) {
System.out.println("【通信线程启动】" + clientInfo + ",开始监听客户端消息...");
String clientMsg;
// 循环读取客户端消息
// readLine()返回null → 客户端主动断开连接(鸿蒙应用关闭/网络断开)
while ((clientMsg = clientReader.readLine()) != null) {
// 过滤客户端空消息(避免无效处理)
if (clientMsg.trim().isEmpty()) {
System.out.println("【空消息忽略】" + clientInfo + "发送了空消息");
continue;
}
// 打印客户端消息:线程名+客户端信息+消息
System.out.printf("[%s] 【客户端消息】%s:%s%n",
Thread.currentThread().getName(), clientInfo, clientMsg);
// 服务器控制台输入回发消息
System.out.print("请输入要回发给" + clientInfo + "的消息:");
String serverMsg = consoleReader.readLine();
// 过滤服务器空消息,避免发送空内容给客户端
if (serverMsg == null || serverMsg.trim().isEmpty()) {
serverMsg = "【服务器】消息不能为空,已忽略";
}
// 发送消息给客户端:PrintWriter开启自动刷新,直接println即可
serverWriter.println(serverMsg);
System.out.printf("[%s] 【服务器回发】%s:%s%n",
Thread.currentThread().getName(), clientInfo, serverMsg);
}
} catch (IOException e) {
// 精细化异常日志:区分客户端正常断开/异常断开
if (socket.isClosed() || e.getMessage().contains("Connection reset")) {
System.out.println("【客户端断开】" + clientInfo + "(正常/异常断开)");
} else {
System.err.println("【通信异常】" + clientInfo + ",原因:" + e.getMessage());
}
} finally {
// 确保Socket关闭(即使try-with-resources出问题)
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
System.err.println("【Socket关闭失败】" + clientInfo + ",原因:" + e.getMessage());
}
System.out.println("【通信线程销毁】" + clientInfo + ",释放所有通信资源\n");
}
}
}
3.3 核心代码解析
(1)线程池管理多客户端
固定线程池避免大量客户端连接导致的线程泛滥,保证服务端稳定性,核心线程数可根据业务调整。
threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
(2)try-with-resources自动释放资源
JVM 自动关闭所有实现AutoCloseable的资源,彻底解决 IO 流泄漏问题,关闭顺序与声明顺序逆序(先关输出流,再关输入流)。
try (
BufferedReader clientReader = new BufferedReader(...);
PrintWriter serverWriter = new PrintWriter(...);
BufferedReader consoleReader = new BufferedReader(...)
) {
// 业务逻辑
}
(3)UTF-8 编码统一
所有流转换显式指定 UTF-8,与鸿蒙客户端编码一致,避免跨语言乱码。
new InputStreamReader(socket.getInputStream(), "UTF-8")
new OutputStreamWriter(socket.getOutputStream(), "UTF-8")
(4)循环监听客户端消息
readLine()按\r\n分割消息,返回null表示客户端断开连接,自动退出循环并释放资源。
while ((clientMsg = clientReader.readLine()) != null) {
// 处理消息
}
4.运行效果展示
4.1 鸿蒙客户端界面

- 绑定本地端口(9990)→ 连接服务器(192.168.247.1:9980)
- 输入消息发送,服务端回发
- 消息面板自动滚动,展示带时间戳的收发记录
4.2 Java 服务端控制台

- 服务端启动后监听 9980 端口,线程池初始化完成
- 客户端连接后打印 IP + 端口,分配工作线程处理通信
- 接收客户端消息后,控制台输入回发内容,自动发送给客户端
5.核心知识点总结
跨语言通信关键:统一 UTF-8 编码 + 换行符做消息边界,保证 ArkTS 与 Java 的字节流解析一致。
TCP 服务端架构:ServerSocket监听 + 线程池管理 +WorkThread处理单客户端。
资源管理:Java 用try-with-resources自动释放 IO 流,鸿蒙用异步 API 避免阻塞主线程。
UI 状态联动:鸿蒙客户端用@State变量控制按钮可用状态,实现【绑定→连接→发送】的流程化交互。
更多推荐



所有评论(0)