`
阅读更多
17.4.2 使用DatagramSocket发送、接收数据

DatagramSocket本身只是码头,不维护状态,不能产生IO流,它的唯一作用就是接收和发送数据报,Java使用DatagramPacket来代表数据报,DatagramSocket接收和发送的数据都是通过DatagramPacket对象完成的。

先看一下DatagramSocket的构造器:

DatagramSocket():创建一个DatagramSocket实例,并将该对象绑定到本机默认IP地址、本机所有可用端口中随机选择的某个端口。

DatagramSocket(int prot):创建一个DatagramSocket实例,并将该对象绑定到本机默认IP地址、指定端口。

DatagramSocket(int port, InetAddress laddr):创建一个DatagramSocket实例,并将该对象绑定到指定IP地址、指定端口。

通过上面三个构造器中任意一个构造器即可创建一个DatagramSocket实例,通常在创建服务器时,我们创建指定端口的 DatagramSocket实例——这样保证其他客户端可以将数据发送到该服务器。一旦得到了DatagramSocket实例之后,就可以通过如下两个方法来接收和发送数据:

receive(DatagramPacket p):从该DatagramSocket中接收数据报。

send(DatagramPacket p):以该DatagramSocket对象向外发送数据报。

从上面两个方法可以看出,使用DatagramSocket发送数据报时,DatagramSocket并不知道将该数据报发送到哪里,而是由 DatagramPacket自身决定数据报的目的。就像码头并不知道每个集装箱的目的地,码头只是将这些集装箱发送出去,而集装箱本身包含了该集装箱的目的地。

当Client/Server程序使用UDP协议时,实际上并没有明显的服务器和客户端,因为两方都需要先建立一个DatagramSocket对象,用来接收或发送数据报,然后使用DatagramPacket对象作为传输数据的载体。通常固定IP、固定端口的DatagramSocket对象所在的程序被称为服务器,因为该DatagramSocket可以主动接收客户端数据。

下面看一下DatagramPacket的构造器:

DatagramPacket(byte buf[],int length):以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据。

DatagramPacket(byte buf[], int length, InetAddress addr, int port):以一个包含数据的数组来创建DatagramPacket对象,创建该DatagramPacket时还指定了IP地址和端口——这就决定了该数据报的目的。

DatagramPacket(byte[] buf, int offset, int length):以一个空数组来创建DatagramPacket对象,并指定接收到的数据放入buf数组中时从offset开始,最多放length个字节。

DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):创建一个用于发送的DatagramPacket对象,也多指定了一个offset参数。

在接收数据前,应该采用上面的第一个或第三个构造器生成一个DatagramPacket对象,给出接收数据的字节数组及其长度。然后调用 DatagramSocket 的方法receive()等待数据报的到来,receive()将一直等待(也就是说会阻塞调用该方法的线程),直到收到一个数据报为止。如下代码所示:

//创建接受数据的DatagramPacket对象
DatagramPacket packet=new DatagramPacket(buf, 256);
socket.receive(packet);//接收数据

发送数据之前,调用第二个或第四个构造器创建DatagramPacket对象,此时的字节数组里存放了想发送的数据。除此之外,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的方法send()实现的,send()根据数据报的目的地址来寻径以传递数据报。如下代码所示:
//创建一个发送数据的DatagramPacket对象
DatagramPacket packet = new DatagramPacket(buf, length, address, port);
//发送数据报
socket.send(packet);

当我们使用DatagramPacket来接收数据时,会感觉DatagramPacket设计得过于烦琐。对于开发者而言,只关心该 DatagramPacket能放多少数据,而DatagramPacket是否采用字节数组来存储数据完全不想关心。但Java要求创建接收数据用的 DatagramPacket时,必须传入一个空的字节数组,该数组的长度决定了该DatagramPacket能放多少数据,这实际上暴露了 DatagramPacket的实现细节。接着DatagramPacket又提供了一个getData()方法,该方法又可以返回 DatagramPacket对象里封装的字节数组,该方法更显得有些多余:如果程序需要获取DatagramPacket里封装的字节数组,直接访问传给 DatagramPacket构造器的字节数组实参即可,无须调用该方法。

当服务器(也可以客户端)接收到一个DatagramPacket对象后,如果想向该数据报的发送者“反馈”一些信息,但由于UDP是面向非连接的,所以接收者并不知道每个数据报由谁发送过来,但程序可以调用DatagramPacket的如下三个方法来获取发送者的IP和端口:

InetAddress getAddress():返回某台机器的 IP 地址,当程序准备发送次数据报时,该方法返回此数据报的目标机器的IP地址;当程序刚刚接收到一个数据报时,该方法返回该数据报的发送主机的IP地址。

int getPort():返回某台机器的端口,当程序准备发送此数据报时,该方法返回此数据报的目标机器的端口;当程序刚刚接收到一个数据报时,该方法返回该数据报的发送主机的端口。

SocketAddress getSocketAddress():返回完整SocketAddress,通常由IP地址和端口组成。当程序准备发送此数据报时,该方法返回此数据报的目标SocketAddress;当程序刚刚接收到一个数据报时,该方法返回该数据报是源SocketAddress。

上面getSocketAddress方法的返回值是一个SocketAddress对象,该对象实际上就是一个IP地址和一个端口号,也就是说 SocketAddress对象封装了一个InetAddress对象和一个代表端口的整数,所以使用SocketAddress对象可以同时代表IP地址和端口。

下面程序使用DatagramSocket实现Server/Client结构的网络通信程序,本程序的服务器端接收客户端传的文件路径地址,并将文件发送给客户端,服务器端代码如下:

程序清单:codes/17/17-4/UdpServer.java

package test;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpServer {
	private DatagramSocket socket;
	private DatagramPacket inPacket;
	private DatagramPacket outPacket;
	private RandomAccessFile raf;
	private byte[] buff;
	private String filePath;
	private static final int BUFF_LEN = 4096;
	
	public UdpServer(){
		run();
	}

	private void run() {
		try {
			socket = new DatagramSocket(9023);// 服务器端固定端口接收客户端信息

			//开始从客户端接收数据
			System.out.println("开始从客户端接收数据...");
			buff = new byte[BUFF_LEN];
			inPacket = new DatagramPacket(buff, BUFF_LEN);
			
			StringBuffer sb = new StringBuffer();
			socket.receive(inPacket);//开始接收收据
			while (inPacket.getLength() == BUFF_LEN) { //如果填满了,就继续接收		
				sb.append(new String(inPacket.getData(),0,inPacket.getData().length));
				socket.receive(inPacket);
			}
			sb.append(new String(inPacket.getData(),0,inPacket.getData().length));//构造不满BUFF_LEN的字符
			
			filePath = sb.toString();
			System.out.println("接收到的文件路径为:["+filePath+"]");			
			
			//开始发送文件
			System.out.println("开始发送文件...");
			raf = new RandomAccessFile(filePath, "rw");
			buff = new byte[BUFF_LEN];
			int len = 0;
			while((len = raf.read(buff))!= -1){
				outPacket = new DatagramPacket(buff,0, len, inPacket.getAddress(),inPacket.getPort());
				socket.send(outPacket);
			}
			raf.close();
			System.out.println("文件发送成功...");
			
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			if(socket != null)
				socket.close();
		}

	}
	
	public static void main(String[] args) {
		new UdpServer();
	}
}


上面程序中粗体字代码就是使用DatagramSocket发送、接收DatagramPacket的关键代码,该程序可以接受1000个客户端发送过来的数据。

客户端代码如下:

程序清单:codes/17/17-4/UdpClient.java

package test;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class UdpClient {
	private static DatagramSocket socket;
	private static DatagramPacket inPacket;
	private static DatagramPacket outPacket;
	private RandomAccessFile raf;
	private String filePath;
	private String toSaveFilePath;
	private byte[] buff;
	private static final int BUFF_LEN = 4096;

	public UdpClient() {

	}

	public UdpClient(String filePath, String toSaveFilePath) {
		this.filePath = filePath;
		this.toSaveFilePath = toSaveFilePath;
		run();
	}

	private void run() {
		try {
			socket = new DatagramSocket();// 客户端使用随机端口即可	
			buff = filePath.getBytes();
			outPacket = new DatagramPacket(buff, buff.length, InetAddress
					.getByName("127.0.0.1"), 9023);
			System.out.println("开始发送请求文件路径...文件路径:[" + filePath + "]");
			socket.send(outPacket);

			// 开始接收文件
			System.out.println("发送请求文件路径成功,开始接收...");

			raf = new RandomAccessFile(toSaveFilePath, "rw");
			buff = new byte[BUFF_LEN];
			inPacket = new DatagramPacket(buff, BUFF_LEN);
			socket.receive(inPacket);//开始接收数据
			while (inPacket.getLength() == BUFF_LEN) {//如果填满了数组  继续接收	
				raf.write(inPacket.getData(), 0, inPacket.getData().length);// 将接收到的数据写入文件中
				socket.receive(inPacket);
			}
			raf.write(inPacket.getData(), 0, inPacket.getData().length);//将不足BUFF_LEN的部分写入文件
			
			raf.close();
			System.out.println("文件接收成功...");

		} catch (NullPointerException e) {
			throw new NullPointerException("filePath is not exists!");
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(socket != null)
				socket.close();
		}

	}

	public String getFilePath() {
		return filePath;
	}

	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}

	public String getToSaveFilePath() {
		return toSaveFilePath;
	}

	public void setToSaveFilePath(String toSaveFilePath) {
		this.toSaveFilePath = toSaveFilePath;
	}

	public static void main(String[] args) {
		String filePath = "E:"+File.separator+"qnyh.jpg";
		String toSaveFilePath = "D:"+File.separator+"qnyh.jpg";
		
		new UdpClient(filePath, toSaveFilePath);
	}
}


上面程序的粗体字代码同样也是通过DatagramSocket发送、接收DatagramPacket的关键代码,这些代码与服务器的代码基本相似。而客户端与服务器端的唯一区别在于:服务器所在IP地址、端口是固定的,所以客户端可以直接将该数据报发送给服务器,而服务器则需要根据接收到的数据报来决定将“反馈”数据报的目的地。

读者可能会发现,使用DatagramSocket进行网络通信时,服务器端无须、也无法保存每个客户端的状态,客户端把数据报发送到服务器后,完全有可能立即退出。但不管客户端是否退出,服务器无法知道客户端的状态。

当使用UDP协议时,如果想让一个客户端发送的聊天信息可被转发到其他所有客户端则比较困难,可以考虑在服务器使用Set来保存所有客户端信息,每当接收到一个客户端的数据报之后,程序检查该数据报的源SocketAddress是否在Set集合中,如果不在就将该SocketAddress添加到该Set集合中,但这样一来又涉及一个问题:可能有些客户端发送一个数据报之后永久性地退出了程序,但服务器端还将该客户端的SocketAddress 保存在Set集合中……总之,这种方式需要处理的问题比较多,编程比较烦琐。幸好Java为UDP协议提供了MulticastSocket类,通过该类可以轻松实现多点广播。
分享到:
评论

相关推荐

    DatagramSocket

    ### DatagramSocket 在网络编程中的应用 #### 一、DatagramSocket 概述 在Java网络编程领域中,`DatagramSocket` 是一个重要的类,它主要用于实现无连接的数据报服务,即UDP(User Datagram Protocol)协议。与...

    Java DataGramSocket的Connect方法问题

    Java中的DataGramSocket类是实现UDP协议的主要工具,UDP是一种无连接的、不可靠的传输层协议,它的特点是轻量级、高效,适用于实时数据传输和不需要确认的通信场景。然而,DataGramSocket类提供了一个`connect()`...

    java DatagramSocket 的上传文件客户端

    Java中的`DatagramSocket`是实现用户数据报协议(UDP)通信的核心类。UDP是一种无连接的、不可靠的传输层协议,相比TCP,它在效率和速度上有优势,但不保证数据的顺序和完整性。在Java网络编程中,`DatagramSocket`...

    网路编程-UDP-DatagramSocket、DatagramPacket

    本篇将详细探讨UDP编程中的关键组件——`DatagramSocket`和`DatagramPacket`,以及如何利用它们进行数据的发送和接收。 `DatagramSocket`是Java提供的一个类,它代表了UDP通信中的一个端点。通过这个类,我们可以...

    DatagramSocket通信一

    NULL 博文链接:https://liaolzy.iteye.com/blog/772962

    DatagramSocket And DatagramPacket

    在Java的网络编程中,`DatagramSocket`和`DatagramPacket`是两个核心的类,它们用于实现UDP(User Datagram Protocol)协议的通信。UDP是一种无连接的、不可靠的传输层协议,适合于对实时性要求较高但对数据完整性...

    j2me-net.rar_HTTP java_datagramsocket_j2me http_java socket _jav

    Test1---http连接 Test2---Socket连接 Test3---DatagramSocket连接 Test4---MMAPI Test5---多客户端服务器 Test6---MMAPI2 Test7---WMAServer

    Java 套接字编程Java 套接字编程

    DatagramSocket serverSocket = new DatagramSocket(9876); byte[] receiveData = new byte[1024]; DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); serverSocket.receive...

    java网络编程,UDP,发送16进制报文。

    DatagramSocket socket = new DatagramSocket(); InetAddress serverAddress = InetAddress.getByName("服务器IP"); int port = 12345; // 服务器端口号 DatagramPacket packet = new DatagramPacket(data, data...

    android socket UDP 通信

    1. 创建DatagramSocket:这是Android UDP通信的基础,通过`DatagramSocket()`构造函数创建一个Socket实例。这个Socket用于发送和接收数据报文。 2. 准备数据:数据通常封装在`DatagramPacket`对象中,包括要发送的...

    syslog协议发送日志(java)

    import java.net.DatagramSocket; import java.net.InetAddress; import java.util.logging.*; public class SyslogHandler extends Handler { // 实现发送syslog消息的方法 } // 配置日志处理器 Logger logger =...

    基于UDP协议的Socket编程

    基于UDP协议的Socket编程主要涉及两个关键类:`DatagramPacket`和`DatagramSocket`,这两个类都位于Java的`java.net`包中。 `DatagramPacket`类是UDP通信的核心,它负责封装和解析实际传输的数据。当需要发送数据时...

    java 简单的UDP聊天程序

    简单的UDP聊天程序,服务器端:创建DatagramSocket对象用于打开指定端口并监听,然后用创建一个DatagramPacket,利用DatagramSocket中的receive(ds)方法接收数据并放到刚创建的DatagramPacket对象中; 这样就完成...

    Java网络编程精解PPT课件.ppt

    DatagramSocket可以把UDP数据报发送给任意一个远程DatagramSocket,也可以接收来自任意一个远程DatagramSocket的UDP数据报。 DatagramPacket类 DatagramPacket表示UDP数据报,它的构造方法可以分为两类:用于接收...

    udp.rar_UDP

    - **创建DatagramSocket**:通过调用`DatagramSocket(int port)`构造方法可以创建一个指定端口的DatagramSocket对象。如果未指定端口,系统会自动分配一个可用端口。 - **发送数据**:使用`send(DatagramPacket ...

    Java_udp.rar_java udp

    DatagramSocket socket = new DatagramSocket(); socket.send(packet); ``` 5. 接收数据: 接收UDP数据,同样需要`DatagramSocket`,但还需要预先创建一个足够大的`DatagramPacket`用于接收数据。在调用`...

    Android 中出现java.net.BindException: bind failed: EADDRINUSE 问题解决办法

    DatagramSocket udpSocket = new DatagramSocket(DEFAULT_PORT ); } catch (Exception e) { e.printStackTrace(); } ``` 如果`DEFAULT_PORT`已经被其他服务占用,上述代码会抛出`java.net.BindException: bind ...

    udp.rar_UDP接收

    1. 创建DatagramSocket实例,指定监听的端口号,如`DatagramSocket serverSocket = new DatagramSocket(12345);`。 2. 定义一个用于接收数据的缓冲区,例如`byte[] buffer = new byte[1024];`。 3. 创建一个...

    java udp 通讯,java中实现UDP通讯

    在Java中,我们通常使用`java.net`包中的`DatagramSocket`类和`DatagramPacket`类来实现UDP通信。 1. **DatagramSocket类**: `DatagramSocket`是Java中用来发送和接收UDP数据报的类。创建一个`DatagramSocket`...

    UDP_java.rar_java udp_udp java

    DatagramSocket socket = new DatagramSocket(); ``` 如果你需要指定端口,可以使用`DatagramSocket(int port)`构造函数。 2. **准备DatagramPacket** 要发送数据,你需要创建一个`DatagramPacket`对象,包含你要...

Global site tag (gtag.js) - Google Analytics