最新消息:380元/半年,推荐全网最具性价比的一站式编程学习平台码丁实验室

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

App Inventor 少儿编程 4101浏览 0评论

友情提示:380元/半年,儿童学编程,就上码丁实验室


已上传GitHub,包括源码及aix,以及测试用aia,顺便贴个apk上去。
国内可以访问Gitee,来自开学后虚脱的我。。。

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服务端长这样
App Inventor插件开发(六)WiFi局域网socket通信
client客户端长这样
App Inventor插件开发(六)WiFi局域网socket通信
客户端的ip处填写server第二行的ip
客户端代码
App Inventor插件开发(六)WiFi局域网socket通信
服务端代码
App Inventor插件开发(六)WiFi局域网socket通信
代码看得出,并不多,使用起来应该比较方便

aia和aix及apk见GitHub

您必须 登录 才能发表评论!