友情提示: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服务端长这样
client客户端长这样
客户端的ip处填写server第二行的ip
客户端代码
服务端代码
代码看得出,并不多,使用起来应该比较方便