Java的UDP接收
1. 代码
1.1 UDP.java
UDP.java用于实现接收数据、开始和结束接收等。
代码如下:
//Edit at 180514:修改了同一个Thread重复使用带来的异常
package udputils;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDP {
private DatagramSocket udp = null;
private UdpHandler mUdpHandler;
private volatile boolean mRecFlag = false;
private int mBufferLen = 4096;
private Thread mRecThr;
public void setmUdpHandler(UdpHandler mUdpHandler) {
this.mUdpHandler = mUdpHandler;
}
public int getmBufferLen() {
return mBufferLen;
}
public void setmBufferLen(int mBufferLen) {
this.mBufferLen = mBufferLen;
}
public void setUdp(DatagramSocket udp) {
this.udp = udp;
}
public void startReceive() {
mRecFlag = true;
mRecThr = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
if (udp != null && !udp.isClosed()) {
while (mRecFlag) {
byte receiveBuf[] = new byte[mBufferLen];
DatagramPacket inPacket = new DatagramPacket(receiveBuf, receiveBuf.length);
try {
udp.receive(inPacket);
if (mUdpHandler != null) {
mUdpHandler.doAfterReceive(inPacket);
}
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
if (mUdpHandler != null) {
mUdpHandler.doWhenUdpIsNotSet();
mRecFlag = false;
}
}
}
});
mRecThr.start(); //WARNING:同一个线程启动两次,会出现异常IllegalThreadStateException
}
public void stopReceive() {
mRecFlag = false;
udp.close();
try {
mRecThr.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
udp = null;
}
public boolean isReceiving() {
return mRecFlag;
}
}
1.2 UdpHandler.java
本文件为回调接口,这里只预留了两个接口:接收到数据后的处理和DatagramSocket出错的接口。
代码如下:
package udputils;
import java.net.DatagramPacket;
public interface UdpHandler {
public void doAfterReceive(DatagramPacket received);
public void doWhenUdpIsNotSet();
}
1.3 调用方法
以下是测试该接收功能的java文件的内容:
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import udputils.UDP;
import udputils.UdpHandler;
public class udpTest {
private static volatile boolean isLoop = true;
private static class MyUdpHandler implements UdpHandler {
public void doAfterReceive(DatagramPacket received) {
System.out.println("=============================================");
System.out.println("Received data From Host: "
+ received.getAddress().getHostAddress());
System.out.println("Host Name: "
+ received.getAddress().getHostName());
System.out.println("Port: " + received.getPort());
System.out.println("Data Length: " + received.getLength());
// Manually get data...
byte[] d = new byte[received.getLength()];
System.arraycopy(received.getData(), 0, d, 0, received.getLength());
String recStr = new String(d);
System.out.println("Data: " + recStr);
if (recStr.contains("close")) {
isLoop = false;
}
}
public void doWhenUdpIsNotSet() {
// TODO Auto-generated method stub
System.out.println("UDP Set Error!!");
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
UDP u = new UDP();
DatagramSocket ds;
try {
ds = new DatagramSocket(22493);
u.setUdp(ds); // Set DatagramSocket
u.setmUdpHandler(new MyUdpHandler()); // Set ReceiveHandler
u.startReceive();
System.out.println("Start Receive...");
while (isLoop)
;
u.stopReceive();
System.out.println("Stop Receive!");
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
如上所述,调用该UDP类进行UDP接收的步骤如下:
新建一个UDP类的实例:
UDP u = new UDP();
新建一个进行UDP连接的DatagramSocket对象(根据不同需要调用不同的构造函数),使用setUdp()方法传入UDP对象:
DatagramSocket ds; ds = new DatagramSocket(22493); //This method should surrounded by "try ... catch ..." u.setUdp(ds); // Set DatagramSocket to UDP object.
新建一个内部类实现UdpHandler接口,并将该类的实例使用setmUdpHandler方法传入UDP对象中:
private static class MyUdpHandler implements UdpHandler { public void doAfterReceive(DatagramPacket received) { System.out.println("============================================="); System.out.println("Received data From Host: " + received.getAddress().getHostAddress()); System.out.println("Host Name: " + received.getAddress().getHostName()); System.out.println("Port: " + received.getPort()); System.out.println("Data Length: " + received.getLength()); // Manually get data... byte[] d = new byte[received.getLength()]; System.arraycopy(received.getData(), 0, d, 0, received.getLength()); String recStr = new String(d); System.out.println("Data: " + recStr); if (recStr.contains("close")) { isLoop = false; } } public void doWhenUdpIsNotSet() { // TODO Auto-generated method stub System.out.println("UDP Set Error!!"); } } ... u.setmUdpHandler(new MyUdpHandler()); // Set ReceiveHandler ...
本接口的doAfterReceive()方法用于处理接收到的DatagramPacket对象。
使用如下方法开始或停止接收:
开始接收:
startReceive()
停止接收:
stopReceive()
==WARNING==
在
stopReceive()
停止接收后,UDP对象内的DatagramSocket对象会被置为null。因此,在再次开始接收前,需要==重新新建一个DatagramSocket对象并设置进入UDP对象中==。
2. 代码解析
在这里主要关注两个类:DatagramSocket
和DatagramPacket
。
2.1 DatagramSocket类
DatagramSocket类(下面简称DS)用于实现UDP协议的数据收发工作。
2.1.1 构造函数
DS类的构造函数有下列几种形式:
构造函数 | 含义 |
---|---|
DatagramSocket() | 创建一个DatagramSocket对象 并将该对象绑定到本机默认IP地址、本机所有可用端口中随机选择的某个端口。 |
DatagramSocket(int port) | 创建一个DatagramSocket对象 并将该对象绑定到本机默认IP地址、指定端口。 |
DatagramSocket(int port,InetAddress laddr) | 创建一个DatagramSocket对象 并将该对象绑定到指定IP地址、指定端口。 |
根据实际情况选择需要使用的构造函数即可。
例如在本例中使用了指定端口号的构造函数。
2.1.2 发送与接收数据
在建立了DS对象后,可以通过如下方法来接受/发送数据:
receive(DatagramPacket p)
:从该DatagramSocket中接收数据报。
send(DatagramPacket p)
:以该DatagramSocket对象向外发送数据报。
注意:
这里的发送、接收方法均为耗时操作(`receive方法是阻塞方法),因此请合理使用多线程编程。
在这里,DS对象只提供了一个数据发送/接收的平台,不关心接受到的数据从哪里来,也不关心要发送的数据的目的地。
这些信息都在DatagramPacket 中。
2.1.3 关闭DS对象
可使用close()
方法关闭DS对象。
注意:
DS对象执行了close()方法后,无法再重新打开。
根据Javadoc,任何堵塞DS对象的receive()方法上的线程,在该DS对象执行了close()方法后,会抛出异常SocketException。
2.2 DatagramPacket类
DP类是实际发送、接收时涉及的数据包对象。
该对象除了要传输的数据外,还包含了传输的目的地/来源。
2.2.1 构造函数
构造函数 | 含义 |
---|---|
DatagramPacket(byte[] buf,int length) | 以一个空数组来创建用于 接收 DatagramSocket中的数据的DatagramPacket对象 数据最大长度为length |
DatagramPacket(byte[] buf, int offset, int length) | 以一个空数组来创建用于 接收 DS中的数据的DP对象 将数据放入数组时从数组的offset处开始 数据最大长度为length |
DatagramPacket(byte[] buf, int length, InetAddress addr, int port) | 以一个包含数据的数组来创建用于发送的DP对象 在创建用于发送的DP对象时,需要指定目的IP地址和端口 发送数据长度为length |
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) | 同上,参数offset说明要发送的数据在buf数组里的起始位置 |
2.2.2 获取数据、IP地址
InetAddress getAddress()
:当程序准备发送此数据报时,该方法返回此数据报的目标机器的IP地址;当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的IP地址。
int getPort()
:当程序准备发送此数据报时,该方法返回此数据报的目标机器的端口;当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的端口。
SocketAddress getSocketAddress()
:当程序准备发送此数据报时,该方法返回此数据报的目标SocketAddress;当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的SocketAddress。
int getLength()
:接收到数据的长度。
byte[] getData()
:获取DP类里的字节数组对象。注意:此处获取到数组的长度不一定是接收到数据的长度,需要配合getLength()方法得到实际的数据内容。