422
社区成员




Socket(也叫套接字)最初是在Unix系统上开发的网络通信接口。后来微软等公司将它移植到windows下。Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
Socket可以看成是两个网络应用通信程序进行通信时各自连接的端点。应用中通过Socket进行数据传输,支持TCP和UDP两种协议。TCP是一种面向连接的、可靠的、基于字节流的传输层协议,提供全双工的通信服务。UDPSocket是面向非连接的协议,它不与对方建立连接,而是直接将要发的数据报发给对方,适用于一次传输数据量很少,对可靠性要求不高的或对实时性要求高的应用场景。
本例使用基于UDP协议的Socket和服务端建立连接,实现了一个能和服务端进行通信的基础聊天案例,在一端发送信息,另一端可以实时收到。
IDE: DevEco Studio 3.0.0.993
SDK: Api Version9
2 创建UDPSocket对象。
3 订阅UDPSocket的打开、消息接收、关闭等事件。
4 绑定IP地址和端口,端口可以指定或由系统随机分配。
5 连接到指定的IP地址和端口。
6 发送数据。
7 关闭UDPSocket连接。
接口名 | 功能描述 |
---|---|
constructUDPSocketInstance() | 创建一个UDPSocket对象 |
bind() | 绑定IP地址和端口号 |
send() | 发送数据 |
close() | 关闭连接 |
connect() | 连接到指定的IP地址 |
on(type: 'message') | 订阅Socket连接的接收消息事件 |
on(type: 'close') | 订阅Socket连接的关闭事件 |
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通信功能所需要的,下面将介绍登录布局和功能的实现。
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)
...
}
将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)}`);
});
}
打印日志如下:
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)}`);
});
}
打印日志如下:
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)
});
}
打印日志如下:
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)}`);
});
}
打印日志如下:
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](