首先我們要知道UDP通信協(xié)議是Socket通信的一種實現(xiàn)方式,Socket通信一般有兩種通信方式:基于TCP協(xié)議、基于UDP協(xié)議。這兩者的差別和優(yōu)缺點就不說了,這里主要講一下基于UDP協(xié)議的實現(xiàn)。
基本原理
基于UDP的通信都是通過java.net.DatagramSocket這個類來實現(xiàn)的,我們常用的有connect()、disConnect()、send()、receive()幾個方法。通過方法名我們可以輕易區(qū)分出用法。另一個需要知道的就是DatagramPacket類,如果把DatagramSocket比作管道的話,DatagramPacket就是管道中運輸?shù)奈锲?,也就是說,它就是數(shù)據(jù)的載體。
DatagramSocket:
public DatagramSocket()throws SocketException {
this(0);
}
public DatagramSocket(int aPort)throws SocketException {
checkPort(aPort);
createSocket(aPort, Inet4Address.ANY);
}
public DatagramSocket(int aPort, InetAddress addr)throws SocketException {
checkPort(aPort);
createSocket(aPort, (addr == null) ? Inet4Address.ANY : addr);
}
三個構(gòu)造方法,我們可以指定端口和IP,也可以不指定,在發(fā)送數(shù)據(jù)的時候,在數(shù)據(jù)包中指定。
DatagramPacket:
public DatagramPacket(byte[]data, int length) {…}
public DatagramPacket(byte[]data, int offset, int length) {…}
public DatagramPacket(byte[]data, int offset, int length, InetAddress host, int aPort) {…}
public DatagramPacket(byte[]data, int length, InetAddress host, int port) {…}
so 我們可以看到可以在數(shù)據(jù)包中設(shè)置ip和端口,所以如果連接時不指定也是可以的。
基本操作
1、connect new出一個DatagramSocket對象,設(shè)置端口和IP,connect();
2、send 創(chuàng)建一個DatagramPacket對象,socket.send(packet)發(fā)送數(shù)據(jù);
3、receive 創(chuàng)建一個DatagramPacket對象,socket.receive(package)接收數(shù)據(jù);
創(chuàng)建一個連接
使用connect方法創(chuàng)建一個Socket連接
public void connect() {
if (mSocket == null || mSocket.isClosed()) {
try {
//獲取連接 ip:192.168.1.3 port:11069
InetAddress address = InetAddress.getByName(Command.udp_address);
mSocket = new DatagramSocket();
mSocket.connect(address, Command.udp_port);
messageQueue = new ArrayList < String > ();
//開啟接收線程
mReceiveThread = new ReceiveThread();
mReceiveThread.start();
} catch (SocketException e) {
e.printStackTrace();
}
}
}
定時發(fā)送數(shù)據(jù)保持通信
要保持通信的話,肯定要用到線程了,定時發(fā)送數(shù)據(jù)的話,可以用Handler,或者也可以使用AlarmManager。這里使用Handler實現(xiàn)就好了。
寫的時候看了一堆博客,發(fā)送的時候都是new 出一個Thread進行發(fā)送數(shù)據(jù),發(fā)送之后寫接收數(shù)據(jù)的代碼,而且沒有進行回收或者怎樣,只能最后通過GC回收,當(dāng)然大體上看是沒毛病的,但是其實有很大的問題!因為我項目要求收到服務(wù)器的應(yīng)答之后做一些相應(yīng)的操作,,然后,然后,,因為每一個線程都在等待著接收數(shù)據(jù),所以服務(wù)端回復(fù)一條數(shù)據(jù)之后,這邊每個線程都作出了回應(yīng)(回復(fù)服務(wù)器一條不一樣的數(shù)據(jù)),10s創(chuàng)建一個線程發(fā)送數(shù)據(jù),服務(wù)器過了一段時間再回復(fù),然后就瞬間爆炸!
所以,,接收和發(fā)送應(yīng)該是要分開的,不能阻塞在同一個線程中。一開始想到Handler的原理和機制,想模仿它死循環(huán)從一個隊列中取數(shù)據(jù),如果有數(shù)據(jù)就發(fā)送,想法是沒毛病,也能發(fā)送,但是當(dāng)我想在發(fā)送成功之后從列表中remove掉這條數(shù)據(jù)之后就不行了,線程阻塞了。具體原因應(yīng)該是造成死鎖了,因為數(shù)據(jù)列表是定義在主線程,然后一直在被工作線程占用著,獲取不到對象進行remove操作,等待一定時間后,直接crash掉了(我的分析是這樣的)。
ok,那就寫兩個線程好了,一個SendThread,一個ReceiveThread,另外通過Handler控制10s定時發(fā)送。
發(fā)送線程:
public class SendThread extends Thread {
@ Override
public void run() {
super.run();
try {
if (mSocket == null || mSocket.isClosed())
return;
if (messageQueue.size() < 1)
return;
//發(fā)送
final String data = messageQueue.get(0);
byte[]datas = data.getBytes();
InetAddress address = InetAddress.getByName(Command.udp_address);
final DatagramPacket packet = new DatagramPacket(datas, datas.length, address, Command.udp_port);
mSocket.send(packet);
Logs.e("ConnectManager", "send success data is:" + data);
messageQueue.remove(0);
} catch (UnknownHostException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
接收線程:
private class ReceiveThread extends Thread {
@ Override
public void run() {
super.run();
if (mSocket == null || mSocket.isClosed())
return;
try {
byte datas[] = new byte[512];
DatagramPacket packet = new DatagramPacket(datas, datas.length, address, Command.udp_port);
mSocket.receive(packet);
String receiveMsg = new String(packet.getData()).trim();
Logs.e("ConnectManager", "receive msg data is:" + receiveMsg);
mHandler.sendEmptyMessage(2);
} catch (IOException e) {
e.printStackTrace();
}
}
}
我們可以看到ReceiveThread中只接收了一次數(shù)據(jù),那如何實現(xiàn)一直保持通信?接收消息?Handler!接收到消息之后我們通過Handler發(fā)送了一條消息,這條消息干嘛的呢,重啟這個線程,也就是讓它繼續(xù)接收數(shù)據(jù)。 這樣的話,就保證了只有一個線程在接收數(shù)據(jù)。
private Handler mHandler = new Handler() {
@ Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 2) {
mReceiveThread.interrupt();
mReceiveThread = null;
mReceiveThread = new ReceiveThread();
mReceiveThread.start();
}
}
};
同樣,定時發(fā)送數(shù)據(jù)也是通過Handler實現(xiàn)的,每10s發(fā)送一個消息,讓socket發(fā)送一個數(shù)據(jù)包,并且給自己發(fā)送一個10s后發(fā)送的同樣的消息,反正就是自己控幾自己啦。
發(fā)送消息:
public void sendPackageRegister() {
String content = "(1001," + Command.deviceId + ",register)";
messageQueue.add(content);
mSendThread.interrupt();
mSendThread = null;
mSendThread = new SendThread();
mSendThread.start();
}
最后,記得停掉Handler一直發(fā)送數(shù)據(jù),停掉線程。