最近做Android客户端与物联网通信,总结一下.
本文将给出一种比较实用的框架,一个线程负责读取服务器的消息,一个线程向服务器发送消息。然后在一些常见疑惑上给出解释和一些其他值得一看的资料。
一、认识socket
什么是socket,在移动端我们通常使用http来访问服务器,因为http为弱连网(手机你懂得,要是2哥的网,哎……),http和socket的最大区别就是http为一次性请求,客户端请求一次服务器应答一次,而socket将保持与服务器的链接。socket多应用于强连网的情况下,比如聊天室等等。关于socket的详细讲解可以参见http://blog.csdn.net/siobhan/article/details/4277380,有比较详细的讲解,在此不再赘述。
二、利用socket进行通信
socket名称为套接字即IP+端口号,什么是端口?可以这么理解IP是网络上唯一识别你电脑的id,而端口是在你电脑上识别应用的id。所以在创建是有Socket s = new Socket("192.168.1.1",3000);创建一个socket信道,当你拿到socket的对象后便可以得到IO流进行数据的读写操作了。这里需要说明和将解的地方是,socket的数据传输是终端与服务端商定好的,在传输接收数据时,按商量好的数据形式读写,如你传一个string过来,我接收时也转换为string。下面程序为socket数据的线程类。
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
//此类实现Runable接口,实现run方法,注意在run方法中又起了一个线程
public class SocketThread implements Runnable {
public Handler revUIHandler; // 接受UI消息的handler
private Handler handler; //接收到服务器的消息后向UI发送消息的handler
OutputStream os;
InputStream in;
public SocketThread(Handler handler){
this.handler = handler;
}
@Override
public void run() {
try { //此处可以定义一个常量IP,和端口,以便更改
Socket socket = new Socket(Data.IP,Data.PORT);
os = socket.getOutputStream();
//br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
in = socket.getInputStream();
new Thread(){
@Override
public void run() {
byte[] data = new byte[1024];
int byteread = 0;
try {
while((byteread = in.read(data))!=-1){
Message msg = new Message();
msg.what=0x123;
msg.obj = data;
handler.sendMessage(msg);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();
//子线程handler的处理,固定形式------------
Looper.prepare();
revUIHandler = new Handler(){
public void handleMessage(Message msg) {
if(msg.what ==0x456){
try {
os.write((byte[])msg.obj);
} catch (IOException e) {
e.printStackTrace();
}
}
};
};
Looper.loop();
<span style="white-space:pre"> </span>//---------------------------------------
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
上面程序中的run方法中又新开了一个线程,这个线程用于一直接收服务器的消息。这里的初学者容易费解的是while()方法,难道该线程在一直死循环?或者该线程读取一次数据while就跳出了,线程也就结束了,哪来一直接收服务器消息呢? 这里要明确的是read()方法的作用,read为阻塞读,也就是在没有检查到服务器发来数据时read方法将阻塞该线程,而有消息时将一次读取data数组这么长的数据,并执行一次while循环。由此可以看出read方法的使用必须放在子线程中。java文件读写可以参见http://blog.csdn.net/smartcat86/article/details/4085739/。当然有人会问这样的线程该如何结束,非阻塞socket编程http://blog.csdn.net/chjttony/article/details/7181427。
下面是UI线程类
public class MainActivity extends Activity {
byte[] data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what==0x123){
data = (byte[])msg.obj;
}
}
};
SocketThread socketThread = new SocketThread(handler);
new Thread(socketThread).start(); //可以使用socketThread.revUIHandler 来向服务器发送消息
}
}
三、关于数据的转换
有时我们规定传输的数据中有几个字节的头,而我们在收发消息时通常用byte数组来传输,这时就存在数据的转换问题,如int如何转换为byte数组:
/**
* int值转成4字节的byte数组
* @param num
* @return
*/
public static byte[] int2byteArray(int num) {
byte[] result = new byte[4];
result[0] = (byte)(num >>> 24);//取最高8位放到0下标
result[1] = (byte)(num >>> 16);//取次高8为放到1下标
result[2] = (byte)(num >>> 8); //取次低8位放到2下标
result[3] = (byte)(num ); //取最低8位放到3下标
return result;
} <pre name="code" class="java">/**
* 将4字节的byte数组转成int值
* @param b
* @return
*/
public static int toInt(byte[] bRefArr) { //byte数组转换为int
int iOutcome = 0;
byte bLoop;
for (int i = 0; i < bRefArr.length; i++) {
bLoop = bRefArr[i];
iOutcome += (bLoop & 0xFF) << (8 * i);
}
return iOutcome;
}
在程序中要转换的地方调用此静态方法即可。
上述如有错误还请各位看官不吝赐教。