​客户端与服务端示例​​ ​​DatagramPacket类​​​​DatagramSocket类​​​​Socket选项​​​​DatagramChannel​

用户数据报协议(User Datagram Protocol,UDP)是在IP之上发送数据的另一种传输层协议.速度很快,但不可靠.当发送UDP数据时,无法知道数据是否会到达,也不知道数据的各个部分是否会以发送时的顺序到达.不过,确实能到达的部分一般都会很快到达.

客户端与服务端示例



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(0);
socket.setSoTimeout(10000);
InetAddress host = InetAddress.getByName("localhost");
DatagramPacket request = new DatagramPacket(new byte[1], 1, host, 13);
DatagramPacket response = new DatagramPacket(new byte[1024], 1024);

socket.send(request);
socket.receive(response);
System.out.println(new String(response.getData(),0,response.getLength()));
}//UDP客户端



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(13);
while (true) {
DatagramPacket request = new DatagramPacket(new byte[1024], 0, 1024);
socket.receive(request);

InetAddress host = request.getAddress();
int port = request.getPort();
byte[] data = "udp server".getBytes();
DatagramPacket response = new DatagramPacket(data, data.length, host, port);

socket.send(response);
}
}//UDP服务端


DatagramPacket类

在Java中,UDP数据报用DatagramPacket类的实例表示.无论是发送数据还是接收数据,无论是客户端还是服务端都是用DatagramPacket类.这个类有6个构造函数,其中2个用于接收数据,4个用于发送数据.

接收数据构造

如果接收的数据报大于buffer或大于length会被丢弃.



public static void main(String[] args) {
byte[] buffer = new byte[512];
//接收数据包的数据部分,将数据部分存储在buffer中,从buffer[0]开始,一直到包
//完全存储,或者已经写入了length个字节.
DatagramPacket dp = new DatagramPacket(buffer,512);

//从buffer[offset]位置开始存储,其他参数意义相同.
DatagramPacket dp2 = new DatagramPacket(buffer,0,512);
}


发送数据

length小于buffer.length最多会发送length个字节.



public static void main(String[] args) throws Exception {
byte[] buffer = new byte[512];
//buffer要发送的数据,length要发送数据的长度,InetAddress只能构造IP,需要额外传入Port,InetSocketAddress可以构造IP+Port
DatagramPacket dp1 = new DatagramPacket(buffer, 512, InetAddress.getByName("localhost"), 1);
DatagramPacket dp2 = new DatagramPacket(buffer, 512, new InetSocketAddress("localhost", 1));
//额外增加一个偏移量,从buffer[offer]开始发送发送length个字节.
DatagramPacket dp3 = new DatagramPacket(buffer, 0, 512, InetAddress.getByName("localhost"), 1);
DatagramPacket dp4 = new DatagramPacket(buffer, 0, 512, new InetSocketAddress("localhost", 1));
}


选择数据报大小

大多数底层UDP实现都不支持超过8192字节的数据报.IPv4数据报的理论限制是65507字节数据,缓冲区为65507字节的DatagramPacket可以接收任何可能的IPv4数据报,而不会丢失数据.IPv6数据报理论限制提高到65536字节.但实际上,很多基于UDP的协议(DNS和TFTP)使用的包中,每个数据报都仅有512字节甚至更少.常用的最大数据大小是NFS所用的8192字节.

get方法

get方法获取的大部分都是来自对方的内容.



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(0);
socket.setSoTimeout(10000);

InetAddress host = InetAddress.getByName("localhost");
DatagramPacket request = new DatagramPacket(new byte[1], 1, host, 13);
DatagramPacket response = new DatagramPacket(new byte[1024], 1024);

socket.send(request);
socket.receive(response);

byte[] data = response.getData();//接收数据
int offset = response.getOffset();//偏移量
int length = response.getLength();//数据长度
System.out.println(new String(data, offset,length));

System.out.println(request.getAddress());//获取目标地址
System.out.println(response.getAddress());//获取源地址

System.out.println(request.getPort());//获取目标端口
System.out.println(response.getPort());//获取源端口

System.out.println(request.getSocketAddress());//获取目标地址
System.out.println(response.getSocketAddress());//获取源地址
}//UDP客户端


set方法

这些set方法的功能在构造函数完全可以完成,不过有了这些会有一些更灵活的组合.



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(0);
socket.setSoTimeout(10000);

InetAddress host = InetAddress.getByName("localhost");
DatagramPacket request = new DatagramPacket(new byte[1], 1, host, 13);

request.setData(new byte[2]);
request.setData(new byte[2],0,1);
request.setAddress(InetAddress.getByName("localhost"));
request.setPort(13);
request.setSocketAddress(new InetSocketAddress("localhost",13));

DatagramPacket response = new DatagramPacket(new byte[1024], 1024);

socket.send(request);
socket.receive(response);

byte[] data = response.getData();//接收数据
int offset = response.getOffset();//偏移量
int length = response.getLength();//数据长度
System.out.println(new String(data, offset,length));
}//UDP客户端


DatagramSocket类

无论是客户端或服务端都使用DatagramSocket类区别在于客户端指定匿名端口,服务端需要已知端口.

构造函数



public static void main(String[] args) throws Exception {
//随机端口1-65535
DatagramSocket socket1 = new DatagramSocket();
//随机端口1-65535
DatagramSocket socket2 = new DatagramSocket(0);
//指定222端口
DatagramSocket socket3 = new DatagramSocket(222);
}


发送与接收

一个客户端连接多个服务端



public static void main(String[] args) throws Exception {
DatagramSocket socket1 = new DatagramSocket(0);//客户端
socket1.send(new DatagramPacket(new byte[1], 1, new InetSocketAddress("localhost", 13)));//发送到本机13端口
socket1.send(new DatagramPacket(new byte[1], 1, new InetSocketAddress("localhost", 14)));//发送到本机14端口

DatagramPacket packet1 = new DatagramPacket(new byte[512], 512);
socket1.receive(packet1);//接收13端口回复
System.out.println(new String(packet1.getData(),0,packet1.getLength()));

DatagramPacket packet2 = new DatagramPacket(new byte[512], 512);
socket1.receive(packet2);//接收14端口回复
System.out.println(new String(packet2.getData(),0,packet1.getLength()));
}


一个服务端接收多个客户端



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(13);
DatagramPacket request = new DatagramPacket(new byte[1024], 1024);
DatagramPacket response = new DatagramPacket(new byte[1],0,1);
while (true) {
socket.receive(request);
byte[] data1 = request.getData();
int offset = request.getOffset();
int length = request.getLength();
System.out.println(new String(data1,offset,length));

InetAddress host = request.getAddress();
int port = request.getPort();
byte[] data = "udp server111111111".getBytes();
response.setData(data);
response.setLength(data.length);
response.setPort(port);
response.setAddress(host);
socket.send(response);
}
}//UDP服务端


关闭连接

关闭连接会释放UDP占用的端口,同时这也是一个很好的习惯.



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(0);
socket.close();
}//UDP客户端


查询监听端口

创建的匿名端口DatagramSocket时,可以调用getLocalPort()获取监听的端口.



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(0);
System.out.println(socket.getLocalPort());
}//UDP客户端


返回SocketAddress

没什么用的方法



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(0);
System.out.println(socket.getLocalSocketAddress());
}//UDP客户端


管理入站

以下服务端socket设置只接受localhost主机,22端口的数据.其他主机22端口的客户端将会被拒绝连接.



DatagramSocket socket = new DatagramSocket(13);
socket.connect(new InetSocketAddress("localhost",22));


Socket选项

SO_TIMEOUT:SO_TIMEOUT是receive()在抛出异常前等待入站数据报的时间,以毫秒计.它的值必须是非负数.使用setSoTime()改变,getSoTimeout()查看.这个值默认是10000毫秒.



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(13);
DatagramPacket request = new DatagramPacket(new byte[1024], 1024);
DatagramPacket response = new DatagramPacket(new byte[1], 0, 1);
while (true) {
socket.receive(request);
byte[] data1 = request.getData();
int offset = request.getOffset();
int length = request.getLength();
System.out.println(new String(data1, offset, length));

TimeUnit.SECONDS.sleep(10);

InetAddress host = request.getAddress();
int port = request.getPort();
byte[] data = "udp server111111111".getBytes();
response.setData(data);
response.setLength(data.length);
response.setPort(port);
response.setAddress(host);
socket.send(response);
}
}//UDP服务端



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
socket.setSoTimeout(10000);

InetAddress host = InetAddress.getByName("localhost");
byte[] bytes = "客户端1".getBytes();
DatagramPacket request = new DatagramPacket(bytes, bytes.length, host, 13);

DatagramPacket response = new DatagramPacket(new byte[1024], 1024);

socket.send(request);

System.out.println(socket.getSoTimeout());
socket.setSoTimeout(3);
socket.receive(response);

byte[] data = response.getData();//接收数据
int offset = response.getOffset();//偏移量
int length = response.getLength();//数据长度
System.out.println(new String(data, offset,length));
socket.close();
}//UDP客户端


SO_RCVBUF:它设置UDP接收数据缓冲区的大小,对于UDP,足够大的接收缓冲区很重要,因为缓冲区满时到达的UDP数据报会丢失.不过对于很多操作系统有自己的限制不允许你设置更大的值.如BSD系统的最大接收缓冲区约为52KB,Linux机器限制为64kb.其他系统可能增大为240kb.使用setReceiveBufferSize()设置大小,getReceiveBufferSize()获取大小,获取大小可能更有用些.



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
socket.setSoTimeout(10000);

InetAddress host = InetAddress.getByName("localhost");
byte[] bytes = "客户端1".getBytes();
DatagramPacket request = new DatagramPacket(bytes, bytes.length, host, 13);

DatagramPacket response = new DatagramPacket(new byte[1024], 1024);

socket.send(request);

System.out.println(socket.getReceiveBufferSize());
socket.setReceiveBufferSize(5);
socket.receive(response);

byte[] data = response.getData();//接收数据
int offset = response.getOffset();//偏移量
int length = response.getLength();//数据长度
System.out.println(new String(data, offset,length));
socket.close();
}//UDP客户端


SO_SENDBUF:设置发送缓冲区大小.调用方法为setSendBufferSize()设置,getSendBufferSize()获取.



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
socket.setSoTimeout(10000);

InetAddress host = InetAddress.getByName("localhost");
byte[] bytes = "客户端1".getBytes();
DatagramPacket request = new DatagramPacket(bytes, bytes.length, host, 13);

DatagramPacket response = new DatagramPacket(new byte[1024], 1024);

socket.send(request);

System.out.println(socket.getSendBufferSize());
socket.setReceiveBufferSize(5);
socket.receive(response);

byte[] data = response.getData();//接收数据
int offset = response.getOffset();//偏移量
int length = response.getLength();//数据长度
System.out.println(new String(data, offset,length));
socket.close();
}//UDP客户端


SO_REUSEADDR:SO_REUSEADDR用于控制允许多个数据报Socket绑定到相同的端口.需要在绑定端口之前设置setReuseAddress()



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(null);
socket.setReuseAddress(true);
System.out.println(socket.getReuseAddress());
socket.bind(new InetSocketAddress(13));
DatagramPacket request = new DatagramPacket(new byte[1024], 1024);
DatagramPacket response = new DatagramPacket(new byte[1], 0, 1);
while (true) {
socket.receive(request);
byte[] data1 = request.getData();
int offset = request.getOffset();
int length = request.getLength();
System.out.println(new String(data1, offset, length));



InetAddress host = request.getAddress();
int port = request.getPort();
byte[] data = "udp server111111111".getBytes();
response.setData(data);
response.setLength(data.length);
response.setPort(port);
response.setAddress(host);
socket.send(response);
}
}//UDP服务端



public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(null);
socket.setReuseAddress(true);
System.out.println(socket.getReuseAddress());
socket.bind(new InetSocketAddress(13));
DatagramPacket request = new DatagramPacket(new byte[1024], 1024);
DatagramPacket response = new DatagramPacket(new byte[1], 0, 1);
while (true) {
socket.receive(request);
byte[] data1 = request.getData();
int offset = request.getOffset();
int length = request.getLength();
System.out.println(new String(data1, offset, length));



InetAddress host = request.getAddress();
int port = request.getPort();
byte[] data = "udp server22222222".getBytes();
response.setData(data);
response.setLength(data.length);
response.setPort(port);
response.setAddress(host);
socket.send(response);
}
}//UDP服务端


SO_BROADCAST:选项控制是否允许一个Socket向广播地址收发包.setBoradcast().getBroadcast()默认是true打开状态.

IP_TOS:用于指定业务流类型.

DatagramChannel

DatagramChannel类相当于非阻塞UDP应用程序,就像SocketChannel和ServerSocketChannel用于非阻塞TCP应用程序一样.类似于SocketChannel和ServerSocketChannel,DatagramChannel是selectabelChannel的子类,可以注册到一个Selector中.

阻塞模式的DatagramChannel使用open()打开,bind()指定端口.receive()依然是读取数据,不过它将读取到的数据ByteBuffer而不是DatagramPacket.send()发送数据,也是使用ByteBuffer.以下示例是一个阻塞客户端.



public static void main(String[] args) throws Exception{
DatagramChannel channel = DatagramChannel.open();
channel.bind(new InetSocketAddress(13));
while (true){
ByteBuffer buffer = ByteBuffer.allocate(100);
SocketAddress client = channel.receive(buffer);
buffer.flip();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (buffer.hasRemaining()){
bos.write(buffer.get());
}
System.out.println(bos.toString());
buffer.clear();

buffer.put("顺丰到付".getBytes());
buffer.flip();
channel.send(buffer,client);
buffer.clear();
TimeUnit.SECONDS.sleep(5);
}
}//NIO服务端,阻塞模式


这里的客户端程序使用的也是DatagramChannel不过它使用write()代替send(),read()代替receive().效果是一样的.接收数据时需要注意如果数据量大于Buffer的容量则直接丢弃.



public static void main(String[] args)throws Exception {
DatagramChannel channel = DatagramChannel.open();
channel.connect(new InetSocketAddress("localhost",13));

ByteBuffer buffer = ByteBuffer.allocate(20);
buffer.put("中通到付".getBytes());
buffer.flip();
channel.write(buffer);
buffer.clear();


channel.read(buffer);
buffer.flip();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (buffer.hasRemaining()){
bos.write(buffer.get());
}

System.out.println(bos.toString());
buffer.clear();
}


非阻塞DatagramChannel服务端将Channel注册到Selector里.Selector的用法与TCP一致.但是需要注意,这里没有accept()所以服务端注册的事件是READ,当READ就绪后重新注册一个WRITE.这里有一个问题没有解决,WRITE时无法获取客户端的IP+Port只能写死.还需要研究.



public static void main(String[] args) throws Exception {
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(false);
channel.bind(new InetSocketAddress(666));

Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_READ);

while (true) {
selector.select();
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();

if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(100);
DatagramChannel cr =(DatagramChannel) key.channel();
cr.register(selector,SelectionKey.OP_WRITE);
channel.receive(buffer);
buffer.flip();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (buffer.hasRemaining()) {
bos.write(buffer.get());
}
System.out.println(bos.toString());
buffer.clear();
}
if (key.isWritable()) {
ByteBuffer buffer = ByteBuffer.allocate(100);
buffer.put("顺丰包邮".getBytes());
buffer.flip();
channel.send(buffer,new InetSocketAddress("localhost",999));
buffer.clear();
key.cancel();
}
}
}
}//NIO服务端,非阻塞模式



public static void main(String[] args)throws Exception {
DatagramChannel channel = DatagramChannel.open();
channel.bind(new InetSocketAddress(999));
channel.connect(new InetSocketAddress("localhost",666));

ByteBuffer buffer = ByteBuffer.allocate(20);
buffer.put("中通到付".getBytes());
buffer.flip();
channel.write(buffer);
buffer.clear();

channel.read(buffer);
buffer.flip();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (buffer.hasRemaining()){
bos.write(buffer.get());
}
System.out.println(bos.toString());
buffer.clear();
}