• 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();
        }
  • 相关阅读:
    declare set声明注意
    Winform 的dadagridview控件的修改操作
    VS2010,VS2008,VS2005;工程之间的转换
    C#程序跨平台?
    上网黑色护眼,设置浏览器黑色风格
    AutoCompleteSource从文件里读取自动填充内容
    两个checkbox的控件控制操作只能选其一
    《博客园精华集CLR/C#分册》第三轮筛选结果 转载
    TransactSQL 示例 查询某个数据库内的所有表的记录行数及其总和
    EF 4.1中内部经常提交的 exec sp_reset_connection 的用途原来是为了重用池中的连接
  • 原文地址:https://www.cnblogs.com/zumengjie/p/15160744.html
Copyright © 2020-2023  润新知