DatagramPacket简介
DatagramPacket是基于UDP协议的,另外还有一个类DatagramSocket,这两个类构成了一个网络链接,也即是不同于TCP协议的不可靠的网络链接.两个设备通信建立的步骤为:
1.通过DatagramSocket绑定本地和端口号(建立套接字);
2.使用DatagramPacket建立数据报;
3.通过DatagramSocket的send(datagrampacket)方法发送(到指定地址的端口);
4.在”1”指定的端口处通过DatagramSocket的receive(~)方法接受server端发送的反馈信息.
(关于DatagramSocket和DatagramPacket的详细使用请参考其他文章,比如:DatagramSocket使用详解)
项目中的使用
项目中涉及到通过局域网来控制一个带有两个电机的设备,和手机作为移动端的Client一样使用了Android的平台,并将其作为server端.按照DatagramSocket的基本用法写了demo之后,简单测试OK.引入项目中使用大致核心方法如下:
private String sendUDPCommand(String command){
DatagramSocket ds = null;
String str_receive = "";
byte[] data = new byte[128];
byte[] jsonData = new byte[128];
String ip = SoundBoxManager.getInstance().currentIP();
if (ip == null){
return "0";
}
try{
JSONObject json = new JSONObject();
json.put("name", "XiaoleClient");
json.put("Clientip", ip);
json.put("ClientContent", command);
json.put("wanted", "sendLocalControlCommand");
String jsonString = json.toString();
jsonData = jsonString.getBytes();
}catch (JSONException e){
e.printStackTrace();
}
try {
ds = BoxUDPBroadcaster.getSocketPort();
InetAddress loc = InetAddress.getByName("255.255.255.255");
DatagramPacket dp_send= new DatagramPacket(jsonData,jsonData.length,loc,21250);
DatagramPacket dp_receive = new DatagramPacket(data, 128);
ds.setSoTimeout(TIMEOUT);
int tries = 0;
boolean receivedResponse = false;
while(!receivedResponse && tries<MAXNUM){
ds.send(dp_send);
try{
ds.receive(dp_receive);
receivedResponse = true;
}catch(InterruptedIOException e){
tries += 1;
}
}
if(receivedResponse) {
str_receive = new String(dp_receive.getData(), 0, dp_receive.getLength());
Log.d("TIEJIANG", "BoxUDPBroadcaster---sendUDPCommand" + " str_receive= " + str_receive);
dp_receive.setLength(128);
}else{
str_receive = "0";
}
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// if (ds != null){
// ds.close();
// }
}
return str_receive;
}
代码当中的”TIMEOUT”为5000ms.server端在Activity启动之后就开启线程一直循环监听移动端发送的指令.核心类如下:
class UDPLocalCommandRunnable implements Runnable{
@Override
public void run() {
DatagramSocket ds = null;
try{
byte[] buf = new byte[256];
DatagramPacket dp_send = null;
ds = new DatagramSocket(21250);
DatagramPacket dp_receive = new DatagramPacket(buf, 256);
boolean f = true;
int jsonSignal = 0;
while(f){
ds.receive(dp_receive);
String str_receive = new String(dp_receive.getData(),0,dp_receive.getLength());
jsonSignal = analysisLocalCommandData(str_receive);
if (jsonSignal == 3){
String localCommandSend = handleJSON("commandSended", currentIP());
dp_send = new DatagramPacket(localCommandSend.getBytes(),localCommandSend.length(),dp_receive.getAddress(),21240);
}
ds.send(dp_send);
dp_receive.setLength(256);
}
}catch (IOException e){
e.printStackTrace();
Log.d("TIEJIANG", "StartActivity---UDPLocalCommandRunnable" + " IOException! restart Thread");
ds.close();
new Thread(new UDPLocalCommandRunnable()).start();
}finally {
if (ds != null){
ds.close();
}
}
}
}
在异常的catch块当代中,如果发生了,那么就采用粗暴的方式关掉当前的端口,再重新打开一次.上述线程类当中调用的解析指令方法为:
public int analysisLocalCommandData(String json_string){
String JSONString = json_string;
int jsonResult = 0;
if (JSONString == null){
return 0;
}
try{
JSONObject parseH3json = new JSONObject(JSONString);
String wanted = parseH3json.getString("wanted");
final String hostip = parseH3json.getString("Clientip");
String name = parseH3json.getString("name");
String clientContent =parseH3json.getString("ClientContent");
if(wanted.equals("sendLocalControlCommand") && name.equals("XiaoleClient") && hostip != null){
jsonResult = 3;
//解析指令(并通过串口向下发送)
analysisCommand(clientContent);
}
}catch (JSONException e){
e.printStackTrace();
Log.d("TIEJIANG", "JSONException");
}
return jsonResult;
}
上述代码很简单,json部分可能还有些问题,但是目前就打算这样使用了.不过问题就出现了.移动端发送控制指令到server时候,刚刚开始还OK,但如果连续发送的话,就无法发送成功.
后来分析,由于发送的指令的端口和握手信号的端口为了方便使用了同一个端口号(移动端会定时通过此端口号发送握手信号到server端口进行连接与否的判断,也是在此基础上进行局域网控制与否的判断).经过不断的调试后发现只要握手信号判断周期一到,则会出现指令发送失败,由于移动端发送的指令连续且频繁,因此这段时间之内要发送的信息都会失败,要等到第二个握手周期来到时候才能够一下子将阻塞的指令信息发送到server端,这个时候server就会突然收到一大堆信息.这直接导致了设备的难以控制.
在将移动端和server的指令发送和监听端口统一修改为一个新的且未占用的端口之后,测试了第一次似乎OK了,但是再次启动的时候发现情况和之前差不多,最后发现一个问题,基本上是在移动端连续发送指令5s左右就会发生指令发送失败的问题,然后阻塞,然后又是server端突然收到一堆指令.
总结
发现问题在于数据超时时间设置有问题.之前设置了超时时间TIMEOUT为5000ms,而DatagramSocket明显是UDP协议的链接.这本来是不稳定的链接,在这种使用情况之下,如果连续发送的指令在某一个指令(某一次发送)出现异常之后,也就是client移动端没有及时收到server的反馈指令,那么就会出现延时等待,而这个时候再次连续发送,则后续的数据报文要嘛阻塞,要嘛就直接丢失了,肯定是处于一种不可控的状态.因此在这里只能使用丢掉数据指令的方式来解决阻塞问题(反正数据是连续发送的,只要求server端能够连续收到就OK).