Java UDP通信:DatagramSocket和DatagramPacket

  UDP 是在IP网络上收发数据的传输层协议,其速度快但不可靠。为什么会使用这种不可靠的协议呢?许多应用保持最快的速度比保证每一位数据都正确更为重要,例如实时音频或视频丢失数据只会作为干扰出现并且可以容忍;另外一些应用,利用UDP数据传输可靠性就需要在应用中进行控制。DNS、NFS、TFTP等都可以配置使用UDP协议。
  我们都知道TCP是面向连接的,所谓连接就是在端点通信前建立起一条“虚电路”,利用逻辑标识来控制路由寻路,这个逻辑标识记录了目的IP、端口、虚电路标识等信息。UDP是无需连接,每个传输单元就称为数据包,数据包上就记录了相应的路由信息,且前后传输的数据包可不相关,这就要求各数据包中记录路由信息。而TCP建立好虚电路后,每个传输单元是不需要关心这些信息的。面向连接的TCP有诸多优点,如可靠性、拥塞可控等,但是其仅能用于端点间通信而不能用于广播或者组播通信,后两者还需要UDP发挥作用。
  Java用两个类实现UDP:DatagramPacket和DatagramSocket,前者将数据字节填充到UDP包,后者收发UDP包。用法很简单,DatagramSocket收发DatagramPacket即可。与TCP不同,UDP的socket并没有客户端和服务端的区别而统一应用此对象。下面给出利用UDP单播通信的daytime示意:

  • UDPServer.java daytime服务端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Date;

/**
 * Created by Cheney Hwang on 2017/5/9.
 */
public class UDPServer {
    public final static int PORT = 5555;

    public static void main(String[] args) {
        byte[] buffer = new byte[64];
        //侦听某个UDP端口
        try (DatagramSocket server = new DatagramSocket(PORT)) {
            server.setSoTimeout(10000);
            while (true) {
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                try {
                    server.receive(packet);//10s超时接收
                    byte[] now = new Date().toString().getBytes("US-ASCII");
                    //组织数据,并传入请求方socket地址作为回应地址
                    DatagramPacket outgoingPacket = new DatagramPacket(now, now.length, packet.getAddress(), packet.getPort());
                    server.send(outgoingPacket);
                } catch (SocketTimeoutException ex) {
                    System.out.println("Timeout!");//超时
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        } catch (SocketException ex) {
            ex.printStackTrace();
        }
    }
}

  UDP向底层IP数据添加少部分内容:源端口、目的端口、IP数据部分长度和可选校验和,这些总共占用8字节。端口、地址、数据长度和数据等信息都可以从DatagramPacket中提取或者向其设置,以上UDPServer.java就是提取这些信息而获取客户端信息并作为回应地址的。UDP包中数据理论最大为65507(65535-20IP基本头-8UDP头),但实际上几乎总是比这个少得多,在很多平台上往往限制在8KB,因此某些程序依赖于发送超过8KB数据的UDP包要多加小心,大多数情况下更大的包都会被简单地截取位8KB数据,为保证最大的安全性,UDP数据部分尽量保持在512B以下。下面是与UDPServer.java通信的示例客户端:

  • UDPClient.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;

/**
 * Created by Cheney Hwang on 2017/5/9.
 */
public class UDPClient {
    public final static int PORT = 5555;
    public final static String HOST = "localhost";

    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket()) {//绑定端口由系统分配
            //DatagramSocket依赖于数据报DatagramPacket收发数据,byte[1]是为了触发服务端
            DatagramPacket packet = new DatagramPacket(new byte[1], 1, InetAddress.getByName(HOST), PORT);
            socket.setSoTimeout(10000);//receive数据阻塞等待10s,超过这个时间仍未收到数据说明丢失了
            socket.send(packet);

            DatagramPacket incomePacket = new DatagramPacket(new byte[64], 64);
            try {
                socket.receive(incomePacket);//阻塞接收,10s超时
                byte[] nowByte = new byte[incomePacket.getLength()];
                System.arraycopy(incomePacket.getData(), 0, nowByte, 0, incomePacket.getLength());
                String now = new String(nowByte, "US-ASCII");
                System.out.println(now);
            } catch (SocketTimeoutException ex) {
                System.out.println("Timeout!");
            }

        } catch (IOException ex) {
            return;
        }
    }
}

  观察上面服务端和客户端的UDP通信示例,可以发现判断DatagramPacket是发数据还是收数据依据的是其构造差异。发数据在构造函数中需要指定目的Socket地址,而收数据并不需要(在生产环境中必须要考虑这边的安全策略)。类似地,DatagramSocket在默认构造时常用于客户端——其并不关心某个本地端口,由系统指定即可;而指定端口的构造常用于服务端,意义不言而喻。
  这边还有个UDP应用场景需要注意的问题,UDP服务器一般不会与客户端做太多的交互,通常可以在连接线程中直接响应客户端。不过如果需要做大量处理的话,可以创建处理线程来做此工作。

你可能感兴趣的:(Java)