UDPSocket使用详解

Ryaaaaaaan 2023-03-10 17:27:03

什么是Socket

Socket(也叫套接字)最初是在Unix系统上开发的网络通信接口。后来微软等公司将它移植到windows下。Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

TCP和UDP传输简介

Socket可以看成是两个网络应用通信程序进行通信时各自连接的端点。应用中通过Socket进行数据传输,支持TCP和UDP两种协议。TCP是一种面向连接的、可靠的、基于字节流的传输层协议,提供全双工的通信服务。UDPSocket是面向非连接的协议,它不与对方建立连接,而是直接将要发的数据报发给对方,适用于一次传输数据量很少,对可靠性要求不高的或对实时性要求高的应用场景。

  • 面向连接的工作流程:

img

  • 无连接的工作流程:

img

开发示例

本例使用基于UDP协议的Socket和服务端建立连接,实现了一个能和服务端进行通信的基础聊天案例,在一端发送信息,另一端可以实时收到。

开发环境

IDE: DevEco Studio 3.0.0.993

SDK: Api Version9

开发流程

  • 基本流程步骤:

1 导入@ohos.net.socket模块。

2 创建UDPSocket对象。

3 订阅UDPSocket的打开、消息接收、关闭等事件。

4 绑定IP地址和端口,端口可以指定或由系统随机分配。

5 连接到指定的IP地址和端口。

6 发送数据。

7 关闭UDPSocket连接。

  • 通信流程图:

img

接口说明

接口名功能描述
constructUDPSocketInstance()创建一个UDPSocket对象
bind()绑定IP地址和端口号
send()发送数据
close()关闭连接
connect()连接到指定的IP地址
on(type: 'message')订阅Socket连接的接收消息事件
on(type: 'close')订阅Socket连接的关闭事件

文件结构

img

AppScope:App作用域目录。

entry/src/main/ets:程序目录,用于存放ets源码。

AbilityStage.ts:实现AbilityStage接口。

MainAbility:应用/服务的入口。

MainAbility.ts:承载Ability生命周期。

Model:实体类目录。

ChatMsg.ets:用于封装聊天消息。

pages:存放应用页面。

ChatPage.ets:聊天界面。

LoginPage.ets:登录界面。

Utils:工具类目录。

SocketUtil.ts:用于封装解析IP地址的函数。

UdpClient.ts:用于封装UDP相关API。

entry/src/main/resources:资源文件目录。

module.json5:应用配置文件,包含网络权限配置。

权限配置

需要在module.json中配置以下权限。其中INTERNET权限用于使用网络socket,GET_WIFI_INFO用于获取WLAN信息(比如获取设备IP)。

"requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "name": "ohos.permission.GET_WIFI_INFO"
      }
]

功能实现

  • 登录页面的实现

登录页面主要功能是获取本机IP和服务端IP,这两个参数是实现UDP通信功能所需要的,下面将介绍登录布局和功能的实现。

  • 预览效果如下:

img

1 . 实现登录页面布局,主要代码如下:

@Entry
@Component
struct Login {
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Row() {
        Text('本地IP').fontSize(35).width('25%').fontWeight(FontWeight.Bold)
        Text(localIp)
          .fontSize(35)
      }.width('80%')

      Row() {
        Text('对端IP').fontSize(35).width('25%').fontWeight(FontWeight.Bold)
        TextInput({ placeholder: '请输入对端IP' })
          ...
      }.height(75)

      Text('确定')
        .onClick(() => {
          ...
        })
    }
  }
}

2 . 导入SocketUtil.ts文件中的resolveIP函数,获取本机IP地址。

import { resolveIP } from '../Utils/SocketUtil'

let localIp = resolveIP(wifi.getIpInfo().ipAddress)

3 . 获取对端的IP地址。

let oppositeIp = ""
TextInput({ placeholder: '请输入对端IP' })
  ...
  .onChange((value: string) => {
    oppositeIp = value
  }) 

4 . 跳转到聊天界面,将本机IP和对端IP作为参数传递到ChatPage.ets文件。

Text('确定')
  ...
  .onClick(() => {
    ...
    router.push({
      url: 'pages/ChatPage',
      params: { localIp: localIp, oppositeIp: oppositeIp}
    })
  })
  • 聊天页面的实现

1 . 标题栏用于显示对端IP地址,主要实现代码如下:

@Component
struct Title {
  private title: string
  build() {
    Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Text(this.title).fontSize(30).fontColor("#fdfdfd")
    }.height(80).backgroundColor("#333534")
  }
} 

2 . 聊天消息列表用于显示聊天内容,右侧为己方发送的消息,左侧为对端发送的消息。主要实现代码如下:

Scroll(this.scroller) {
  Column() {
    ForEach(this.msgArr, (item) => {
      if (item.isSend) {
        RightMessageBox({ msgStr: item.message })
      } else {
        LeftMessageBox({ msgStr: item.message })
      }
    }, item => item.id)
  }.width('100%')
}
.layoutWeight(1)
.width("100%")

3 . 左侧消息组件LeftMessageBox实现代码如下:

@Component
struct LeftMessageBox {
  private msgStr: string
  build() {
    Row() {
      Image($r("app.media.xiong")).width(100).height(100).margin({ left: 10, right: 10 })
      Text(this.msgStr)
        .fontSize(30)
        .backgroundColor('#22BE2C')
        .padding(15)
        .borderRadius(10)
    }
    .width('100%')
    .padding({top:20,bottom:20,right:280})
    .alignItems(VerticalAlign.Center)
  }
}

4 . 右侧消息组件RightMessageBox实现代码如下:

@Component
struct RightMessageBox {
  private msgStr: string
  build() {
    Row() {
      Image($r("app.media.kang")).width(100).height(100).margin({ left: 10, right: 10 })
      Text(this.msgStr)
        .fontSize(30)
        .backgroundColor('#FFF200')
        .padding(15)
        .borderRadius(10)
    }
    .width('100%')
    .padding({top:20,bottom:20,left:280})
    .alignItems(VerticalAlign.Center)
    .direction(Direction.Rtl)
  }
}  

5 . 底部栏用于输入和发送消息,主要实现代码如下:

Row() {
  TextInput({ placeholder: '', text: '' })
    .height(60)
    .fontSize(30)
    .width('70%')
    .margin(10)
   ...

  Button("发送")
    .height(60)
    .width(100)
    .margin(10)
    .fontSize(30)
    ...
}
  • UdpClient类实现

将UDP相关API的实现封装到UdpClient类中,创建UDPSocket对象、绑定IP地址和端口、发送消息、订阅消息以及关闭UDPSocket连接等,实现步骤如下:

1 . 导入socket模块,定义构造函数,在构造函数中使用constructUDPSocketInstance创建UDPSocket对象。

import socket from '@ohos.net.socket';

export default class UdpClient {
    private localIp: string= ''
    private oppositeIp: string= ''
    private udp: any= null

    constructor(localIp: string, oppositeIp: string) {
        this.localIp = localIp
        this.oppositeIp = oppositeIp
        this.udp = socket.constructUDPSocketInstance(); 
    }
}

2 . 定义bindUdp函数,绑定本机IP地址和端口,端口可以指定或由系统随机分配。

bindUdp() {
    let promise = this.udp.bind({
        address: this.localIp, port: 9000, family: 1
    });
    promise.then(() => {
        console.log(`${TAG} udp bind success`);
    }).catch(err => {
        console.log(`${TAG} udp bind failed:${JSON.stringify(err)}`);
    });
}

打印日志如下:

img

3 . 定义函数sendMsg,用于发送消息。

sendMsg(msg: string) {
    let promise = this.udp.send({
        data: msg,
        address: {
            address: this.oppositeIp,
            port: 9000,
            family: 1
        }
    });
    promise.then(() => {
        console.log(`${TAG} udp send success:${msg}`);
    }).catch(err => {
        console.log(`${TAG} udp send fail:${JSON.stringify(err)}`);
    });
}

打印日志如下:

img

4 . 定义函数onMessage,用于订阅服务端消息。

onMessage(callback) {
    this.udp.on('message', value => {
        console.log(`${TAG} udp on message:${value.message}`);
        // 收到的是ArrayBuffer 需要进行转换解析
        let dataView = new DataView(value.message)
        console.log(`${TAG} udp message length:${dataView.byteLength}`);
        let str = ""
        for (let i = 0;i < dataView.byteLength; ++i) {
            let c = String.fromCharCode(dataView.getUint8(i))
            if (c !== "\n") {
                str += c
            }
        }
        console.log(`${TAG} udp on message array buffer:${str}`);
        callback(str)
    });
}

打印日志如下:

img

5 . 定义closeUdp函数,用于关闭UDPSocket连接。

closeUdp() {
    let promise = this.udp.close();
    promise.then(() => {
        console.log(`${TAG} udp close success`);
    }).catch(err => {
        console.log(`${TAG} udp close fail:${JSON.stringify(err)}`);
    });
}

打印日志如下:

img

  • 使用UDP实现两端通信

1 . 创建ChatMsg类用于封装发送和订阅的消息。

xport default class ChatMsg {
  isSend: boolean
  message: string
  id: string;
  constructor(isSend: boolean, message: string) {
    this.id = `${NextId++}`;
    this.isSend = isSend
    this.message = message
  }
} 

2 . 获取从登录界面传过来的对端IP和本机IP,定义消息数组。

import router from '@ohos.router';

@Entry
@Component
export struct ChatPage {
  @State msgArr: ChatMsg[]= []
  private localIp: string = router.getParams().localIp
  private oppositeIp: string = router.getParams().oppositeIp
  }

3 . 在ChatPage.ets中导入UdpClient类,创建UdpClient对象,使用UdpClient对象调用bindUdp和onMessage函数。

import UdpClient  from '../Utils/UdpClient';

onPageShow() {
  this.udpClient = new UdpClient(this.localIp, this.oppositeIp)
  this.udpClient.bindUdp()
  this.udpClient.onMessage((msg) => {
    this.msgArr.push(new ChatMsg(false, msg))
  })
}

4 . 给"发送"按钮绑定点击事件,使用UdpClient对象调用sendMsg函数实现发送消息功能。

Button("发送")
  ...
  .onClick(() => {
    this.msgArr.push(new ChatMsg(true, this.sendMsg))
    this.udpClient.sendMsg(this.sendMsg)
    this.sendMsg = ''
  }) 

5 . 在aboutToDisappear中关闭UDPSocket连接。

aboutToDisappear() {
  this.udpClient.closeUdp()
}

参考文档

[1] 使用UDP实现与服务器通信. [https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/UdpDemoOH](

...全文
292 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

422

社区成员

发帖
与我相关
我的任务
社区描述
OpenHarmony开发者社区
其他 企业社区
社区管理员
  • csdnsqst0025
  • shewaliujingli
  • BaoWei
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧