Java网络教程:UDP DatagramSocket

Java, UDP

Posted by kylo on December 19, 2015

原文地址 译者:Kylo

数据报套接字(DatagramSocket)是Java的网络通讯方式,它通过UDP而不是TCP实现。UDP位于IP层的上层,可以使用数据报套接字来发送和接收UDP数据包。

UDP vs. TCP

UDP的工作原理和TCP稍有不同。通过TCP发送数据时,需要先创建一个TCP连接。只要连接建立,TCP就能确保你的数据成功到达接收方,否则它将告知你发生了错误。

使用UDP的话,只需把数据包发送给网络上的IP地址。你无法确保该数据包是否成功到达,你也无法确保数据包以期望的顺序到达。这意味着UDP在协议开销上(没有数据流的完整性检查)比TCP小。

UDP非常适合一些并不强调数据包完整的数据传输。比如,网络直播的电视信号传输。你想要的是到达客户端的信号尽可能的接近现场(译者注:这里强调及时性)。所以,丢失一两个画面并无大碍。你并不希望直播是为了确保每一个画面都可靠传到而延迟,宁愿忽略这些丢失的画面,也要确保画面总是最新的。

网络上的实时监控摄像也是个很好的例子。谁关心过去发生的事呢,你只想要监控现在。你不想只是因为为了把所有的画面都展现给监控人员而延迟30秒吧。这和录像有一点不同。在你往磁盘录入影像的时候,可能并不想丢掉任何一个画面。你宁愿慢点,也不愿过后无法回看和检查这些画面,尤其是有重要的事情发生的时候。

通过DatagramSocket发送数据

通过Java的DatagramSocket发送数据,首先要创建一个DatagramPacket。代码如下:

byte[] buffer = new byte[65508];
InetAddress address = InetAddress.getByName("jenkov.com");
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, 9000);

byte[]数组buffer就是要在UDP数据报中发送的数据。上面的65508是单个UDP数据包能发送的最大容量。DatagramPacket构造方法中的长度是buffer中的数据的长度,buffer中所有超出最大容量的数据将被丢弃。

InetAddress实例目标节点(比如服务器)的地址。InetAddress类代表一个IP地址(网络地址),getByName()方法返回一个InetAddress实例,这个实例带有与给定的主机名对应的IP地址。

port参数是服务器到接收方的数据监听UDP端口。UDP和TCP端口是不同的。一个主机可以拥有不同的进程分别监听UDP80端口和TCP80端口。

要发送DatagramPacket,需要创建用于发送数据的DatagramSocket。代码如下:

DatagramSocket datagramSocket = new DatagramSocket();

发送数据要调用send()方法,如下:

datagramSocket.send(packet);

下面是完整实例:

DatagramSocket datagramSocket = new DatagramSocket();

byte[] buffer = "0123456789".getBytes();
InetAddress receiverAddress = InetAddress.getLocalHost();

DatagramPacket packet = new DatagramPacket(
buffer, buffer.length, receiverAddress, 80);
datagramSocket.send(packet);

通过DatagramSocket接收数据

通过DatagramSocket接收数据,首先要创建一个DatagramPacket,然后通过DatagramSocket’s receive()方法写入。代码如下:

DatagramSocket datagramSocket = new DatagramSocket(80);

byte[] buffer = new byte[10];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

datagramSocket.receive(packet);

注意DatagramSocket实例化时通过构造方法传入的80。这个参数是DatagramSocket接收UDP数据包的UDP端口。正如之前所说,TCP和UDP端口是不同的,这里就不再赘述了,两个进程分别监听TCP80端口和UDP80端口不会有任何的冲突。

第二,这里创建了一个byte[]数组buffer和DatagramPacket实例。注意这里的DatagramPacket并没有目标节点的相关信息,这和创建用于发送的DatagramPacket不同。这是因为现在的DatagramPacket是用来接收数据的,而不是发送,所以并不需要目标地址。

最后,调用了DatagramSocket的receive()方法。这个方法阻塞了线程,知道接收到一个DatagramPacket。

接收到的数据位于DatagramPacket的byte[]数组buffer中,buffer可以通过调用方法来获得:

byte[] buffer = packet.getData();

接收到的数据有多大,就留给你去探索了。你现在使用的协议应该指明一个UDP数据包传输的数据大小和一个结束标识符。

一个真正的服务器程序很可能在一个循环中调用receive()方法,然后把接收到的DatagramPacket传递给一个工作线程池,就像TCP服务器处理接收到的连接那样(详情参见Java多线程服务器)。