在Android客户端和PC服务器端间传输自定义对象

    • 导语
    • 自定义ticket类
    • Java服务器端
    • Android客户端
    • 示例代码
      • 服务器端代码
      • 客户端代码

导语

之前我们实现了Android客户端和PC服务器端之间的双向通信:
面向UDP的Android——PC双向通信(二):实现Android客户端和PC服务器端的双向通信。但仅仅是传输文本消息。接下来我们想要制定一个协议,来传输我们自定义类的对象。

这里我们考虑模拟订票系统,Android客户端向服务器端提交一个ticket对象,服务器端负责生成一些其他信息,再回传给Android端,实现一个订单的确认。

自定义ticket类

public class Ticket {private String name;      //购票者姓名private String ID;           //IDprivate 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;public InetAddress ClientAddress;public String AddressStr;public int ClientPort;public DatagramSocket socket;public ArrayList<Ticket> tickets = new ArrayList<>();public int cnt=0;//发送端口号public int port=9999;public static void main(String[] args) throws Exception{UDPServer server = new UDPServer();server.showUI();server.receiveMsg();//String s="北京欢迎你!";//byte[] test1 = StringToBytes(s,10);//String test2 = new String(test1);//String s1 = BytesToString(test1);//System.out.println("test1:  "+test1.toString());//System.out.println("test2:  "+test2);//System.out.println(s1);}/** * 字符串转换成大小为len的byte数组,并在末尾补占位符‘-’ * @param str字符串 * @param lenbyte数组长度 * @return补全后的byte数组 * @throws Exception */public static  byte[] StringToBytes(String str,int len) throws Exception{byte[] newData = new byte[len];byte[] oldData = str.getBytes("GBK");for(int i=0;i<oldData.length&&i<len;i++){newData[i]=oldData[i];}for(int i=oldData.length;i<len;i++){newData[i]=(byte) '-';}return newData;}/** * byte数组转换为字符串,并删除末尾占位符‘-’ * @param oldData含占位符的byte数组 * @return新字符串 * @throws UnsupportedEncodingException */public static String BytesToString(byte[] oldData) throws UnsupportedEncodingException{String newData = new String(oldData,"GBK");if(newData.contains("-")){newData = newData.substring(0, newData.indexOf('-'));}return newData;}/** * 从byte数组中分割出小的byte数组 * @param oldData原byte数组 * @param start切割点的起点 * @param len切割的长度 * @return新byte数组 */public byte[] subBytes(byte[] oldData,int start,int len){byte[] newData = new byte[len];for(int i=0;i<len;i++){newData[i]=oldData[i+start];}return newData;}//姓名、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;}/** * 4位byte数组转化为int型数据 * @param b4位byte数组 * @returnint型数据 */public static int BytesToInt(byte[] b){return b[3]&0xFF|(b[2]&0xFF)<<8|(b[1]&0xFF)<<16|(b[0]&0xFF)<<24;}public void showUI(){frame = new JFrame("UDP Server");frame.setSize(800, 700);frame.setLocationRelativeTo(null);frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setLayout(new FlowLayout());JLabel IPLabel = new JLabel("    源IP:");IPLabel.setPreferredSize(new Dimension(80,30));IPShow = new JLabel("__________________");IPShow.setPreferredSize(new Dimension(300,30));JLabel PortLabel = new JLabel("端口:");PortLabel.setPreferredSize(new Dimension(80,30));PortShow = new JLabel("__________________");PortShow.setPreferredSize(new Dimension(300,30));JLabel RecvLabel = new JLabel("接收到消息:");RecvLabel.setPreferredSize(new Dimension(700,30));MsgReceive = new JTextArea();MsgReceive.setPreferredSize(new Dimension(750,250));JLabel SendLabel = new JLabel("待发送消息:");RecvLabel.setPreferredSize(new Dimension(700,30));MsgSend = new JTextArea();MsgSend.setPreferredSize(new Dimension(750,250));SendBtn = new JButton("发送");SendBtn.setPreferredSize(new Dimension(80,50));SendBtn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {try {sendMsg();//发送消息} catch (Exception e1) {e1.printStackTrace();}}});frame.add(IPLabel);//源IPframe.add(IPShow);frame.add(PortLabel);//端口frame.add(PortShow);frame.add(RecvLabel);//接收到消息:frame.add(MsgReceive);frame.add(SendLabel);//待发送消息:frame.add(MsgSend);frame.add(SendBtn);//发送按钮frame.setVisible(true);}/** * 接收消息,转化为ticket对象,并添加到tickets队列中 * @throws Exception */public void receiveMsg() throws Exception{System.out.println("UDPServer start...");socket = new DatagramSocket(9999);new Thread(){public void run(){while(true){byte[] data = new byte[52];DatagramPacket request = new DatagramPacket(data, 52);//byte[] b=new byte[4];//port=BytesToInt(addBytes());System.out.println("准备接收消息");try {socket.receive(request);System.out.println("消息接收完毕");String test = new String(data);System.out.println("---TEST--接收到的byte数组为:"+test);//将Ticket对象添加到tickets队列中tickets.add(BytesToTicket(data));cnt++;ClientAddress=request.getAddress();IPShow.setText(ClientAddress.toString());System.out.println("客户机IP地址:"+IPShow.getText());ClientPort=request.getPort();PortShow.setText(""+ClientPort);System.out.println("客户机端口:"+PortShow.getText());String s = new String(data,"GBK");System.out.println("收到新消息:"+s+"\n"+s.length());//MsgReceive.setText(s);MsgReceive.setText(tickets.get(cnt-1).toString());} catch (IOException e) {e.printStackTrace();}}}}.start();}///**// * 发送消息// * @throws Exception// *///public void sendMsg() throws Exception{//System.out.println("准备发送消息");//String Msg = MsgSend.getText();//System.out.println("要发送的消息是:  "+Msg);//byte data[] = Msg.getBytes("GBK");//DatagramPacket request =//        new DatagramPacket(data,data.length,ClientAddress,9999);//socket.send(request);//System.out.println("发送成功");//}/** * 将byte数组添加到大的byte数组中 * @param myData要复制 的byte数组 * @param oldData目的byte数组 * @param start插入点的起点 * @param len插入的长度 * @return新byte数组 */public byte[] addBytes(byte[] myData,byte[] oldData,int start,int len){for(int i=0;i<len;i++){oldData[i+start]=myData[i];}return oldData;}//姓名、 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;}/** * 将int型数据转换成byte数组 * @param a * @return */public byte[] IntToBytes(int a) {byte[] b = new byte[4];b[0] = (byte)((a>>24)&0xFF);b[1] = (byte)((a>>16)&0xFF);b[2] = (byte)((a>>8)&0xFF);b[3] = (byte)(a&0xFF);return b;}public byte[] DoubleToBytes(double d){long value = Double.doubleToLongBits(d);byte[] b = new byte[8];for(int i=0;i<8;i++){b[i]=(byte)((value>>8*i)&0xff);}return b;}/** * 生成起始时刻、时长、车厢、座位、票价 * @param t */public void handleTicket(Ticket t){Random r = new Random();t.setStartTime("1258");t.setDuration("0215");t.setVehicle(r.nextInt(16)+1);t.setSeat(r.nextInt(100)+1);t.setPrice(12.5);System.out.println(t.toString());}/** * 发送ticket对象 * @throws Exception */public void sendMsg() throws Exception{System.out.println("准备发送消息");Ticket t = tickets.get(cnt-1);handleTicket(t);MsgSend.setText(t.toString());byte[] data = TicketToBytes(t);//DatagramSocket sendSocket=new DatagramSocket(port);DatagramPacket request =        new DatagramPacket(data,data.length,ClientAddress,port);socket.send(request);System.out.println("发送成功");}}

客户端代码

//MainActivity.javaimport java.net.DatagramSocket;import android.app.Activity;import android.os.Bundle;import android.view.Menu;import android.widget.Button;import android.widget.EditText;public class MainActivity extends Activity {public static DatagramSocket receivesocket=null;public static DatagramSocket getsocket() throws Exception {        if(receivesocket==null){        receivesocket= new DatagramSocket(9999);        }        return  receivesocket;    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        EditText name=(EditText)this.findViewById(R.id.nametext);        EditText ID=(EditText)this.findViewById(R.id.IDtext);        EditText start=(EditText)this.findViewById(R.id.starttext);        EditText end=(EditText)this.findViewById(R.id.endtext);        EditText type=(EditText)this.findViewById(R.id.typetext);        EditText date=(EditText)this.findViewById(R.id.datetext);        EditText number=(EditText)this.findViewById(R.id.numbertext);        Button submit=(Button)this.findViewById(R.id.submit);                ButtonListener bl=new ButtonListener(this,name,ID,start,end,type,date,number);        submit.setOnClickListener(bl);            }    @Override    public boolean onCreateOptionsMenu(Menu menu) {        // Inflate the menu; this adds items to the action bar if it is present.        getMenuInflater().inflate(R.menu.main, menu);        return true;    }    }
//ReceiveActivity.javapackage com.example.udpsend;import java.io.UnsupportedEncodingException;import java.net.DatagramPacket;import java.net.DatagramSocket;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.Menu;import android.widget.TextView;public class ReceiveActivity extends Activity {public DatagramSocket receivesocket;public byte[] msg;public TextView show;public int port;public byte[] value;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_receive);show=(TextView)this.findViewById(R.id.show);new Thread(){        public void run(){         try {         receivesocket=MainActivity.getsocket();         //receivesocket = new DatagramSocket(9999);         while(true){        receive();        }} catch (Exception e) {e.printStackTrace();}        }       }.start();}public void receive() throws Exception {byte[] data = new byte[76];DatagramPacket receivepacket = new DatagramPacket(data, 76);receivesocket.receive(receivepacket);//receivesocket.close();Ticket tiket=rechange(data);String s=showsetText(tiket);Message msg=hand.obtainMessage();msg.obj =s;hand.sendMessage(msg);}public Handler hand=new Handler(){ public void handleMessage(Message msg) { String s=(String)msg.obj; show.setText(s);    }};public String showsetText(Ticket tiket){String s=tiket.getName()+"¶©Æ±³É¹¦£¡"+"\nÏêϸÐÅÏ¢ÈçÏ£º\nÐÕÃû£º"+tiket.getName()+"\nID£º"+tiket.getID()+"\nÈÕÆÚ£º"+tiket.getDate()+" "+tiket.getStartTime()+"\n´Ó£º"+tiket.getStartStation()+"³ö·¢µ½"+tiket.getEndStation()+"\nʱ³¤£º"+tiket.getDuration()+"\n³µÏáºÅ£º"+tiket.getVehicle()+" ×ùλºÅ£º"+tiket.getSeat()+"\nƱ¼Û£º"+tiket.getPrice()+"\n\n´ËÏûÏ¢ÐòÁкţº"+tiket.getSN(); return s;}public byte[] convert(int start,int l,byte[] msg){byte[] c=new byte[l];for(int i=start;i<start+l;i++)c[i-start]=msg[i];return c;}public Ticket rechange(byte[] msg) throws UnsupportedEncodingException{String name=BytesToString(convert(0,6,msg));String ID=BytesToString(convert(6,6,msg));String SN=BytesToString(convert(12,8,msg));String StartStation=BytesToString(convert(20,8,msg));String EndStation=BytesToString(convert(28,8,msg));int Type=byteArrayToInt(convert(36,4,msg));String Date=BytesToString(convert(40,6,msg));String TrainNumber=BytesToString(convert(46,6,msg));String StartTime=BytesToString(convert(52,4,msg));String duration=BytesToString(convert(56,4,msg));int vehicle=byteArrayToInt(convert(60,4,msg));int seat=byteArrayToInt(convert(64,4,msg));double price=bytes2Double(convert(68,8,msg));      Ticket tiket=new Ticket(name,ID,SN,StartStation,EndStation,Type,Date,TrainNumber,StartTime,duration,vehicle,seat,price);return tiket;}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.receive, menu);return true;}public static int byteArrayToInt(byte[] b) {   return   b[3] & 0xFF |               (b[2] & 0xFF) << 8 |               (b[1] & 0xFF) << 16 |               (b[0] & 0xFF) << 24;   }   public static String BytesToString(byte[] oldData) throws UnsupportedEncodingException{String newData = new String(oldData,"GBK");if(newData.contains("-")){newData = newData.substring(0, newData.indexOf('-'));}return newData;}public static double bytes2Double(byte[] arr) {long value = 0;for (int i = 0; i < 8; i++) {value |= ((long) (arr[i] & 0xff)) << (8 * i);}return Double.longBitsToDouble(value);}}

更多相关文章

  1. Android调用WebService系列之KSoap2对象解析
  2. Android Application对象必须掌握的七点
  3. android XMl 解析神奇xstream 五: 把复杂对象转换成 xml ,并写入S
  4. Android判断网络状态是否断开+Android完全关闭应用程序+ 本文讲
  5. Android NDK 学习之传递类对象
  6. android访问服务器端上传及服务器端接收 .
  7. android访问服务器端上传及服务器端接收
  8. Android根据上下文对象Context找到对应的Activity
  9. 面向UDP的Android——PC双向通信(二):实现Android客户端和PC服务器

随机推荐

  1. Android(安卓)UI秘笈:谨记该做什么不该做
  2. 2016年腾讯android开发工程师面试题目
  3. EditText的简单使用
  4. android自定义UI模板图文详解
  5. Android定时任务的应用及实现
  6. Android如何同时安装相同应用程序不同版
  7. Android(安卓)UI 之 各种长度单位
  8. Android(安卓)强指针和弱指针
  9. Android(安卓)Studio com.android.dex.De
  10. Android(安卓)View从源码的角度分析事件