Harmonyos網(wǎng)絡通信真機Demo演練(一)之TCP聊天室
本Demo界面由ArkUI實現(xiàn),網(wǎng)絡邏輯部分由java實現(xiàn),服務器用容易部署演練的Go實現(xiàn)。JAVA和GO初次實戰(zhàn),本Demo還存在并發(fā)數(shù)據(jù)安全未處理,所以本Demo僅能用于學習??蓪W習之處有以下幾點:
一、FA與PA采用ACE方式的調(diào)用及相互交互、數(shù)據(jù)流轉(zhuǎn)等。
二、Harmonyos的事件機制及使用–自定義事件.
三、異步多線程TCP通信。
一、效果展示
1,服務器端

2,客戶端

二、設計流程圖

三、界面編寫
1,界面效果

2,界面代碼
HML代碼:
- <div class="container">
- <div class="container1">
- <text class="title">
- tcp-client test
- </text>
- </div>
- <div class="container2">
- <text class="text2">IP:</text>
- <input class="input2" placeholder="enter ip" onchange="onChange2">
- </input>
- </div>
- <div class="container3">
- <text class="text3">Port:</text>
- <input class="input3" placeholder="enter port" onchange="onChange3">
- </input>
- </div>
- <button class="button1" type="capsule" onclick="onConnect">連接服務器</button>
- <button class="button3" type="capsule" onclick="onSubResvMsg">訂閱消息</button>
- <textarea class="text4">
- {{outcont}}
- </textarea>
- <div class="container4">
- <input class="input5" placeholder="enter msg" onchange="onChange4">
- </input>
- <button class="button2" type="capsule" onclick="onSend">發(fā)送消息</button>
- </div>
- </div>
JS代碼:
- import prompt from '@system.prompt';
- export default {
- data: {
- title: 'World',
- outcont: '',
- ip:'',
- port:'',
- msg:''
- },
- initConnectAction: function (code,ip,port) {
- var actionData = {};
- actionData.ip = ip;
- actionData.port = port;
- var action = {};
- action.bundleName = "com.gane.tcpclient";
- action.abilityName = "TcpClientAbility";
- action.messageCode = code;
- action.data = actionData;
- action.abilityType = 1;
- action.syncOption = 0;
- return action;
- },
- initAction: function (code) {
- var actionData = {};
- var action = {};
- action.bundleName = "com.gane.tcpclient";
- action.abilityName = "TcpClientAbility";
- action.messageCode = code;
- action.data = actionData;
- action.abilityType = 1;
- action.syncOption = 0;
- return action;
- },
- initAction2: function (code,msg) {
- var actionData = {};
- actionData.msg = msg;
- var action = {};
- action.bundleName = "com.gane.tcpclient";
- action.abilityName = "TcpClientAbility";
- action.messageCode = code;
- action.data = actionData;
- action.abilityType = 1;
- action.syncOption = 0;
- return action;
- },
- onChange2(e){
- this.ip = e.value;
- },
- onChange3(e){
- this.port = e.value;
- },
- onConnect: async function() {
- try {
- var action = this.initConnectAction(1001,this.ip,this.port);
- var result = await FeatureAbility.callAbility(action);
- console.info(" result = " + result);
- this.showToast(result);
- } catch (pluginError) {
- console.error("getBatteryLevel : Plugin Error = " + pluginError);
- }
- },
- onSubResvMsg:async function(){
- try {
- var action = this.initAction(1003);
- var that = this;
- var result = await FeatureAbility.subscribeAbilityEvent(action,function (msgdata) {
- console.info(" batteryLevel info is: " + msgdata);
- var msgRet = JSON.parse(msgdata).data;
- that.printData(msgRet.msg);
- that.showToast(" batteryState change: " + msgRet.msg);
- });
- this.showToast(" subscribe result " + result);
- console.info(" subscribeCommonEvent result = " + result);
- } catch (pluginError) {
- console.error("subscribeCommonEvent error : result= " + result + JSON.stringify(pluginError));
- }
- },
- onSend: async function() {
- try {
- var action = this.initAction2(1002,this.msg);
- var result = await FeatureAbility.callAbility(action);
- console.info("onSend result = " + result);
- this.showToast(result);
- } catch (pluginError) {
- console.error("getBatteryLevel : Plugin Error = " + pluginError);
- }
- },
- onChange4(e){
- this.msg = e.value;
- },
- printData(msg){
- if(this.outcont != null || this.outcont != ""){
- this.outcont = msg + "\n" + this.outcont;
- } else {
- this.outcont = msg;
- }
- },
- showToast: function (msg) {
- prompt.showToast({
- message: msg
- });
- }
- }
注意:
1,這里的交互方法都是用的異步方法,因為這樣不會因業(yè)務側(cè)而阻塞UI線程,從而阻塞主線程。
2,仔細看清楚每個initAction(),弄明白action的構造和帶參傳遞的寫法。
四、PA編寫與交互
java類實現(xiàn)方式如下:
- public class TcpClientAbility extends AceInternalAbility {
- private static final String TAG = TcpClientAbility.class.getSimpleName();
- private static final String BUNDLE_NAME = "com.gane.tcpclient";
- private static final String ABILITY_NAME = "TcpClientAbility";
- public static final String SELF_SOCKET_MSG = "TCP.CLIENT.MSG";
- private static TcpClientAbility instance;
- private TcpClientAbility() {
- super(BUNDLE_NAME, ABILITY_NAME);
- }
- /**
- * ACE注冊
- */
- public void register() {
- this.setInternalAbilityHandler(this::onRemoteRequest);
- HiLog.error(LABEL_LOG,"socket_register");
- }
- /**
- * ACE取消注冊
- */
- public void deregister() {
- this.setInternalAbilityHandler(null);
- HiLog.error(LABEL_LOG,"socket_unregister");
- }
- /**
- * ACE事件回調(diào)接口
- */
- public boolean onRemoteRequest(int code, MessageParcel data, MessageParcel reply, MessageOption option) {
- switch (code) {
- }
- return true;
- }
- }
注意:
1,類必須繼承AceInternalAbility,必須實現(xiàn)注冊、取消注冊、事件回調(diào)接口。
2,register()、deregister()需在合適的位置調(diào)用,我是在mainAblity的onstart和onstop中調(diào)用的
五、TCP客戶端網(wǎng)絡
網(wǎng)絡實現(xiàn),考慮到要能隨時隨地的自由發(fā)送和接收消息,就將消息的收、發(fā)分離,全采用異步進行。根據(jù)業(yè)務需求選型了AsynchronousSocketChannel作為本次實現(xiàn)的網(wǎng)絡基礎類型,主要用到了AsynchronousSocketChannel.open()、AsynchronousSocketChannel.setOption()、AsynchronousSocketChannel.connect()、AsynchronousSocketChannel.write()、AsynchronousSocketChannel.read()等接口。
示例代碼如下:
- socketChannel = AsynchronousSocketChannel.open();
- socketChannel.setOption(StandardSocketOptions.TCP_NODELAY,true);
- socketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
- socketChannel.connect(new InetSocketAddress(param.getIp(), param.getPort()), null,
- new CompletionHandler<Void, Object>() {
- @Override
- public void completed(Void result, Object attachment) {
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- String intput = "我是:";
- try {
- intput = intput + socketChannel.getLocalAddress().toString();
- } catch (IOException e) {
- e.printStackTrace();
- }
- try {
- byteBuffer.put(intput.getBytes("UTF-8"));
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- socketChannel.write(byteBuffer, 1, TimeUnit.SECONDS, null,
- new CompletionHandler<Integer, Object>() {
- @Override
- public void completed(Integer result, Object attachment) {
- }
- public void failed(Throwable exc, Object attachment) {
- }
- });
- }
- @Override
- public void failed(Throwable exc, Object attachment) {
- }
- });
- if (TcpClientAbility.socketChannel != null && TcpClientAbility.socketChannel.isOpen()) {
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- CompletionHandler<Integer, Object> comphandler = new CompletionHandler<Integer, Object>() {
- @Override
- public void completed(Integer result, Object attachment) {
- byteBuffer.flip();
- byte[] byten = new byte[byteBuffer.limit()]; // 可用的字節(jié)數(shù)量
- byteBuffer.get(byten, byteBuffer.position(), byteBuffer.limit());
- String ret = new String(byten);
- TcpClientAbility.socketChannel.read(byteBuffer, -1, TimeUnit.SECONDS, null, this);
- }
- @Override
- public void failed(Throwable exc, Object attachment) {
- TcpClientAbility.socketChannel.read(byteBuffer, -1, TimeUnit.SECONDS, null, this);
- }
- };
- TcpClientAbility.socketChannel.read(byteBuffer, -1, TimeUnit.SECONDS, null, comphandler);
- }
六、自定義事件
由官方提供的CommonEventManager通用事件啟發(fā)而來,官方提供了harmonyos系統(tǒng)提供了藍牙、電池、時間、日期等等相關的通用事件,還提供了電池相關的Demo,具體介紹看官方文檔。我這里拿CommonEventManager的CommonEventManager.subscribeCommonEvent()訂閱事件、CommonEventManager.publishCommonEvent()發(fā)布事件給大家看下:
- MatchingSkills matchingSkills = new MatchingSkills();
- matchingSkills.addEvent(SELF_SOCKET_MSG);
- IRemoteObject notifier = data.readRemoteObject();
- CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills);
- subscriber = new CommonEventSubscriber(subscribeInfo) {
- @Override
- public void onReceiveEvent(CommonEventData commonEventData) {
- //HiLog.info(LABEL_LOG,"socket===shijian" + commonEventData.getData() + ret);
- }
- };
- try {
- CommonEventManager.subscribeCommonEvent(subscriber);
- } catch (RemoteException e) {
- }
- }
- Intent intent = new Intent();
- Operation operation = new Intent.OperationBuilder().withAction(TcpClientAbility.SELF_SOCKET_MSG).build();
- intent.setOperation(operation);
- intent.setParam("msg",ret);
- CommonEventData eventData = new CommonEventData(intent);
- eventData.setData(ret);
- try {
- CommonEventManager.publishCommonEvent(eventData);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
七、總結
大概思路和所用到的重點知識點在上面以分別列出來了,做完了覺得很簡單,但實際上用一門或多門不怎么熟悉而且相關開發(fā)思路借鑒比較少的開發(fā)框架寫東西時,確實會在動手前很迷茫。覺得迷茫不要退縮,還是那句話,沒有程序解決不了的問題,只有沒思路的程序員,只要想做,就要將整體拆解,化整為零,個個擊破。
本文雖然實現(xiàn)了簡單的多客戶端自由聊天,但還有很多不足,如聊天記錄保存,跳轉(zhuǎn)頁面后回來怎么恢復頁面,websocket、UDP、HTTP、藍牙等通信模式的探索實踐等,不足之處后續(xù)有空繼續(xù)探索不上。有啥不足之處歡迎大家留言,助我改進提升。
文章相關附件可以點擊下面的原文鏈接前往下載
https://harmonyos.51cto.com/resource/1567