1. TCP回聲服務(wù)器實(shí)現(xiàn)可行性
在前文鴻蒙網(wǎng)絡(luò)編程系列2-UDP回聲服務(wù)器的實(shí)現(xiàn)中,介紹了什么是回聲服務(wù)器疆前,并且基于UDP協(xié)議實(shí)現(xiàn)了一個(gè)簡單的回聲服務(wù)器懒熙,本節(jié)將基于TCP協(xié)議實(shí)現(xiàn)一個(gè)類似的回聲服務(wù)器梆奈。在鴻蒙API10以后侦副,提供了TCPSocketServer類,該類封裝了TCP服務(wù)端的相關(guān)接口镀娶,包括用來監(jiān)聽的listen方法奖蔓,訂閱各種事件的on方法赞草,以及發(fā)送數(shù)據(jù)的send方法,關(guān)于這些接口的簡介見前文鴻蒙網(wǎng)絡(luò)編程系列23-實(shí)現(xiàn)一個(gè)基于鴻蒙API的HTTP服務(wù)器中的第一部分吆鹤,或者直接查看鴻蒙官方文檔厨疙,有了這些接口的支持,編寫回聲服務(wù)器也是易如反掌了疑务。
2. 實(shí)現(xiàn)TCP回聲服務(wù)器示例
本示例運(yùn)行后的界面如下所示:
輸入要監(jiān)聽的端口沾凄,然后單擊“啟動(dòng)”按鈕即可啟動(dòng)對該端口的監(jiān)聽了梗醇,如果有TCP客戶端發(fā)送數(shù)據(jù)到這個(gè)端口,服務(wù)器會(huì)接收數(shù)據(jù)并在下面的日志區(qū)域顯示撒蟀,然后回寫到發(fā)送端叙谨。
下面詳細(xì)介紹創(chuàng)建該示例應(yīng)用的步驟并演示用法。
步驟1:創(chuàng)建Empty Ability項(xiàng)目保屯。
步驟2:在module.json5配置文件加上對權(quán)限的聲明:
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
},
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
}
]
這里添加了對互聯(lián)網(wǎng)的訪問權(quán)限以及后臺(tái)執(zhí)行權(quán)限手负。
在module.json5配置文件的abilities節(jié)點(diǎn)下加上長時(shí)任務(wù)類型的配置:
"backgroundModes": [
"dataTransfer"
]
步驟3:在文件EntryAbility.ets中的onCreate生命周期回調(diào)函數(shù)添加如下的代碼:
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
let wantAgentInfo: wantAgent.WantAgentInfo = {
// 點(diǎn)擊通知后,將要執(zhí)行的動(dòng)作列表
wants: [
{
bundleName: "com.zl.network.demo.tcp.echoserver",
abilityName: "EntryAbility"
}
],
// 點(diǎn)擊通知后姑尺,動(dòng)作類型
actionType: wantAgent.OperationType.START_ABILITY,
// 使用者自定義的一個(gè)私有值
requestCode: 0,
// 點(diǎn)擊通知后竟终,動(dòng)作執(zhí)行屬性
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
try {
// 通過wantAgent模塊下getWantAgent方法獲取WantAgent對象
wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
try {
let list: Array<string> = ["dataTransfer"];
backgroundTaskManager.startBackgroundRunning(this.context, list, wantAgentObj)
.then((res: backgroundTaskManager.ContinuousTaskNotification) => {
console.info("Operation startBackgroundRunning succeeded");
})
.catch((error: BusinessError) => {
console.error(`Operation startBackgroundRunning failed. code is ${error.code} message is ${error.message}`);
});
} catch (error) {
console.error(`Operation startBackgroundRunning failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
}
});
} catch (error) {
console.error(`Operation getWantAgent failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
}
}
該段代碼主要是啟動(dòng)后臺(tái)長時(shí)服務(wù),因?yàn)門CP服務(wù)端有可能需要長時(shí)間在后臺(tái)運(yùn)行切蟋,通過該步驟可以獲得授權(quán)统捶。
步驟4:在頁面文件Index.ets里添加如下的代碼:
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { ArrayList, util } from '@kit.ArkTS';
//TCP服務(wù)端實(shí)例
let tcpSocketServer: socket.TCPSocketServer = socket.constructTCPSocketServerInstance()
@Entry
@Component
struct Index {
@State title: string = 'TCP回聲服務(wù)器示例';
@State running: boolean = false
//連接、通訊歷史記錄
@State msgHistory: string = ''
//本地端口
@State port: number = 9999
scroller: Scroller = new Scroller()
//已連接的客戶端列表
clientList = new ArrayList<socket.TCPSocketConnection>()
build() {
RelativeContainer() {
Text(this.title)
.id('txtTitle')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.alignRules({
middle: { anchor: '__container__', align: HorizontalAlign.Center },
top: { anchor: '__container__', align: VerticalAlign.Top }
})
.margin(10)
Text("綁定的服務(wù)器端口:")
.id('txtPort')
.fontSize(15)
.textAlign(TextAlign.Start)
.height(40)
.width(150)
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: 'txtTitle', align: VerticalAlign.Bottom }
})
.margin(10)
TextInput({ text: this.port.toString() })
.onChange((value) => {
this.port = parseInt(value)
})
.type(InputType.Number)
.height(40)
.id('txtInputPort')
.fontSize(15)
.alignRules({
left: { anchor: 'txtPort', align: HorizontalAlign.End },
right: { anchor: 'btnStart', align: HorizontalAlign.Start },
top: { anchor: 'txtTitle', align: VerticalAlign.Bottom }
})
.margin(5)
Button(this.running ? "停止" : "啟動(dòng)")
.onClick(() => {
if (!this.running) {
this.startServer()
} else {
this.stopServer()
}
})
.height(40)
.width(80)
.id('btnStart')
.fontSize(15)
.alignRules({
right: { anchor: '__container__', align: HorizontalAlign.End },
top: { anchor: 'txtTitle', align: VerticalAlign.Bottom }
})
.margin(5)
Scroll(this.scroller) {
Text(this.msgHistory)
.textAlign(TextAlign.Start)
.padding(10)
.width('100%')
.fontSize(12)
.backgroundColor(0xeeeeee)
}
.alignRules({
left: { anchor: '__container__', align: HorizontalAlign.Start },
top: { anchor: 'txtPort', align: VerticalAlign.Bottom },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom }
})
.align(Alignment.Top)
.backgroundColor(0xeeeeee)
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.On)
.scrollBarWidth(20)
.margin(5)
.id('scrollHis')
}
.height('100%')
.width('100%')
}
//停止服務(wù)
stopServer() {
tcpSocketServer.off('connect')
for (let client of this.clientList) {
client.off('message')
}
this.running = false
this.msgHistory += "停止服務(wù)\r\n"
}
//啟動(dòng)服務(wù)
startServer() {
//訂閱連接事件消息
tcpSocketServer.on('connect', (clientSocket: socket.TCPSocketConnection) => {
this.clientList.add(clientSocket)
clientSocket.on('message', (msgInfo: socket.SocketMessageInfo) => {
//收到的信息轉(zhuǎn)化為字符串
let content = buf2String(msgInfo.message)
//顯示信息日志柄粹,最后加上回車換行
this.msgHistory += `[${msgInfo.remoteInfo.address}:${msgInfo.remoteInfo.port}]${content}\r\n`
//把收到的信息發(fā)回客戶端
clientSocket.send({ data: msgInfo.message })
})
});
let listenAddress: socket.NetAddress = { address: '0.0.0.0', port: this.port }
//綁定到指定的端口并啟動(dòng)客戶端連接監(jiān)聽
tcpSocketServer.listen(listenAddress).then(() => {
this.msgHistory += "監(jiān)聽成功\r\n"
this.running = true
this.msgHistory += "服務(wù)啟動(dòng)\r\n"
}).catch((err: BusinessError) => {
this.msgHistory += "監(jiān)聽失敗\r\n"
});
}
}
//ArrayBuffer轉(zhuǎn)utf8字符串
function buf2String(buf: ArrayBuffer) {
let msgArray = new Uint8Array(buf);
let textDecoder = util.TextDecoder.create("utf-8");
return textDecoder.decodeWithStream(msgArray)
}
步驟5:編譯運(yùn)行喘鸟,可以使用模擬器或者真機(jī)。
步驟6:單擊“啟動(dòng)”按鈕啟動(dòng)TCP服務(wù)端驻右。
步驟7:啟動(dòng)TCP客戶端迷守,輸入服務(wù)端地址和端口,單擊“連接”按鈕連接到服務(wù)端旺入,然后輸入要發(fā)送給服務(wù)端的消息,最后單擊“發(fā)送”按鈕凯力,界面如下圖所示茵瘾。
步驟8:此時(shí)查看TCP服務(wù)端界面,可以看到服務(wù)端也接收到了客戶端的消息咐鹤,如下圖所示拗秘。
在本示例中,要注意的是TCP服務(wù)端是可以接受多個(gè)客戶端的連接的祈惶,也就是說在訂閱連接事件的消息并進(jìn)行處理時(shí)雕旨,每一個(gè)連接(TCPSocketConnection)都代表一個(gè)新的TCP客戶端,這些客戶端都是互相獨(dú)立的捧请,要處理與客戶端的消息收發(fā)凡涩,就要訂閱每一個(gè)連接的接收消息事件。
3.注意事項(xiàng)
服務(wù)端需要長時(shí)間運(yùn)行疹蛉,為了在應(yīng)用進(jìn)入后臺(tái)后仍然可以響應(yīng)活箕,在代碼里申請了后臺(tái)運(yùn)行權(quán)限,在oncreate回調(diào)里也做了響應(yīng)的處理可款,不過本示例只在模擬器里調(diào)試通過育韩,還沒有機(jī)會(huì)在實(shí)體機(jī)中實(shí)際運(yùn)行克蚂。
(本文作者原創(chuàng),除非明確授權(quán)禁止轉(zhuǎn)載)
本文源碼地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/tcp/TCPEchoServer
本系列源碼地址: