[本文正在参加星光计划3.0--夏日挑战赛] 活动链接
在过去很长一段时间,我都非常希望能够将OpenHarmony与HarmonyOS设备进行一个联动,但是碍于一些底层接口未完善一直无法实现。但是在前几个月,OpenHarmony3.1 带来了更多可能。本次,我将分享如何在搭载HarmonyOS的手机和搭载OpenHarmony的开发板上,实现socket对话!
0. 效果演示
1.HarmonyOS侧
1.1 新建一个JAVA工程,编写简单的测试页面

- ability_main.xml (主页面)
 
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="center"
    ohos:orientation="vertical">
    <TextField
        ohos:id="$+id:sendfield"
        ohos:height="200px"
        ohos:width="800px"
       ohos:bottom_margin="32vp"
        ohos:text_size="32fp"
        ohos:hint="请输入发送信息"
        ohos:background_element="$graphic:tfbg"
        />
    <Button
        ohos:id="$+id:btn"
        ohos:height="120px"
        ohos:width="300px"
        ohos:text="启动"
        ohos:text_size="32fp"
       ohos:background_element="$graphic:btn"
        />
    <Text
        ohos:id="$+id:text"
        ohos:top_margin="50vp"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text_size="32fp"
        ohos:text="暂无消息"
        />
    <Text
        ohos:id="$+id:localip"
        ohos:height="match_content"
        ohos:width="match_content"
        ohos:text="本机IP"
        ohos:top_margin="30vp"
        ohos:text_size="32fp"
        />
    <TextField
        ohos:id="$+id:textfield"
        ohos:height="200px"
        ohos:width="800px"
        ohos:top_margin="50vp"
        ohos:text_size="32fp"
        ohos:hint="请输入IP地址"
        ohos:background_element="$graphic:tfbg"
        />
</DirectionalLayout> 
这是我自己编写的一个测试页面,但不是最重要的。
1.2 编写socket功能
目前,鸿蒙的Socket通信功能只能在JAVA侧实现,并且官网对相关的功能解析不够全面,但是足够实现UDP通信。
这里直接参考官网,可以快速实现Socket功能,传送门:JAVA-socket
在MainAbilitySlice中编写两个主要函数,并结合个人情况绑定到测试按键上即可。
- 
StartServer()
HiLog.info(LABEL_LOG, "StartServer run"); NetManager netManager = NetManager.getInstance(null); if (!netManager.hasDefaultNet()) { HiLog.error(LABEL_LOG, "netManager.hasDefaultNet() failed"); return; } NetHandle netHandle = netManager.getDefaultNet(); DatagramSocket socket = null; // 通过Socket绑定来进行数据传输 try { HiLog.info(LABEL_LOG, "wait receive data"); //通过getLocalIpAddress()快速获取本机的IP地址 InetAddress address = netHandle.getByName(getLocalIpAddress()); //端口号+主机地址 socket = new DatagramSocket(10006, address); netHandle.bindSocket(socket); /*至此绑定Socket结束*/ HiLog.info(LABEL_LOG, "绑定成功"); /*监听函数*/ byte[] buffer = new byte[1024]; DatagramPacket response = new DatagramPacket(buffer, buffer.length); while (true) { //接收数据 socket.receive(response); int len = response.getLength(); HiLog.info(LABEL_LOG, "接收成功"); //将数据打印到屏幕上 s = new String(buffer, StandardCharsets.UTF_8).substring(0, len); //一个textfield组件 text.setText(s); HiLog.info(LABEL_LOG, "receive data: " + s); } } catch (IOException e) { HiLog.error(LABEL_LOG, "rev IOException: "); } } - sendMessage()
 
NetManager netManager = NetManager.getInstance(null);
                if (!netManager.hasDefaultNet()) {
                    HiLog.error(LABEL_LOG,
                            "netManager.hasDefaultNet() failed");
                    return;
                }
                NetHandle netHandle = netManager.getDefaultNet();
                // 通过Socket绑定来进行数据传输
                DatagramSocket socket = null;
                try {
                    //从textfield组件获取用户输入的对端ip地址
                    HOST = iptf.getText();
                    InetAddress address = netHandle.getByName(HOST);
                    socket = new DatagramSocket();
                    netHandle.bindSocket(socket);
                    /*至此 已绑定对端Socket*/
                    //从一个textfield组件获取用户输入的要发送的信息。
                    String data = new String(sendtf.getText());
                    //这里默认还是发送至对端的10006端口
                    DatagramPacket request = new DatagramPacket(data.getBytes(
                            StandardCharsets.UTF_8), data.length(),
                            address, PORT);
                    // buffer赋值
                    // 发送数据
                    socket.send(request);
                    HiLog.info(LABEL_LOG, "send data: " + data);
                } catch (IOException e) {
                    HiLog.error(LABEL_LOG, "send IOException: ");
                } finally {
                    if (null != socket) {
                    }
                }
- getlocalip()
private String getLocalIpAddress() { WifiDevice wifiDevice = WifiDevice.getInstance(getContext()); Optional<IpInfo> ipInfo = wifiDevice.getIpInfo(); int ip = ipInfo.get().getIpAddress(); return (ip & 0xFF) + "." + ((ip >> 8) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "." + (ip >> 24 & 0xFF); }至此,接收信息和发送信息的函数都编写完了。但并没有结束,这样的函数并不能跑在UI线程上,我们必须让其在其他线程上运作,那必须请出我们最爱的EventRunner.
1.3 编写Mythread类
EventRunner传送门
package com.example.hoop.util; 
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
public class Mythread {
public static void inUI(Runnable runnable) {
//返回主线程
EventRunner runner = EventRunner.getMainEventRunner();
    EventHandler eventHandler = new EventHandler(runner);
    eventHandler.postSyncTask(runnable);
}
public static void inBG(Runnable runnable) {
    EventRunner runner = EventRunner.create(true);
    EventHandler eventHandler = new EventHandler(runner);
    //投递任务,我们的Socket函数应该投递于此。
    eventHandler.postTask(runnable, 0, EventHandler.Priority.IMMEDIATE);
}
}
+ 将任务投递给EventRunner
> 只需要将刚刚的两个主要函数投递就行了
```java
private void sendMessage() {
        Mythread.inBG(new Runnable() {
            @Override
            public void run() {
                NetManager netManager = NetManager.getInstance(null);
                if (!netManager.hasDefaultNet()) {
                    HiLog.error(LABEL_LOG,"netManager.hasDefaultNet() failed");
                    return;
                }
                NetHandle netHandle = netManager.getDefaultNet();
                // 通过Socket绑定来进行数据传输
                DatagramSocket socket = null;
                try {
                    //从textfield组件获取用户输入的对端ip地址
                    HOST=iptf.getText();
                    InetAddress address = netHandle.getByName(HOST);
                    socket = new DatagramSocket();
                    netHandle.bindSocket(socket);
                    /*至此 已绑定对端Socket*/
                    //从一个textfield组件获取用户输入的要发送的信息。
                    String data = new String(sendtf.getText());
                    //这里默认还是发送至对端的10006端口
                    DatagramPacket request = new DatagramPacket(data.getBytes(StandardCharsets.UTF_8), data.length(), address, PORT);
                    // buffer赋值
                    // 发送数据
                    socket.send(request);
                    HiLog.info(LABEL_LOG,"send data: " + data);
                } catch (IOException e) {
                    HiLog.error(LABEL_LOG,"send IOException: ");
                } finally {
                    if (null != socket) {
                    }
                }
            }
        });
    }
    private void StartServer() {
        Mythread.inBG(new Runnable() {
            @Override
            public void run() {
                HiLog.info(LABEL_LOG,"StartServer run");
                NetManager netManager = NetManager.getInstance(null);
                if (!netManager.hasDefaultNet()) {
                    HiLog.error(LABEL_LOG,"netManager.hasDefaultNet() failed");
                    return;
                }
                NetHandle netHandle = netManager.getDefaultNet();
                DatagramSocket socket = null;
                // 通过Socket绑定来进行数据传输
                try {
                    HiLog.info(LABEL_LOG,"wait receive data");
                    //通过getLocalIpAddress()快速获取本机的IP地址
                    InetAddress address = netHandle.getByName(getLocalIpAddress());
                    //端口号+主机地址
                    socket = new DatagramSocket(10006, address);
                    netHandle.bindSocket(socket);
                    /*至此绑定Socket结束*/
                    HiLog.info(LABEL_LOG,"绑定成功");
                    /*监听函数*/
                    byte[] buffer = new byte[1024];
                    DatagramPacket response = new DatagramPacket(buffer, buffer.length);
                    while(true){
                        //接收数据
                        socket.receive(response);
                        int len = response.getLength();
                        HiLog.info(LABEL_LOG,"接收成功");
                        //将数据打印到屏幕上
                        s = new String(buffer, StandardCharsets.UTF_8).substring(0, len);
                        Mythread.inUI(new Runnable() {
                            @Override
                            public void run() {
                                //一个textfield组件
                                text.setText(s);
                            }
                        });
                        HiLog.info(LABEL_LOG,"receive data: " + s);
                    }
                } catch (IOException e) {
                    HiLog.error(LABEL_LOG,"rev IOException: " );
                }
            }
        });
    }
2.OpenHarmony侧
2.1 新建工程,编写测试页面

- index.hml
<div class="container"> <text class="title"> {{ title }} </text> <!-- <div style="width: 30%;height: 30%;background-color: blueviolet;">--> <!-- </div>--> <!-- <input type="text" id="localip" placeholder="输入本机IP"--> <!-- @change="newlocalip">--> <!-- </input>--> <text style="width: 300px;height: 70px;"> 本机IP{{getIpAddress()}} </text> <button type="capsule" style="width: 300px;height: 70px;" onclick="creatScoket"> 点击创建 </button> <input id="remoteip" placeholder="输入服务器IP" @change="newremoteip" /> <button type="capsule" style="width: 300px;height: 70px;margin-top: 5%;" onclick="sendMessage"> 点击连接 </button> <input placeholder="输入需要发送的信息" type="text" enterkeytype="done" @change="newMessage" /> </div> 
### 2.2 编写Socket功能
[OpenHarmony-Socket](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/connectivity/socket-connection.md)
```javascript
import socket from '@ohos.net.socket';
import wifi from '@ohos.wifi';
export default {
    data: {
        title: "等待新的消息...",
        tcp: socket.constructTCPSocketInstance(),
        remoteip:'none',
        localip:'none',
        msg:"A NEW WORLD",
        udp:socket.constructUDPSocketInstance(),
    },
    onInit() {
      this.getIpAddress();
      this.creatScoket();
      this.title="暂无新消息"
    },
    //创建udpSocket 默认端口10006
    creatScoket: async function(){
        this.udp.bind({address: this.getIpAddress(), port: 10006, family: 1}, err => {
            if (err) {
                console.info('bind fail');
                this.title='bind fail'
                return;
            }
            this.title='bind success';
            console.info('bind success');
        })
//          this.tcp.bind({address: this.localip, port: 10006,family: 1}, err => {
//            if (err) {
//                console.log('bind fail');
//                this.title='bind fail';
//                return;
//            }
//            this.title='bind success';
//            console.log('bind success');
//        })
        //监听收到的信息 打印到屏幕上
       this.udp.on('message', value => {
           let buffer = value.message;
           let dataView = new DataView(buffer);
           let str = "";
           for (let i = 0;i < dataView.byteLength; ++i) {
               str += String.fromCharCode(dataView.getUint8(i))
           }
           this.title =str;
        });
    },
    sendMessage: async function(){
//        let promise1 = this.udp.connect({ address: {address: this.remoteip, port: 10006, family: 1} , timeout: 6000});
//        promise1.then(() => {
//            console.log('connect success');
//            let promise2 =this.udp.send({
//                data:this.msg
//            });
//            promise2.then(() => {
//                console.log('send success');
//            }).catch(err => {
//                console.log('send fail');
//            });
//        }).catch(err => {
//            this.title='connect fail';
//            console.log('connect fail');
//        });
        //发送信息
        let promise = this.udp.send({
            data:this.msg,
            address: {
                address:this.remoteip,
                port:10006,
                family:1
            }
        });
        promise.then(() => {
            this.title='send success';
            console.info('send success');
        }).catch(err => {
            this.title='send fail'
            console.info('send fail');
        });
    },
    newlocalip(e){
       this.localip = e.value;
        this.title = e.value;
    },
    newremoteip(e){
        this.remoteip = e.value;
    },
    newMessage(e){
        this.msg=e.value;
        this.title=e.value;
    },
    //获取本机ip地址
    getIpAddress(){
       let ip=wifi.getIpInfo().ipAddress;
       this.localip = (ip >> 24 & 0xFF)+"."+ ((ip >> 16) & 0xFF)+"."+((ip >> 8) & 0xFF)+"."+(ip & 0xFF);
    },
}
3.测试
这里为了便于测试,我们最好下载一个网络调试助手
网络调试助手
3.1结果
- OpenHarmony侧

 - HarmonyOS侧

 - OpenHarmony&HarmonyOS 联动
4 结语
实现了OpenHarmony与HarmonyOS的简单通讯后,应用开发,软硬件联动都有了更多的可能性。目前可能存在一些稳定性,不完整性问题,未来两侧基础功能不断完善之后,将会变得更有趣也更好用。如有明显错误,希望读者能够积极提出,我会尽力完善。
 
想了解更多关于开源的内容,请访问:
51CTO 开源基础软件社区
https://ost.51cto.com/#bkwz










