App Inventor插件开发(六)WiFi局域网socket通信

已上传GitHub,包括源码及aix,以及测试用aia,顺便贴个apk上去。

0.前言

Wifi下通信应该是大家都希望的吧,网上TaifunWiFi似乎很高级的样子,但是原谅我英语渣,看不懂。。。
mac和ip我懂,可是ssid是神马。。。看来我tcp/ip还没啃到家。。。
TaifunWiFi盯了半天似乎好像大概可能应该差不多是怎么去连接WiFi而不是通信的。
好像很多人的需求是和arduino有关,谁能来介绍一下,不懂
Android下很多人都讲了怎么用socket通信,可是移植到app inventor上的很少。
那就让我来开心的皮一下,介绍一下怎么在局域网内通信,不止WiFi哦。

1.解决步骤

1.1原理

我就不扯tcp/ip了,估计也没人有心思听。
这里只是讲一些基础知识,因为这个插件目前只能传字符串,如果要更多功能就需要自行补充了。
两台计算机间进行通讯需要以下三个条件:IP地址、协议、端口号。
IP地址肯定听说过,用人话说就像一个港口。
端口号用于区分一台主机的多个不同应用程序,范围为0-65535,我取8000,0-1023为为系统保留。用人话讲就像港口有很多船位,可以同时停很多条船。
Socket由IP地址+端口号组成。在Java中是使用TCP协议实现的网络通信。
ServerSocket是服务端。
因为网络连接是一个非常耗时的操作,比读写硬盘还耗时,需要单独一个线程,甚至不止一个。

1.2测试时bug记录

Client客户端状态良好,可发,收等会再说
Sever服务端莫名智障,打电话也不接,发短信也不回,最后发现是message是直接new的,要从myHandler.obtainMessage()获取才有用,坑死我了。。。
又被这个message坑了一回,每次sendMessage都要重新获取message一回,或者相邻几行不用,我也没搞懂,反正每次sendMessage都获取一遍问题就解决了,不然闪退,连个报错都没有,连接adb才得到错误信息,坑啊
Sever服务端测试成功!
等我重写一下,达到能够使用的程度。
又是一个坑,OutputStream的flush()是个空方法,只能再建一个BufferedOutputStream。
readline结尾要加\n!不是OutputStream的问题,这是装饰模式,空方法就是用于覆盖。
测试完成,鉴定可食用。

1.3服务端源码讲解

全文见SocketUtil.java
收到消息的回调,顺便加了个回车

@SimpleEvent public void GetMessage(String s){
      EventDispatcher.dispatchEvent(this, "GetMessage", "\n"+s);
}

handler用于从子进程返回UI线程,并调用回调。
如果你需要区分不同的消息的话,设置what,在handler里if-else或switch都可以。

public Handler handler = new Handler(){

    @Override public void handleMessage(Message msg) {
        /*switch(msg.what){
            case 1:...break;
        }*/
        GetMessage(msg.obj.toString());
    }
};
/*Message message_1 = handler.obtainMessage();
message_1.what= 11;
message_1.obj = "";
handler.sendMessage(message_1);

获得ip以及port的代码

String ip;
int port;
private ServerSocket serverSocket = null;

public void getLocalIpAddress(ServerSocket serverSocket){

  try {
     for (Enumeration<NetworkInterface> en=NetworkInterface.getNetworkInterfaces();en.hasMoreElements();){
            NetworkInterface intf = en.nextElement();
            for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses();enumIpAddr.hasMoreElements();){
                InetAddress inetAddress = enumIpAddr.nextElement();
                String mIP = inetAddress.getHostAddress().substring(0, 3);
                if(mIP.equals("192")){
                    ip = inetAddress.getHostAddress();    //获取本地IP
                    port = serverSocket.getLocalPort();
                }
            }
        }
  } catch (SocketException e) {}
}

内部类,用于读取数据直到换行符,就是新的设备连接时的消息处理类

class ServerThread extends Thread{

        Socket socket;
        Message message_2;


        public ServerThread(Socket socket){
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                BufferedReader br = null;
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while(true){
                    String msg = null;
                    msg = br.readLine();
                    if(msg != null){
                        message_2 = handler.obtainMessage();
                        message_2.obj = socket.getInetAddress().getHostAddress()+":"+msg;//把ip和冒号拼到消息上
                        handler.sendMessage(message_2);
                    }
                }
            } catch (IOException e) {
                message_2 = handler.obtainMessage();
                message_2.obj = "他好像不见了";
                handler.sendMessage(message_2);
                try{socket.close();}catch(Exception e1){}
            }
        }
    }

启动服务的方法

@SimpleFunction public void receiveData(){
    Thread thread = new Thread(){
        @Override
        public void run() {
            //..
        }
    };
    thread.start();
}

端口8000,初始化ip和port,发送一个message

try {
    serverSocket = new ServerSocket(8000);
} catch (IOException e) {
    e.printStackTrace();
}
getLocalIpAddress(serverSocket);

Message message_1 = handler.obtainMessage();
message_1.obj = "IP:" + ip + " PORT: " + port;
handler.sendMessage(message_1);

如果有新的设备连接,新建一个线程用于接收

while (true){
    Socket socket = null;
    try {
        socket = serverSocket.accept();//如果没有设备连接会阻塞在这一句
        //如果想控制连接数就把while改成for,然后计数即可
        Message message_2 = handler.obtainMessage();
        message_2.obj = "有兄弟连上了!"+socket.getInetAddress().getHostAddress();
        handler.sendMessage(message_2);
     catch (IOException e) {}
    new ServerThread(socket).start();
}

1.4客户端源码讲解

全文见SocketClient.java
handle等服务端出现的就不再叙述
这仨函数也没必要介绍了,一看就明白,重点在于那个线程

Socket socket = null;
MyThread mt;
final int CONNECT = 100001;
final int SENDMESSAGE = 100002;
final int CLOSE = 100003;
@SimpleFunction(description = "start")
//关闭链接
public void closeConnect(){
    if(socket != null){
        mt = new MyThread(CLOSE);
        mt.start();
    }else{
        GetMessage("连接未创建!");
    }
}
//发送消息
@SimpleFunction(description = "start")
public void sendMessage(String s){
    if(socket != null){
        mt = new MyThread(SENDMESSAGE);
        mt.setText(s);
        mt.start();
    }else{
        GetMessage("连接未创建!");
    }
}
//连接
@SimpleFunction(description = "start")
public void connect(String ip){
    if(socket == null){
        mt = new MyThread(CONNECT);
        mt.setIP(ip);
        mt.start();
    }else{
        GetMessage("连接已创建!");
    }
}

额因为懒得写三个类所以合起来了

class MyThread extends Thread {
        public String txt1;
        public String IP;
        Message msg;
        public int flag;
        public MyThread(int flag) {this.flag = flag; }
        public void setText(String s){txt1 = s;}
        public void setIP(String ip){IP = ip;}
        @Override
        public void run() {
            switch(flag){
                case CONNECT:
                    //连接...
                break;
                case SENDMESSAGE
                    //发送...
                break;
                case CLOSE:
                    //关闭...
                break;
            }
        }
    }

连接

case CONNECT:
    try {
        socket = new Socket();
        msg = myHandler.obtainMessage();
        msg.obj = "开始连接";
        myHandler.sendMessage(msg);
        socket.connect(new InetSocketAddress(IP, 8000), 1000);//端口8000,超时1000ms
        ou = socket.getOutputStream();//获取输出流
        msg = myHandler.obtainMessage();
        msg.obj = "连接成功";
        myHandler.sendMessage(msg);
    } catch (SocketTimeoutException aa) {
        msg = myHandler.obtainMessage();
        msg.obj = "连接超时";
        myHandler.sendMessage(msg);
        socket = null;
    } catch (IOException e) {
        msg = myHandler.obtainMessage();
        msg.obj = "未知错误";
        myHandler.sendMessage(msg);
        socket = null;
    }
break;

发信

case SENDMESSAGE:
    try {
        ou.write(txt1.getBytes("utf-8"));//用utf8输出字符串,一定要化成字节流
        ou.write("\n".getBytes("utf-8"));//因为是读取一行,一行结束了要加换行
        ou.flush();//清空缓冲区
        msg = myHandler.obtainMessage();
        msg.obj = "发送完毕";
        myHandler.sendMessage(msg);
    }catch (IOException e) {
        msg = myHandler.obtainMessage();
        msg.obj = "未知错误";
        myHandler.sendMessage(msg);
    }
break;

关闭

case CLOSE:
    try {
        ou.close();
        socket.close();
        socket = null;
        msg = myHandler.obtainMessage();
        msg.obj = "关闭";
        myHandler.sendMessage(msg);
    }catch (IOException e) {
        msg = myHandler.obtainMessage();
        msg.obj = "未知错误";
        myHandler.sendMessage(msg);
    }
break;

2.先来几张测试图

难得我终于发测试图了。。。
不过一个问题,就是要测试的话需要两个手机,我翻箱倒柜找到了一个特别卡的旧手机,所以截图只能在那个比较新的手机上完成
所以我用那个新手机分别做了一次client和server,分别截图
还有,server只能打开一次,第二次会闪退,要彻底关掉才能打开第二次
发现一个很神奇的事,电脑热点共享后竟然可以直连,但只能单向
server服务端长这样
这里写图片描述
client客户端长这样
这里写图片描述
客户端的ip处填写server第二行的ip
客户端代码
这里写图片描述
服务端代码
这里写图片描述
代码看得出,并不多,使用起来应该比较方便

aia和aix及apk见GitHub

猜你喜欢

转载自blog.csdn.net/aiw_prton/article/details/82015735