介紹
本示例主要演示了Socket在網(wǎng)絡(luò)通信方面的應(yīng)用,展示了Socket在兩端設(shè)備的連接驗(yàn)證、聊天通信方面的應(yīng)用。
效果預(yù)覽
使用說(shuō)明
1.打開(kāi)應(yīng)用,點(diǎn)擊用戶文本框選擇要登錄的用戶,并輸入另一個(gè)設(shè)備的IP地址,點(diǎn)擊確定按鈕進(jìn)入已登錄的用戶頁(yè)面(兩個(gè)設(shè)備都要依次執(zhí)行此步驟)。
2.在其中一個(gè)設(shè)備上點(diǎn)擊創(chuàng)建房間按鈕,任意輸入房間號(hào),另一個(gè)設(shè)備會(huì)收到有房間號(hào)信息的彈框,點(diǎn)擊確定按鈕后,兩個(gè)設(shè)備進(jìn)入聊天頁(yè)面。
3.在其中一個(gè)設(shè)備上輸入聊天信息并點(diǎn)擊發(fā)送按鈕后,另一個(gè)設(shè)備的聊天頁(yè)面會(huì)收到該聊天消息。
4.點(diǎn)擊頂部標(biāo)題欄右側(cè)的退出圖標(biāo)按鈕,則返回已登錄的用戶頁(yè)面。
5.點(diǎn)擊聊天頁(yè)面中的昵稱欄,會(huì)彈出一個(gè)菜單,選擇離線選項(xiàng)后,兩端設(shè)備的狀態(tài)圖標(biāo)都會(huì)切換為離線圖標(biāo),并且昵稱欄都會(huì)變成灰色,此時(shí)任何一端發(fā)送消息另一端都接收不到消息。
6.當(dāng)點(diǎn)擊昵稱欄再次切換為在線狀態(tài),則兩端的己方賬號(hào)狀態(tài)會(huì)切換為在線圖標(biāo),同時(shí)兩端的昵稱欄會(huì)顯示藍(lán)色,此時(shí)可正常收發(fā)消息。
工程目錄
entry/src/main/ets/MainAbility
|---app.ets
|---model
| |---chatBox.ts // 聊天頁(yè)面
| |---DataSource.ts // 數(shù)據(jù)獲取
| |---Logger.ts // 日志工具
|---pages
| |---Index.ets // 監(jiān)聽(tīng)消息頁(yè)面
| |---Login.ets // 首頁(yè)登錄頁(yè)面
|---Utils
| |---Utils.ets
具體實(shí)現(xiàn)
- 本示例分為三個(gè)模塊
- 輸入對(duì)端IP模塊
- 使用wifi.getIpInfo()方法獲取IP地址,constructUDPSocketInstance方法創(chuàng)建一個(gè)UDPSocket對(duì)象
- 源碼鏈接:[Login.ets]
- 輸入對(duì)端IP模塊
/*
* Copyright (c) 2022-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import wifi from '@ohos.wifi';
import router from '@ohos.router';
import { resolveIP } from '../Utils/Util'
import socket from '@ohos.net.socket';
import Logger from '../model/Logger'
const TAG: string = '[Login]'
let localAddr = {
address: resolveIP(wifi.getIpInfo().ipAddress),
family: 1,
port: 0
}
let oppositeAddr = {
address: '',
family: 1,
port: 0
}
let loginCount = 0
let udp = socket.constructUDPSocketInstance()
@Entry
@Component
struct Login {
@State login_feng: boolean = false
@State login_wen: boolean = false
@State user: string = ''
@State roomDialog: boolean = false
@State confirmDialog: boolean = false
@State ipDialog: boolean = true
@State warnDialog: boolean = false
@State warnText: string = ''
@State roomNumber: string = ''
@State receiveMsg: string = ''
bindOption() {
let bindOption = udp.bind(localAddr);
bindOption.then(() = > {
Logger.info(TAG, 'bind success');
}).catch(err = > {
Logger.info(TAG, 'bind fail');
})
udp.on('message', data = > {
Logger.info(TAG, `data:${JSON.stringify(data)}`);
let buffer = data.message;
let dataView = new DataView(buffer);
Logger.info(TAG, `length = ${dataView.byteLength}`);
let str = '';
for (let i = 0;i < dataView.byteLength; ++i) {
let c = String.fromCharCode(dataView.getUint8(i));
if (c != '') {
str += c;
}
}
if (str == 'ok') {
router.clear();
loginCount += 1;
router.push({
url: 'pages/Index',
params: { address: oppositeAddr.address, port: oppositeAddr.port, loginCount: loginCount }
})
}
else {
this.receiveMsg = str;
this.confirmDialog = true;
}
})
}
build() {
Stack({ alignContent: Alignment.Center }) {
Column() {
Text($r('app.string.MainAbility_label'))
.width('100%')
.height(50)
.backgroundColor('#0D9FFB')
.textAlign(TextAlign.Start)
.fontSize(25)
.padding({ left: 10 })
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
if (!this.ipDialog) {
Column() {
Image(this.login_feng ? $r('app.media.fengziOn') : $r('app.media.wenziOn'))
.width(100)
.height(100)
.objectFit(ImageFit.Fill)
Text('用戶名:' + this.user).fontSize(25).margin({ top: 50 })
Button() {
Text($r('app.string.create_room')).fontSize(25).fontColor(Color.White)
}
.width('150')
.height(50)
.margin({ top: 30 })
.type(ButtonType.Capsule)
.onClick(() = > {
this.roomDialog = true
this.bindOption()
})
}.width('90%').margin({ top: 100 })
}
}.width('100%').height('100%')
if (this.confirmDialog) {
Column() {
Text('確認(rèn)碼:' + this.receiveMsg).fontSize(25)
Row() {
Button($r('app.string.cancel'))
.onClick(() = > {
this.confirmDialog = false
}).backgroundColor(0xffffff).fontColor(Color.Black)
Button($r('app.string.confirm'))
.onClick(() = > {
udp.send({
data: 'ok',
address: oppositeAddr
}).then(function (data) {
Logger.info(TAG, `send ${JSON.stringify(data)}`);
}).catch(function (err) {
Logger.info(TAG, `send ${JSON.stringify(err)}`);
})
router.clear()
loginCount += 1;
router.push({
url: 'pages/Index',
params: { address: oppositeAddr.address, port: oppositeAddr.port, loginCount: loginCount }
})
this.confirmDialog = false;
}).backgroundColor(0xffffff).fontColor(Color.Red)
}.margin({ bottom: 10 })
.justifyContent(FlexAlign.SpaceAround)
}
.width('80%')
.height(150)
.margin({ top: '10%' })
.backgroundColor(Color.White)
.border({ radius: 10, width: 3 })
}
if (this.ipDialog) {
Column() {
Text('本地IP:' + localAddr.address).fontSize(25).margin({ top: 10 })
Text('用戶:' + this.user).fontSize(20).margin({ top: 10 })
.bindMenu([{
value: '風(fēng)子',
action: () = > {
this.user = '風(fēng)子'
this.login_feng = true
this.login_wen = false
localAddr.port = 8080
oppositeAddr.port = 9090
}
},
{
value: '蚊子',
action: () = > {
this.user = '蚊子'
this.login_wen = true
this.login_feng = false
localAddr.port = 9090
oppositeAddr.port = 8080
}
}
])
TextInput({ placeholder: '請(qǐng)輸入對(duì)端ip' })
.width(200)
.fontSize(25)
.margin({ top: 10 })
.onChange((value: string) = > {
oppositeAddr.address = value;
})
if (this.warnDialog) {
Text(this.warnText).fontSize(10).fontColor(Color.Red).margin({ top: 5 })
}
Button($r('app.string.confirm'))
.fontColor(Color.Black)
.height(30)
.margin({ bottom: 10 })
.onClick(() = > {
if (this.user == '') {
this.warnDialog = true;
this.warnText = '請(qǐng)先選擇用戶';
} else if (oppositeAddr.address === '') {
this.warnDialog = true;
this.warnText = '請(qǐng)先輸入對(duì)端IP';
} else {
this.bindOption()
this.ipDialog = false;
Logger.debug(TAG, `peer ip=${oppositeAddr.address}`);
Logger.debug(TAG, `peer port=${oppositeAddr.port}`);
Logger.debug(TAG, `peer port=${localAddr.port}`);
}
})
.backgroundColor(0xffffff)
}
.width('80%')
.height(200)
.margin({ top: '10%' })
.backgroundColor(Color.White)
.border({ radius: 10, width: 3 })
}
if (this.roomDialog) {
Column() {
Text($r('app.string.input_roomNumber')).fontSize(25).margin({ top: 10 })
TextInput()
.width(100)
.fontSize(25)
.margin({ top: 10 })
.onChange((value: string) = > {
this.roomNumber = value;
})
Row() {
Button($r('app.string.cancel'))
.onClick(() = > {
this.roomDialog = false
}).backgroundColor(0xffffff).fontColor(Color.Black)
Button($r('app.string.confirm'))
.onClick(() = > {
Logger.info(TAG, `[ROOM]address=${oppositeAddr.address}`);
Logger.info(TAG, `[ROOM]port=${oppositeAddr.port}`);
/*點(diǎn)擊確定后發(fā)送房間號(hào),另一端開(kāi)始監(jiān)聽(tīng)*/
Logger.info(TAG, `[ROOM]oppositeAddr.address=${oppositeAddr.address}`);
Logger.info(TAG, `[ROOM]oppositeAddr.port=${oppositeAddr.port}`);
Logger.info(TAG, `[ROOM]localAddr.address=${localAddr.address}`);
Logger.info(TAG, `[ROOM]localAddr.port=${localAddr.port}`);
this.bindOption()
udp.send({
data: this.roomNumber,
address: oppositeAddr
}).then(function (data) {
Logger.info(TAG, `send success, data = ${JSON.stringify(data)}`);
}).catch(function (err) {
Logger.info(TAG, `send fail, err = ${JSON.stringify(err)}`);
})
this.roomDialog = false;
}).backgroundColor(0xffffff).fontColor(Color.Red)
}.margin({ bottom: 10 })
.justifyContent(FlexAlign.SpaceAround)
}
.width('80%')
.height(150)
.margin({ top: '10%' })
.backgroundColor(Color.White)
.border({ radius: 10, width: 3 })
}
}
}
}
[Util.ets]
/*
* Copyright (c) 2022-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function resolveIP(ip) {
if (ip < 0 || ip > 0xFFFFFFFF) {
throw ('The number is not normal!');
}
return (ip > >> 24) + '.' + (ip > > 16 & 0xFF) + '.' + (ip > > 8 & 0xFF) + '.' + (ip & 0xFF);
}
- 創(chuàng)建房間模塊
- 點(diǎn)擊創(chuàng)建房間按鈕,彈出創(chuàng)建房間框,輸入房間號(hào),點(diǎn)擊確定,進(jìn)入聊天頁(yè)面
- 源碼鏈接:[Login.ets]
/*
* Copyright (c) 2022-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import wifi from '@ohos.wifi';
import router from '@ohos.router';
import { resolveIP } from '../Utils/Util'
import socket from '@ohos.net.socket';
import Logger from '../model/Logger'
const TAG: string = '[Login]'
let localAddr = {
address: resolveIP(wifi.getIpInfo().ipAddress),
family: 1,
port: 0
}
let oppositeAddr = {
address: '',
family: 1,
port: 0
}
let loginCount = 0
let udp = socket.constructUDPSocketInstance()
@Entry
@Component
struct Login {
@State login_feng: boolean = false
@State login_wen: boolean = false
@State user: string = ''
@State roomDialog: boolean = false
@State confirmDialog: boolean = false
@State ipDialog: boolean = true
@State warnDialog: boolean = false
@State warnText: string = ''
@State roomNumber: string = ''
@State receiveMsg: string = ''
bindOption() {
let bindOption = udp.bind(localAddr);
bindOption.then(() = > {
Logger.info(TAG, 'bind success');
}).catch(err = > {
Logger.info(TAG, 'bind fail');
})
udp.on('message', data = > {
Logger.info(TAG, `data:${JSON.stringify(data)}`);
let buffer = data.message;
let dataView = new DataView(buffer);
Logger.info(TAG, `length = ${dataView.byteLength}`);
let str = '';
for (let i = 0;i < dataView.byteLength; ++i) {
let c = String.fromCharCode(dataView.getUint8(i));
if (c != '') {
str += c;
}
}
if (str == 'ok') {
router.clear();
loginCount += 1;
router.push({
url: 'pages/Index',
params: { address: oppositeAddr.address, port: oppositeAddr.port, loginCount: loginCount }
})
}
else {
this.receiveMsg = str;
this.confirmDialog = true;
}
})
}
build() {
Stack({ alignContent: Alignment.Center }) {
Column() {
Text($r('app.string.MainAbility_label'))
.width('100%')
.height(50)
.backgroundColor('#0D9FFB')
.textAlign(TextAlign.Start)
.fontSize(25)
.padding({ left: 10 })
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
if (!this.ipDialog) {
Column() {
Image(this.login_feng ? $r('app.media.fengziOn') : $r('app.media.wenziOn'))
.width(100)
.height(100)
.objectFit(ImageFit.Fill)
Text('用戶名:' + this.user).fontSize(25).margin({ top: 50 })
Button() {
Text($r('app.string.create_room')).fontSize(25).fontColor(Color.White)
}
.width('150')
.height(50)
.margin({ top: 30 })
.type(ButtonType.Capsule)
.onClick(() = > {
this.roomDialog = true
this.bindOption()
})
}.width('90%').margin({ top: 100 })
}
}.width('100%').height('100%')
if (this.confirmDialog) {
Column() {
Text('確認(rèn)碼:' + this.receiveMsg).fontSize(25)
Row() {
Button($r('app.string.cancel'))
.onClick(() = > {
this.confirmDialog = false
}).backgroundColor(0xffffff).fontColor(Color.Black)
Button($r('app.string.confirm'))
.onClick(() = > {
udp.send({
data: 'ok',
address: oppositeAddr
}).then(function (data) {
Logger.info(TAG, `send ${JSON.stringify(data)}`);
}).catch(function (err) {
Logger.info(TAG, `send ${JSON.stringify(err)}`);
})
router.clear()
loginCount += 1;
router.push({
url: 'pages/Index',
params: { address: oppositeAddr.address, port: oppositeAddr.port, loginCount: loginCount }
})
this.confirmDialog = false;
}).backgroundColor(0xffffff).fontColor(Color.Red)
}.margin({ bottom: 10 })
.justifyContent(FlexAlign.SpaceAround)
}
.width('80%')
.height(150)
.margin({ top: '10%' })
.backgroundColor(Color.White)
.border({ radius: 10, width: 3 })
}
if (this.ipDialog) {
Column() {
Text('本地IP:' + localAddr.address).fontSize(25).margin({ top: 10 })
Text('用戶:' + this.user).fontSize(20).margin({ top: 10 })
.bindMenu([{
value: '風(fēng)子',
action: () = > {
this.user = '風(fēng)子'
this.login_feng = true
this.login_wen = false
localAddr.port = 8080
oppositeAddr.port = 9090
}
},
{
value: '蚊子',
action: () = > {
this.user = '蚊子'
this.login_wen = true
this.login_feng = false
localAddr.port = 9090
oppositeAddr.port = 8080
}
}
])
TextInput({ placeholder: '請(qǐng)輸入對(duì)端ip' })
.width(200)
.fontSize(25)
.margin({ top: 10 })
.onChange((value: string) = > {
oppositeAddr.address = value;
})
if (this.warnDialog) {
Text(this.warnText).fontSize(10).fontColor(Color.Red).margin({ top: 5 })
}
Button($r('app.string.confirm'))
.fontColor(Color.Black)
.height(30)
.margin({ bottom: 10 })
.onClick(() = > {
if (this.user == '') {
this.warnDialog = true;
this.warnText = '請(qǐng)先選擇用戶';
} else if (oppositeAddr.address === '') {
this.warnDialog = true;
this.warnText = '請(qǐng)先輸入對(duì)端IP';
} else {
this.bindOption()
this.ipDialog = false;
Logger.debug(TAG, `peer ip=${oppositeAddr.address}`);
Logger.debug(TAG, `peer port=${oppositeAddr.port}`);
Logger.debug(TAG, `peer port=${localAddr.port}`);
}
})
.backgroundColor(0xffffff)
}
.width('80%')
.height(200)
.margin({ top: '10%' })
.backgroundColor(Color.White)
.border({ radius: 10, width: 3 })
}
if (this.roomDialog) {
Column() {
Text($r('app.string.input_roomNumber')).fontSize(25).margin({ top: 10 })
TextInput()
.width(100)
.fontSize(25)
.margin({ top: 10 })
.onChange((value: string) = > {
this.roomNumber = value;
})
Row() {
Button($r('app.string.cancel'))
.onClick(() = > {
this.roomDialog = false
}).backgroundColor(0xffffff).fontColor(Color.Black)
Button($r('app.string.confirm'))
.onClick(() = > {
Logger.info(TAG, `[ROOM]address=${oppositeAddr.address}`);
Logger.info(TAG, `[ROOM]port=${oppositeAddr.port}`);
/*點(diǎn)擊確定后發(fā)送房間號(hào),另一端開(kāi)始監(jiān)聽(tīng)*/
Logger.info(TAG, `[ROOM]oppositeAddr.address=${oppositeAddr.address}`);
Logger.info(TAG, `[ROOM]oppositeAddr.port=${oppositeAddr.port}`);
Logger.info(TAG, `[ROOM]localAddr.address=${localAddr.address}`);
Logger.info(TAG, `[ROOM]localAddr.port=${localAddr.port}`);
this.bindOption()
udp.send({
data: this.roomNumber,
address: oppositeAddr
}).then(function (data) {
Logger.info(TAG, `send success, data = ${JSON.stringify(data)}`);
}).catch(function (err) {
Logger.info(TAG, `send fail, err = ${JSON.stringify(err)}`);
})
this.roomDialog = false;
}).backgroundColor(0xffffff).fontColor(Color.Red)
}.margin({ bottom: 10 })
.justifyContent(FlexAlign.SpaceAround)
}
.width('80%')
.height(150)
.margin({ top: '10%' })
.backgroundColor(Color.White)
.border({ radius: 10, width: 3 })
}
}
}
}
[Util.ets]
/*
* Copyright (c) 2022-2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function resolveIP(ip) {
if (ip < 0 || ip > 0xFFFFFFFF) {
throw ('The number is not normal!');
}
return (ip > >> 24) + '.' + (ip > > 16 & 0xFF) + '.' + (ip > > 8 & 0xFF) + '.' + (ip & 0xFF);
}
相關(guān)概念
UDP Socket是面向非連接的協(xié)議,它不與對(duì)方建立連接,而是直接把我要發(fā)的數(shù)據(jù)報(bào)發(fā)給對(duì)方,適用于一次傳輸數(shù)據(jù)量很少、對(duì)可靠性要求不高的或?qū)?shí)時(shí)性要求高的應(yīng)用場(chǎng)景。
相關(guān)權(quán)限
1.允許使用Internet網(wǎng)絡(luò)權(quán)限:[ohos.permission.INTERNET]
2.允許應(yīng)用獲取WLAN信息權(quán)限:[ohos.permission.GET_WIFI_INFO]
依賴
不涉及。
約束與限制
1.本示例僅支持標(biāo)準(zhǔn)系統(tǒng)上運(yùn)行,支持設(shè)備:RK3568。
2.本示例僅支持API9版本SDK,版本號(hào):3.2.11.9 及以上。
3.本示例需要使用DevEco Studio 3.1 Beta2 (Build Version: 3.1.0.400 構(gòu)建 2023年4月7日)及以上才可編譯運(yùn)行。
下載
如需單獨(dú)下載本工程,執(zhí)行如下命令:
git init
git config core.sparsecheckout true
echo codeBasicFeatureConnectivitySocket > .git/info/sparse-checkout
git remote add origin https://gitee.com/openharmony/applications_app_samples.git
git pull origin master
審核編輯 黃宇
-
Socket
+關(guān)注
關(guān)注
1文章
212瀏覽量
35542 -
網(wǎng)絡(luò)通信
+關(guān)注
關(guān)注
4文章
824瀏覽量
30731 -
鴻蒙
+關(guān)注
關(guān)注
59文章
2503瀏覽量
43762
發(fā)布評(píng)論請(qǐng)先 登錄
HarmonyOS 網(wǎng)絡(luò)管理開(kāi)發(fā) —Socket 連接
鴻蒙實(shí)戰(zhàn)項(xiàng)目開(kāi)發(fā):【短信服務(wù)】
鴻蒙原生應(yīng)用開(kāi)發(fā)-網(wǎng)絡(luò)管理Socket連接(一)
鴻蒙原生應(yīng)用開(kāi)發(fā)-網(wǎng)絡(luò)管理Socket連接(二)
鴻蒙原生應(yīng)用開(kāi)發(fā)-網(wǎng)絡(luò)管理Socket連接(三)
鴻蒙原生應(yīng)用開(kāi)發(fā)-網(wǎng)絡(luò)管理模塊總述
鴻蒙Flutter實(shí)戰(zhàn):07混合開(kāi)發(fā)
【中秋國(guó)慶不斷更】HarmonyOS網(wǎng)絡(luò)管理開(kāi)發(fā)—Socket連接
什么是Socket連接?Socket與TCP連接的關(guān)系
Socket 網(wǎng)絡(luò)編程框架介紹

什么是Socket連接?Socket的工作原理 它與TCP連接有什么關(guān)系?
鴻蒙OS開(kāi)發(fā)實(shí)戰(zhàn):【Socket小試MQTT連接】

評(píng)論