面向UDP的Android——PC双向通信(三):在Android客户端和PC服务器端之间传输自定义对象
在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);}}
更多相关文章
- Android调用WebService系列之KSoap2对象解析
- Android Application对象必须掌握的七点
- android XMl 解析神奇xstream 五: 把复杂对象转换成 xml ,并写入S
- Android判断网络状态是否断开+Android完全关闭应用程序+ 本文讲
- Android NDK 学习之传递类对象
- android访问服务器端上传及服务器端接收 .
- android访问服务器端上传及服务器端接收
- Android根据上下文对象Context找到对应的Activity
- 面向UDP的Android——PC双向通信(二):实现Android客户端和PC服务器