鴻蒙網(wǎng)絡(luò)編程系列25-TCP回聲服務(wù)器的實(shí)現(xiàn)

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)行后的界面如下所示:

image.png

輸入要監(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ā)送”按鈕凯力,界面如下圖所示茵瘾。

image.png

步驟8:此時(shí)查看TCP服務(wù)端界面,可以看到服務(wù)端也接收到了客戶端的消息咐鹤,如下圖所示拗秘。

image.png

在本示例中,要注意的是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

本系列源碼地址:

https://gitee.com/zl3624/harmonyos_network_samples

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末筋讨,一起剝皮案震驚了整個(gè)濱河市埃叭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悉罕,老刑警劉巖赤屋,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蛮粮,居然都是意外死亡益缎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門然想,熙熙樓的掌柜王于貴愁眉苦臉地迎上來莺奔,“玉大人,你說我怎么就攤上這事变泄×钣矗” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵妨蛹,是天一觀的道長屏富。 經(jīng)常有香客問我,道長蛙卤,這世上最難降的妖魔是什么狠半? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮颤难,結(jié)果婚禮上神年,老公的妹妹穿的比我還像新娘。我一直安慰自己行嗤,他們只是感情好已日,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著栅屏,像睡著了一般飘千。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上栈雳,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天护奈,我揣著相機(jī)與錄音,去河邊找鬼哥纫。 笑死逆济,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奖慌,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼抛虫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了简僧?” 一聲冷哼從身側(cè)響起建椰,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岛马,沒想到半個(gè)月后棉姐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡啦逆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年伞矩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夏志。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乃坤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沟蔑,到底是詐尸還是另有隱情湿诊,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布瘦材,位于F島的核電站厅须,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏食棕。R本人自食惡果不足惜朗和,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望簿晓。 院中可真熱鬧眶拉,春花似錦、人聲如沸抢蚀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽皿曲。三九已至,卻和暖如春吴侦,著一層夾襖步出監(jiān)牢的瞬間屋休,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來泰國打工备韧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留劫樟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像叠艳,于是被迫代替她去往敵國和親奶陈。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容