JAVA网络编程-UDP
客户端与服务端示例
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(); }