• 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();
        }
  • 相关阅读:
    Python函数参数学习笔记
    Python基础笔记
    winform碎片
    常用sql语句
    《零基础入门学习Python》学习过程笔记【021匿名函数】
    统计下边这个长字符串中各个字符出现的次数并找到小甲鱼送给大家的一句话
    《零基础入门学习Python》学习过程笔记【020函数的局部变量和全全局变量内部函数和闭包】
    编写一个函数,分别统计出传入字符串参数(可能不止一个参数)的英文字母,空格,数字和其他字符的个数
    写一个函数,判断一个字符串是否为回文联
    《零基础入门学习Python》学习过程笔记【019函数返回值问题】
  • 原文地址:https://www.cnblogs.com/zumengjie/p/15160744.html
Copyright © 2020-2023  润新知