在Android客户端和PC服务器端间传输自定义对象
导语
之前我们实现了Android客户端和PC服务器端之间的双向通信:
面向UDP的Android——PC双向通信(二):实现Android客户端和PC服务器端的双向通信。但仅仅是传输文本消息。接下来我们想要制定一个协议,来传输我们自定义类的对象。
这里我们考虑模拟订票系统,Android客户端向服务器端提交一个ticket对象,服务器端负责生成一些其他信息,再回传给Android端,实现一个订单的确认。
自定义ticket类
public class Ticket {
private String name; //购票者姓名
private String ID; //ID
private String SN; //序列号
private String StartStation;//起始站
private String EndStation;//终点站
private int Type; //车票类型
private String Date; //出发日期
private String TrainNumber; //车次
private String StartTime; //起始时刻
private String duration; //时长
private int vehicle; //车厢
private int seat; //座位号
private double price; //票价
//getter、setter方法
... ...
}
Java服务器端
服务器端在之前的代码基础上,增加了ticket对象转换为byte数组、byte数组转换为ticket对象的方法。
转换的依据就是我们所制定的协议。
我们暂定的协议如下:
从客户端接收的、即将被转换为ticket对象的byte数组的格式如下:
//属性: 姓名、ID、序列号、起始站、终点站、车票类型、出发日期、车次 | 总
//长度: 6 |6 |8 |8 |8 |4 |6 |6 | 52
/**
* byte数组转换为Ticket对象
* @param data
* @return
* @throws UnsupportedEncodingException
*/
public Ticket BytesToTicket(byte[] data) throws UnsupportedEncodingException{
Ticket t = new Ticket();
t.setName(BytesToString(subBytes(data,0,6)));
t.setID(BytesToString(subBytes(data,6,6)));
t.setSN(BytesToString(subBytes(data,12,8)));
t.setStartStation(BytesToString(subBytes(data,20,8)));
t.setEndStation(BytesToString(subBytes(data,28,8)));
t.setType(BytesToInt(subBytes(data,36,4)));
t.setDate(BytesToString(subBytes(data,40,6)));
t.setTrainNumber(BytesToString(subBytes(data,46,6)));
System.out.println(t.toString());
return t;
}
在接收到的ticket对象基础上,服务器生成起始时刻、时长、票价,分配车厢、座位号,再转换为byte数组:
//属性: 姓名、ID、序列号、起始站、终点站、车票类型、出发日期、车次 、起始时刻、时长、车厢、座位号、票价 |总
//长度: 6 |6 |8 |8 |8 |4 |6 |6 |4 |4 |4 |4 |8 |76
/**
* 把ticket对象转换为要发送的byte数组
* @param t
* @return
* @throws Exception
*/
public byte[] TicketToBytes(Ticket t) throws Exception{
byte[] data=new byte[76];
addBytes(StringToBytes(t.getName(),6),data,0,6);
addBytes(StringToBytes(t.getID(),6),data,6,6);
addBytes(StringToBytes(t.getSN(),8),data,12,8);
addBytes(StringToBytes(t.getStartStation(),8),data,20,8);
addBytes(StringToBytes(t.getEndStation(),8),data,28,8);
addBytes(IntToBytes(t.getType()),data,36,4);
addBytes(StringToBytes(t.getDate(),6),data,40,6);
addBytes(StringToBytes(t.getTrainNumber(),6),data,46,6);
addBytes(StringToBytes(t.getStartTime(),4),data,52,4);
addBytes(StringToBytes(t.getDuration(),4),data,56,4);
addBytes(IntToBytes(t.getVehicle()),data,60,4);
addBytes(IntToBytes(t.getSeat()),data,64,4);
addBytes(DoubleToBytes(t.getPrice()),data,68,8);
return data;
}
再增加一些基础数据类型之间的转换就可以了,服务器端没有遇到什么问题。
Android客户端
在Android端,ticket对象和byte数组之间的相互转换恰好与Java端相反,难度不大,这里不加赘述。
但是我们遇到了其他的问题。
之前实现双向通信仅仅是在MainActivity中进行消息收发。
而如今我们写了两个界面,在第一个界面MainActivity中填写购票信息,并点击提交,跳转第二个界面ReceiveActivity等待确认购票成功的信息。如果想要再次提交新的购票信息,则需要返回第一个界面填写购票信息。
但是重新创建第二个界面会重复执行代码:
receivesocket = new DatagramSocket(9999);
肯定会报错,因为之前的socket连接没有关闭,端口9999被占用。
如何解决socket在不同界面(前后创建ReceiveActivity的代码虽然相同,但实际上是不同的两个界面)之间共享的问题呢?
我们采用单例模式,即使用静态socket对象,这样DatagramSocket在该应用程序中只有一个实例。
在MainActivity中增加代码:
//静态变量
public static DatagramSocket receivesocket=null;
//静态方法
public static DatagramSocket getsocket() throws Exception {
if(receivesocket==null){
receivesocket= new DatagramSocket(9999);
}
return receivesocket;
}
并在ReceiveActivity中更改代码:
receivesocket = MainActivity.getsocket();
至此,我们可以实现一个或多个客户端的购票提交申请,以及接收来自服务器端对应的确认购票成功的消息。但是丢包仍然是比较常见。
示例代码
服务器端代码
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;
public class UDPServer {
public JFrame frame;
public JLabel IPShow;
public JLabel PortShow;
public JTextArea MsgReceive;
public JTextArea MsgSend;
public JButton SendBtn