• 周五. 3月 29th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

JAVA网络编程-UDP

admin

11月 28, 2021

客户端与服务端示例

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();
    }

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注