面向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;           //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;
	
	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 len	byte数组长度
	 * @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 b		4位byte数组
	 * @return		int型数据
	 */
	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() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				try {
					
					sendMsg();		//发送消息
					
				} catch (Exception e1) {
					e1.printStackTrace();
				}
			}
		});
		
		frame.add(IPLabel);		//源IP
		frame.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.java
import 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.java
package 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;
	@Override
	protected 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;
	}
	@Override
	public 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);
	}
	
}

你可能感兴趣的:(网络通信)