欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

面向UDP的Android——PC双向通信(三):在Android客户端和PC服务器端之间传输自定义对象

程序员文章站 2024-02-11 12:54:16
...

在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;
	
	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);
	}
	
}