鴻蒙與Java跨平臺(tái)Socket通信實(shí)戰(zhàn)代碼實(shí)例
前言
本篇博客將從零實(shí)現(xiàn)一個(gè)鴻蒙 ArkTS TCP 客戶端與Java 多線程 TCP 服務(wù)器的雙向聊天功能,涵蓋【綁定端口→建立連接→持續(xù)收發(fā)→資源釋放】全流程,代碼可直接運(yùn)行,適配鴻蒙 5.0 + 與 Java 8 + 環(huán)境。
1.整體通信架構(gòu)
鴻蒙客戶端(ArkTS)
綁定本地端口 → 連接Java服務(wù)端 → 發(fā)送消息(帶換行符做邊界)
Java服務(wù)端(Java)
監(jiān)聽端口(ServerSocket)→ 線程池分配工作線程 → 讀取客戶端消息(按行解析) → 控制臺(tái)輸入回發(fā)消息 → 客戶端接收并展示
2.鴻蒙 ArkTS TCP 客戶端實(shí)現(xiàn)
2.1 完整代碼
import { socket } from "@kit.NetworkKit"
import { BusinessError } from "@kit.BasicServicesKit"
// 導(dǎo)入鴻蒙工具庫(kù),這里主要用其編解碼能力(TextDecoder)
import util from "@ohos.util"
// 構(gòu)建TCP套接字實(shí)例,這是整個(gè)TCP通信的核心對(duì)象,所有TCP操作都基于該實(shí)例
let tcpSocket: socket.TCPSocket = socket.constructTCPSocketInstance()
@Entry
@Component
struct Index {
// 本地綁定的端口號(hào),默認(rèn)9990
@State localPort: number = 9990
// 消息歷史記錄,拼接所有收發(fā)消息
@State msgHistory: string = ""
scroller: Scroller = new Scroller()
// 遠(yuǎn)程TCP服務(wù)器的IP地址,默認(rèn)局域網(wǎng)IP
@State serverIP: string = "192.168.247.1"
// 遠(yuǎn)程TCP服務(wù)器的端口號(hào)
@State serverPort: number = 9980
// 控制“連接服務(wù)器”按鈕的可用狀態(tài):綁定本地端口成功后才啟用
@State visibleFlag: boolean = false
// 控制“發(fā)送消息”按鈕的可用狀態(tài):連接服務(wù)器成功后才啟用
@State sendFlag: boolean = false
// 輸入框中待發(fā)送的消息內(nèi)容
@State sendMsg: string = ""
// 綁定本地IP和端口
async bindPort() {
// 定義本地地址對(duì)象:address為0.0.0.0表示綁定本機(jī)“所有網(wǎng)卡”的該端口
let localAddress: socket.NetAddress = { address: "0.0.0.0", port: this.localPort }
await tcpSocket.bind(localAddress).then(() => {
this.msgHistory = '綁定服務(wù)成功' + "\r\n"
this.visibleFlag = true
}).catch((e: BusinessError) => {
this.msgHistory = "綁定服務(wù)失敗" + "\r\n"
})
// 注冊(cè)TCP的message事件監(jiān)聽:服務(wù)器發(fā)送消息時(shí),觸發(fā)該回調(diào)
tcpSocket.on("message", async (value) => {
console.log("鴻蒙接受到服務(wù)器傳遞的消息")
// value.message是服務(wù)器發(fā)送的“二進(jìn)制緩沖區(qū)”,鴻蒙的TCPSocket接收的消息是ArrayBuffer類型,必須通過util.TextDecoder解碼為字符串才能展示
let buffer = value.message
// 創(chuàng)建UTF-8解碼器(鴻蒙標(biāo)準(zhǔn)編解碼API)
let textDecoder = util.TextDecoder.create("UTF-8")
// 將二進(jìn)制緩沖區(qū)轉(zhuǎn)為Uint8Array,再解碼為UTF-8字符串
let str = textDecoder.decodeToString(new Uint8Array(buffer))
// 拼接消息歷史:服務(wù)器消息+時(shí)間戳+換行,實(shí)現(xiàn)日志式展示
this.msgHistory +="服務(wù)器發(fā)送的消息為:["+this.getCurrentTimeString()+"]:"+str+"\r\n"
// 滾動(dòng)器自動(dòng)滾到底部,顯示最新的服務(wù)器消息
this.scroller.scrollEdge(Edge.Bottom)
})
}
// 連接遠(yuǎn)程 TCP 服務(wù)器
async connServer() {
// 封裝服務(wù)器地址對(duì)象:由UI輸入框的serverIP/serverPort賦值
let serverAddress: socket.NetAddress = { address: this.serverIP, port: this.serverPort }
// 異步連接服務(wù)器:TCP的三次握手過程,IO操作需異步處理
await tcpSocket.connect({ address: serverAddress }).then(() => {
this.msgHistory = "連接服務(wù)器成功" + "\r\n"
this.sendFlag = true
}).catch(() => {
this.msgHistory = "連接服務(wù)器失敗" + "\r\n"
})
}
// 補(bǔ)零工具函數(shù)
padZero = (n: number) => n < 10 ? "0" + n : n
// 獲取當(dāng)前時(shí)間戳
getCurrentTimeString(): string {
let time = ""
let date = new Date()
// time = date.getHours().toString() + ":" + date.getMinutes().toString() + ":" + date.getSeconds().toString()
// 加上補(bǔ)零處理后的時(shí)間戳
time = this.padZero(date.getHours()) + ":" + this.padZero(date.getMinutes()) + ":" + this.padZero(date.getSeconds())
return time
}
// 向服務(wù)器發(fā)送消息
sendMessageServer() {
// TCP是面向字節(jié)流的協(xié)議,沒有“消息邊界”,服務(wù)器無(wú)法區(qū)分連續(xù)發(fā)送的多條消息,因此添加換行符作為消息分隔符,是 TCP 字節(jié)流通信的通用解決方案。
tcpSocket.send({ data: this.sendMsg + "\r\n" })
.then(() => {
this.msgHistory += "我:[" + this.getCurrentTimeString() + "]:" + this.sendMsg + "\r\n"
}).catch(() => {
this.msgHistory += "我:發(fā)送失敗" + "\r\n"
})
}
build() {
Column({ space: 20 }) {
Text("鴻蒙套接字通信示例").width("100%").textAlign(TextAlign.Center).fontWeight(FontWeight.Bold)
// 本地端口綁定區(qū)
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("本地IP和端口:").width("30%").fontSize(12)
// 數(shù)字類型輸入框,綁定localPort,輸入變化時(shí)更新變量
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)
}
// 服務(wù)器連接區(qū)
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
Text("服務(wù)器地址:").width("20%").fontSize(12)
TextInput({ text: this.serverIP }).width("25%").onChange((value) => {
this.serverIP = value
console.log("輸入服務(wù)器IP地址為:" + this.serverIP)
})
TextInput({ text: this.serverPort.toString() }).width("25%").onChange((value) => {
this.serverPort = Number(value)
console.log("輸入服務(wù)器PORT為:" + this.serverPort)
})
// 連接按鈕:僅visibleFlag為true時(shí)可用,點(diǎn)擊觸發(fā)connServer()
Button("連接服務(wù)器").enabled(this.visibleFlag).width("30%").onClick(() => {
this.connServer()
})
}
// 消息發(fā)送區(qū)
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
TextInput({ placeholder: "請(qǐng)輸入要發(fā)送的消息:" }).onChange((value) => {
this.sendMsg = value
})
// 發(fā)送按鈕:僅sendFlag為true時(shí)可用,點(diǎn)擊觸發(fā)sendMessageServer()
Button("發(fā)送消息").enabled(this.sendFlag).width("30%").onClick(() => {
this.sendMessageServer()
})
}
// 消息歷史展示區(qū)
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是核心通信對(duì)象,所有 TCP 操作(綁定、連接、收發(fā))都基于該實(shí)例。
let tcpSocket: socket.TCPSocket = socket.constructTCPSocketInstance()
(2)綁定本地端口
0.0.0.0表示綁定本機(jī)所有網(wǎng)卡,確保局域網(wǎng)內(nèi)其他設(shè)備可連接。綁定成功后啟用【連接服務(wù)器】按鈕。
let localAddress: socket.NetAddress = { address: "0.0.0.0", port: this.localPort }
await tcpSocket.bind(localAddress)(3)消息監(jiān)聽與解碼
服務(wù)器消息是二進(jìn)制ArrayBuffer,需用TextDecoder解碼為 UTF-8 字符串,自動(dòng)滾動(dòng)到底部保證最新消息可見。
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 += "服務(wù)器發(fā)送的消息為:[" + this.getCurrentTimeString() + "]:" + str + "\r\n"
this.scroller.scrollEdge(Edge.Bottom)
})(4)發(fā)送消息(帶邊界)
TCP 是字節(jié)流協(xié)議,無(wú)天然消息邊界,添加\r\n作為分隔符,與 Java 服務(wù)端的readLine()完美匹配。
tcpSocket.send({ data: this.sendMsg + "\r\n" })3.Java 多線程 TCP 服務(wù)器實(shí)現(xiàn)
3.1 主服務(wù)類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 {
// 服務(wù)器端的核心套接字對(duì)象,用于監(jiān)聽客戶端的TCP連接請(qǐng)求
private ServerSocket serverSocket;
// 固定線程池:管理工作線程,避免線程泛濫
private ExecutorService threadPool;
// 服務(wù)器綁定端口(與鴻蒙客戶端默認(rèn)端口一致)
private static final int SERVER_PORT = 9980;
// 線程池核心線程數(shù)
private static final int THREAD_POOL_SIZE = 10;
// 構(gòu)造方法:初始化服務(wù)、啟動(dòng)監(jiān)聽、線程池
public Server()
{
try {
// 初始化固定線程池
threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
// 創(chuàng)建ServerSocket并綁定端口
serverSocket = new ServerSocket(SERVER_PORT);
// 避免端口被占用
serverSocket.setReuseAddress(true);
System.out.println("=== TCP服務(wù)器啟動(dòng)成功 ===");
System.out.println("監(jiān)聽端口:" + SERVER_PORT);
System.out.println("線程池初始化完成,核心線程數(shù):" + THREAD_POOL_SIZE);
// 死循環(huán):持續(xù)監(jiān)聽客戶端連接
while(true)
{
// 阻塞方法:調(diào)用后程序會(huì)暫停執(zhí)行,直到有客戶端發(fā)起 TCP連接請(qǐng)求并完成三次握手,才會(huì)返回Socket對(duì)象并繼續(xù)執(zhí)行
Socket socket = serverSocket.accept();
// 獲取客戶端IP+端口并打印
String clientInfo = socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
System.out.println("【新客戶端連接】" + clientInfo);
// 將通信任務(wù)提交到線程池
threadPool.execute(new WorkThread(socket));
}
} catch (IOException e) {
System.err.println("=== TCP服務(wù)器啟動(dòng)失敗 ===");
System.err.println("失敗原因:端口" + SERVER_PORT + "被占用/權(quán)限不足," + e.getMessage());
System.exit(1); // 啟動(dòng)失敗直接退出程序
}
}
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 {
// 與單個(gè)客戶端通信的Socket對(duì)象(由Server的accept()返回)
private final Socket socket;
// 客戶端IP+端口(用于日志打?。?
private String clientInfo;
// 構(gòu)造方法:接收客戶端Socket
public WorkThread(Socket socket) {
this.socket = socket;
this.clientInfo = socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
}
@Override
public void run() {
// try-with-resources語(yǔ)法:自動(dòng)關(guān)閉所有實(shí)現(xiàn)AutoCloseable的資源
// 一次性創(chuàng)建流,循環(huán)復(fù)用,避免重復(fù)創(chuàng)建;統(tǒng)一指定UTF-8編碼,解決跨語(yǔ)言亂碼
try (
// 客戶端消息輸入流:字節(jié)流→字符流(UTF-8)→緩沖流,一次創(chuàng)建持續(xù)使用
BufferedReader clientReader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8")
);
// 服務(wù)器向客戶端輸出流:字節(jié)流→打印流(UTF-8+自動(dòng)刷新),無(wú)需手動(dòng)flush
PrintWriter serverWriter = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF-8"),
true // 自動(dòng)刷新
);
// 服務(wù)器控制臺(tái)輸入流:讀取控制臺(tái)回發(fā)消息,UTF-8編碼
BufferedReader consoleReader = new BufferedReader(
new InputStreamReader(System.in, "UTF-8")
)
) {
System.out.println("【通信線程啟動(dòng)】" + clientInfo + ",開始監(jiān)聽客戶端消息...");
String clientMsg;
// 循環(huán)讀取客戶端消息
// readLine()返回null → 客戶端主動(dòng)斷開連接(鴻蒙應(yīng)用關(guān)閉/網(wǎng)絡(luò)斷開)
while ((clientMsg = clientReader.readLine()) != null) {
// 過濾客戶端空消息(避免無(wú)效處理)
if (clientMsg.trim().isEmpty()) {
System.out.println("【空消息忽略】" + clientInfo + "發(fā)送了空消息");
continue;
}
// 打印客戶端消息:線程名+客戶端信息+消息
System.out.printf("[%s] 【客戶端消息】%s:%s%n",
Thread.currentThread().getName(), clientInfo, clientMsg);
// 服務(wù)器控制臺(tái)輸入回發(fā)消息
System.out.print("請(qǐng)輸入要回發(fā)給" + clientInfo + "的消息:");
String serverMsg = consoleReader.readLine();
// 過濾服務(wù)器空消息,避免發(fā)送空內(nèi)容給客戶端
if (serverMsg == null || serverMsg.trim().isEmpty()) {
serverMsg = "【服務(wù)器】消息不能為空,已忽略";
}
// 發(fā)送消息給客戶端:PrintWriter開啟自動(dòng)刷新,直接println即可
serverWriter.println(serverMsg);
System.out.printf("[%s] 【服務(wù)器回發(fā)】%s:%s%n",
Thread.currentThread().getName(), clientInfo, serverMsg);
}
} catch (IOException e) {
// 精細(xì)化異常日志:區(qū)分客戶端正常斷開/異常斷開
if (socket.isClosed() || e.getMessage().contains("Connection reset")) {
System.out.println("【客戶端斷開】" + clientInfo + "(正常/異常斷開)");
} else {
System.err.println("【通信異?!? + clientInfo + ",原因:" + e.getMessage());
}
} finally {
// 確保Socket關(guān)閉(即使try-with-resources出問題)
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
System.err.println("【Socket關(guān)閉失敗】" + clientInfo + ",原因:" + e.getMessage());
}
System.out.println("【通信線程銷毀】" + clientInfo + ",釋放所有通信資源\n");
}
}
}
3.3 核心代碼解析
(1)線程池管理多客戶端
固定線程池避免大量客戶端連接導(dǎo)致的線程泛濫,保證服務(wù)端穩(wěn)定性,核心線程數(shù)可根據(jù)業(yè)務(wù)調(diào)整。
threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
(2)try-with-resources自動(dòng)釋放資源
JVM 自動(dòng)關(guān)閉所有實(shí)現(xiàn)AutoCloseable的資源,徹底解決 IO 流泄漏問題,關(guān)閉順序與聲明順序逆序(先關(guān)輸出流,再關(guān)輸入流)。
try (
BufferedReader clientReader = new BufferedReader(...);
PrintWriter serverWriter = new PrintWriter(...);
BufferedReader consoleReader = new BufferedReader(...)
) {
// 業(yè)務(wù)邏輯
}(3)UTF-8 編碼統(tǒng)一
所有流轉(zhuǎn)換顯式指定 UTF-8,與鴻蒙客戶端編碼一致,避免跨語(yǔ)言亂碼。
new InputStreamReader(socket.getInputStream(), "UTF-8") new OutputStreamWriter(socket.getOutputStream(), "UTF-8")
(4)循環(huán)監(jiān)聽客戶端消息
readLine()按\r\n分割消息,返回null表示客戶端斷開連接,自動(dòng)退出循環(huán)并釋放資源。
while ((clientMsg = clientReader.readLine()) != null) {
// 處理消息
}4.運(yùn)行效果展示
4.1 鴻蒙客戶端界面

- 綁定本地端口(9990)→ 連接服務(wù)器(192.168.247.1:9980)
- 輸入消息發(fā)送,服務(wù)端回發(fā)
- 消息面板自動(dòng)滾動(dòng),展示帶時(shí)間戳的收發(fā)記錄
4.2 Java 服務(wù)端控制臺(tái)

- 服務(wù)端啟動(dòng)后監(jiān)聽 9980 端口,線程池初始化完成
- 客戶端連接后打印 IP + 端口,分配工作線程處理通信
- 接收客戶端消息后,控制臺(tái)輸入回發(fā)內(nèi)容,自動(dòng)發(fā)送給客戶端
5.核心知識(shí)點(diǎn)總結(jié)
跨語(yǔ)言通信關(guān)鍵:統(tǒng)一 UTF-8 編碼 + 換行符做消息邊界,保證 ArkTS 與 Java 的字節(jié)流解析一致。
TCP 服務(wù)端架構(gòu):ServerSocket監(jiān)聽 + 線程池管理 +WorkThread處理單客戶端。
資源管理:Java 用try-with-resources自動(dòng)釋放 IO 流,鴻蒙用異步 API 避免阻塞主線程。
UI 狀態(tài)聯(lián)動(dòng):鴻蒙客戶端用@State變量控制按鈕可用狀態(tài),實(shí)現(xiàn)【綁定→連接→發(fā)送】的流程化交互。
到此這篇關(guān)于鴻蒙與Java跨平臺(tái)Socket通信實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)鴻蒙與Java跨平臺(tái)Socket通信內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何自定義hibernate validation注解示例代碼
Hibernate Validator 是 Bean Validation 的參考實(shí)現(xiàn) . Hibernate Validator 提供了 JSR 303 規(guī)范中所有內(nèi)置 constraint 的實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于如何自定義hibernate validation注解的相關(guān)資料,需要的朋友可以參考下2018-04-04
Springboot分模塊項(xiàng)目搭建的實(shí)現(xiàn)
在軟件開發(fā)中,利用Spring?Boot進(jìn)行分模塊項(xiàng)目搭建能夠提高代碼的模塊化和復(fù)用性,本文主要介紹了Springboot分模塊項(xiàng)目搭建的實(shí)現(xiàn),感興趣的可以了解一下2024-10-10
idea gradle項(xiàng)目復(fù)制依賴小技巧(推薦)
這篇文章主要介紹了idea gradle項(xiàng)目復(fù)制依賴小技巧,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
詳解Spring boot+CXF開發(fā)WebService Demo
這篇文章主要介紹了詳解Spring boot+CXF開發(fā)WebService Demo,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05
Java讀取制表符文本轉(zhuǎn)換為JSON實(shí)現(xiàn)實(shí)例
在Java開發(fā)中,處理各種數(shù)據(jù)格式是常見的任務(wù),本文將介紹如何使用Java讀取制表符文本文件,并將其轉(zhuǎn)換為JSON格式,以便于后續(xù)的數(shù)據(jù)處理和分析,我們將使用Java中的相關(guān)庫(kù)來(lái)實(shí)現(xiàn)這個(gè)過程,并提供詳細(xì)的代碼示例2024-01-01

