2021.10.21:UDP编程
与TCP编程相比,UDP编程就简单得多,因为UDP没有创建连接,数据包也是一次收发一个,所以没有流的概念。
在Java中使用UDP编程,仍然需要Socket,因为应用程序在使用UDP时必须指定IP和端口。注意:UDP和TCP虽然都使用0~65535,但是他们是两套独立的端口,即一个应用程序用TCP占用了端口1234,不影响另一个应用程序用UDP占用端口1234。
Server
Server端,使用UDP也需要监听指定端口。Java提供了DatagramSocket来实现这个功能,代码如下:
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.nio.charset.StandardCharsets; public class Server { public static void main(String[]args) throws IOException { DatagramSocket ds = new DatagramSocket(6666); while(true){ //数据缓冲区 byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer,buffer.length); ds.receive(packet);//收到一个UDP数据包 //收取到的数据存储在buffer中,由packet.getOffset(),packet,getLength()指定起始位置和长度 //将其按照UTF-8编码转换为String String s = new String(packet.getData(),packet.getOffset(),packet.getLength(), StandardCharsets.UTF_8); //发送数据 byte[] data="ACK".getBytes(StandardCharsets.UTF_8); packet.setData(data); ds.send(packet); } } }
Server首先使用如下语句在指定的端口监听UDP数据包:
DatagramSocket ds = new DatagramSocket(6666);
如果没有其他应用程序占据这个端口,那么坚挺成功,我们就使用一个无限循环来处理收到的UDP数据包:
while(true){ ... }
要收到一个UDP数据包,需要准备一个byte[ ]缓冲区,并通过DatagramPacket实现接收:
byte[] buffer = new byte[1024]; DatagramPacket packet = new DatagramPacket(buffer,buffer.length); ds.receive(packet);
假设我们收到的是一个String,那么,通过DatagramPacket返回的packet.getOffset()、packet.getLength()确定数据在缓冲区的起止位置:
String s = new String(packet.getData() , packet.getOffset() , packet.getLength() , StandardCharsets.UTF_8);
当Server收到一个DatagramPacket后,通常必须回复一个或者多个UDP包,因为Client地址在DatagramPacket中,每次收到的DatagramPacket可能是不同的Client,如果不回复,Client就收不到任何UDP包。
发送UDP包也是通过DatagramPacket实现的,发送代码非常简单:
byte[] data = ... packet.setData(data); ds.send(packet);
Client
与Server相比,Client使用UDP时,只需要直接向Server发送UDP包,然后接受返回的UDP包:
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public class UDPClient { public static void main(String[]args) throws IOException { DatagramSocket ds = new DatagramSocket(); ds.setSoTimeout(1000); //连接Server的IP与端口 ds.connect(InetAddress.getByName("localhost"),6666); //发送 byte[] data="Hello".getBytes(); DatagramPacket packet = new DatagramPacket(data,data.length); ds.send(packet); //接收 byte[] buffer = new byte[1024]; packet = new DatagramPacket(buffer,buffer.length); ds.receive(packet); String resp = new String(packet.getData(),packet.getOffset(),packet.getLength()); ds.disconnect(); } }
Client打开一个DatagramSocket使用如下代码:
DatagramSocket ds = new DatagramSocket(); ds.setSoTimeout(1000); ds.connect(InetAddress.getByName("localhost"),6666);
Client创建DatagramSocket实例并不需要指定端口,而是由OS自动指定一个未使用的端口。紧接着,调用setSoTimeout(1000)设定超时1s,意思是后续接收UDP包时,等待时间最多不超过1s,否则在没有收到UDP包时,Client会无限等待下去。这不同于Server,因为Server可以无限等待。
注意到Client的DatagramSocket还调用了一个connect()方法"连接"到指定的Server。UDP是无连接协议,所以这里的connect()方法不是真连接,它是为了在Client的DatagramSocket实例中保存Server的IP和端口,确保这个DatagramSocket只能往指定IP和端口发送UDP包,不能往其他地址和端口发送。这么做不是UDP的限制,而是Java内置了安全检查。
如果Client希望向两个不同Server发送UDP包,那么它必须创建两个DatagramSocket实例。
后续的收发数据和Server端是一致的。通常来说,Client必须首先发送UDP包,以告知Server自己的IP和端口。
如果Client认为通信结束,就可以调用disconnect()断开连接:
ds.disconnect();
这里的disconnect()也不是真正断开连接,它只是清除了Client的DatagramSocket记录的Server的IP和端口,这样,DatagramSocket就可以连接到另一个Server。
小结
使用UDP协议通信时,Server和Client无需建立连接:
- Server用DatagramSocket(port)监听端口;
- Client用DatagramSocket.connect()指定远程IP与端口;
- 双方通过receive()和send()读写数据;
- DatagramSocket没有IO流接口,数据被直接写入 byte[ ] 缓冲区。

浙公网安备 33010602011771号