[疯狂Java]UDP:接收发送数据报、获取接收到的数据报的相关信息

1. Java的UDP模型:

    1) DatagramSocket是UDP的socket,由于它只是个码头只能发货和收货,因此它就只有两个方法,一个是send用来发送数据报,一个即使receive用来接收数据报;

    2) 由于DatagramSocket只是个码头,因此只能绑定自己的IP地址和端口(构造器),不能在构造器中指定目的地的IP地址和端口,码头怎么会自己移动呢?因此只能绑定自己的地理位置而已;

    3) 数据传递的主体是数据报DatagramPacket,因此数据报送到哪儿只有数据报自己知道,所以要在数据报中指定目的地的IP地址和端口号(在数据报的构造器中指定);

    4) 发送数据/接受数据的步骤:

         i. 发送时肯定是先创建一个数据报,然后往里面写上内容,并附上目的地的IP地址和端口,然后在通过socket的send发送出去;

         ii. 接收时肯定是先创建一个空的数据报,然后用socket的receive方法接收(将受到的数据写入空的数据报中),然后取出数据分析;


2. 接收和发送:

    1) 首先需要构造数据报:所有的数据报都需要依赖一个字节数组来存放数据,即byte[],因为数据报底层要求必须用无类型的字节块传输(且大小不超过64KB),不能用流传输(什么InputStream之类的,流就是不行);

         i. 接收数据的数据报:

            a. DatagramPacket(byte buf[], int length); // 指定缓存buff和长度length

            b. DatagramPacket(byte buf[], int offset, int length); // 指定从buf的offset位置处存放数据

!!可以看到,已经有一种强烈的C语言风格,非常的繁琐,不仅要指定缓存区,还要指定长度,这些都不能自动识别

         ii. 发送数据:肯定还需要指定目的地的IP地址和端口号了

             a. DatagramPacket(byte buf[], int length, InetAddress address, int port);

             b. DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port); // 指定从buf的offset开始发送数据

             c. 最后两个参数可以合二为一直接用一个SocketAddress对象代替(重载版本),即InetAddress和port的结构体;

    2) 接着就是发送和接受数据报了,直接使用DatagramSocket的send和receive方法(都是对象方法)就行了:

         i. void DatagramSocket.send(DatagramPacket p); // 准备好的数据报

!!发送数据时socket还会将客户端的有关信息(IP和端口)一并包装在数据报的报头中发送,这样在服务器端可以通过各种方法获取报头中有关客户端的相关信息;

         ii. synchronized void receive(DatagramPacket p); // 准备好接收用的数据报

!!receive到数据报之前调用该方法的线程会一直阻塞,要注意这点,合理利用多线程提高效率;

!!可以看到,这里没有C/S一说,你一个数据报既可以做发送数据报也可以做接收数据报,由于send不改变数据报中的内容,而receive会改变,因此receive需要同步;

    3) 当然在接受和发送之前肯定要准备好DatagramSocket对象的,了解一下它的构造器:只需要绑定本机(本应用)的IP地址和端口号即可:

         i. DatagramSocket(); // 绑定默认IP和端口

         ii. DatagramSocket(int port); // 绑定默认IP和指定端口

         iii. DatagramSocket(int port, InetAddress laddr); // 全指定

         iv. DatagramSocket(SocketAddress bindaddr); // 全指定,更简洁方便


2. 获取接收到的数据报中的相关信息:

    1) 这里是指获取和客户端相关的信息,无非就是客户端的IP地址和端口号而已,以及获取其中的数据;

    2) receive之后便可以调用DatagramPacket的相关方法获取信息了;

    3) synchronized int getPort(); // 获取客户端的端口号

    4) synchronized InetAddress getAddress(); // 获取客户端的IP地址

    5) synchronized SocketAddress getSocketAddress(); // 打包一起获取

    6) 获取接受到的数据:synchronized byte[] getData(); // 返回的就是构造DatagramPacket传入的缓存buf,可以自己测试一下

    7) 所有的这些方法都是同步的,因为UDP部分发送数据的数据报和接收数据的数据报,两者用的都是同一种类型,因此一个发送数据的数据报可以在几行代码之后变成接收数据的数据报,这就存在多线程冲突的问题,因此必须同步;

!!但是不建议两者混用,发送和接收区分开来,这样不容易出错,养成良好的习惯,保证程序的安全性和健壮性


3. 示例:

public class Server {
	
	private static final int PORT = 30000; // 本机端口
	private static final int PACKET_SIZE = 4096; // 报的大小
	
	private byte[] buff = new byte[PACKET_SIZE]; // 输入缓冲区
	private DatagramPacket inPack = new DatagramPacket(buff, buff.length);
	private DatagramPacket outPack;
	
	private String[] books = new String[] { // 随便返回给客户端的内容
			"学雷锋",
			"长征",
			"天安门",
			"延安"
	};
	
	public void init() throws IOException {
		try (DatagramSocket sock = new DatagramSocket(PORT)) {
			for (int i = 0; i < 100; i++) {
				sock.receive(inPack);
				System.out.println(buff == inPack.getData()); // true
				System.out.println(new String(buff, 0, inPack.getLength())); // 打印接收包的内容
				
				byte[] outBuff = books[i % 4].getBytes(); // 包装回送
				outPack = new DatagramPacket(outBuff, outBuff.length, inPack.getSocketAddress());
				sock.send(outPack);
			}
		}
	}

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		new Server().init();
	}

}
public class Client {

	private static final int DEST_PORT = 30000;
	private static final String DEST_IP = "127.0.0.1";
	private static final int PACK_SIZE = 4096;
	private byte[] inbuf = new byte[PACK_SIZE];
	private DatagramPacket inpack = new DatagramPacket(inbuf, inbuf.length);
	private DatagramPacket outpack;
	
	public void init() throws IOException {
		try(DatagramSocket sock = new DatagramSocket()) {
			outpack = new DatagramPacket(new byte[0], 0, InetAddress.getByName(DEST_IP), DEST_PORT);
			Scanner scan = new Scanner(System.in);
			while (scan.hasNextLine()) { // 键盘输入
				byte[] buf = scan.nextLine().getBytes(); // 捕获发送
				outpack.setData(buf);
				sock.send(outpack);
				
				sock.receive(inpack); // 阻塞等待接受
				System.out.println(new String(inbuf, 0, inpack.getLength())); // 打印回送内容
			}
		}
	}
	
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		new Client().init();
	}

}
!可以直接通过DatagramSocket的setData方法改变和其绑定的byte[]缓存区:synchronized void DatagramSocket.setData(byte[] buf); // 大小默认设成buf的length,默认从offset=0处开始


你可能感兴趣的:(疯狂Java笔记)