Java IO与NIO的UDP开发

UDP协议与TCP的协议

UDP优点:速度快

--这里不作过多的累赘,估计都了略了解一二

 

先说说IO中的UDP:

1、java.util.DatagramSocket:负责接收和发送UDP数据报。

2、java.util.DatagramPacket:表示UDP数据报。

 

作为服务端:DatagramSocket必须与本地主机的ip和端口进行绑定,同时都可以接收任意远程的UDP数据,在DatagramPacket中它一定包含了远的主机ip和端口信息。

作为客户端:只是少了服务端的bind(int port),使用无参构造函数时,则就像TCP中使用任意一个空闲的UDP端口进行发送DatagramPacket。

DatagramPacket类包括以下属性:
data:表示数据报的数据缓冲区。
offset:表示数据报的数据缓冲区的起始位置。
length:表示数据报的长度。
address:对于用于发送的数据报,address属性表示数据报的目标地址。对于用于接收的数据报,address属性表示发送者的地址。
port:对于用于发送的数据报,address属性表示数据报的目标UDP端口。对于用于接收的数据报,port属性表示接收者的UDP端口。

它们的关系如下图:

 

 

3、UDP协议是无连接的协议

客户端的DatagramSocket与服务器端的DatagramSocket不存在一一对应关系,两者无需建立连接,就能交换数据报。

DatagramSocket提供了接收和发送数据报的方法:
public void receive(DatagramPacket dst)throws IOException //接收数据报
public void send(DatagramPacket src)throws IOException //发送数据报

 

4、Java IO的UDP服务端核心代码:

/**
     * 接收
     */
    @Test
    public void testRec() {
        try {
            InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), port);
            DatagramSocket ds = new DatagramSocket(address);
            /**
             * 大多数的udp传输都是8k
             */
            byte[] buffer = new byte[8 * 1024];
            DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
            //如果在接收端指定datagrampacket的address则意思是只接收指定ip和端口的udp数据报文
            //DatagramPacket dp = new DatagramPacket(buffer, buffer.length, new InetSocketAddress("127.0.0.1", 4444));

            while (true) {
                ds.receive(dp);
                System.out.println("dp.getLength:" + dp.getLength());
                System.out.println(new String(dp.getData(),0, dp.getLength()));
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 

5、Java IO的UDP客户端核心代码:

    /**
     * 指定发送ip
     */
    @Test
    public void testSendSpecifyIp() {

        try {
            // 指定目标地址
            InetAddress address = InetAddress.getLocalHost();
            // 无参构造函数,使用随机端口发送
            DatagramSocket ds = new DatagramSocket();
            String content = "Hi udp sever, testSendSpecifyIp!";
            // 数据报文
            DatagramPacket datagramPacket = new DatagramPacket(content.getBytes(), content.length(), address, port);
            System.out.println(ds.getLocalAddress());
            // 发送
            ds.send(datagramPacket);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


 6、如果需要广播的发送则在指定目标ip中改成广播地址即可:

    /**
     * 本地广播发送
     */
    @Test
    public void testSendLocalBrocast(){
        // 本网段的广播地址
        InetSocketAddress address = new InetSocketAddress("172.16.27.255",port);
        try {
            // 匿名udp socket
            DatagramSocket ds = new DatagramSocket();
            String content = "Test sending local broacast... msg=testSendLocalBrocast !";
            // 广播报文
            DatagramPacket dp = new DatagramPacket(content.getBytes(), content.length(), address);
            ds.send(dp);
        } catch (Exception e) {
           e.printStackTrace();
        }
    }

附:广播地址的计算(广播地址B与IP地址I,子网掩码M的关系为:B = (I & M)|~M)

第一步:获得网段(I&M)

第二步:将子网俺码按位取反

第三步:将1、2步进行相或

最简单的例子:比如宿舍中本机ip为192.168.1.100,子网掩码:255.255.255.0,求广播地址。如下图(为了直观图中使用十进制,实际需要转为二进制进行计算)

 

7、组播MulticastSocket

它继承DatagramSocket,所以它们是比较相像的,用法基本相同,不过需要在发送前加入组,不需要后需要离开组。

组播的概念,通过对比简单解释一下,避免过多的书面语:

(1)单播:即是基于一对一的思想进行传播,发送端需要指定ip与端口,接收端同样需要指定需要接收的ip,port(一对一,在DatagramPackage中指定);

(2)单播:即是基于多对一的思想进行传播,(不错依然是单播)多个发送端口需要指定ip与port,接收端不需要指定ip,port(多对一)。

(3)广播:即是基于一对多r的思想进行传播,先说说大大家都清楚的广播,顾名思义广播即是对网内所有的ip进行发送数据,网络对其中每一台主机发出的信号都进行无条件复制并转发,所有主机都可以接收到所有信息(不管你是否需要),缺点浪费资源。

(4)多播:只能说在需求状况下,它基于单播与广播之间,属于按需分配,只有都指定了组播地址和端口才能发送与接收。如果偏要说它属于“一对多还,多对多,抑者是一对一”,只能说它的思想还是(多对一对多)在电子商务领域有一个叫c2b2c的概念,中间那个“一”就是组播ip,发送端,根据组播ip发送,接收端根据组播ip接收。

附:http://www.360doc.com/content/07/0801/22/38435_648534.shtml

(5)看代码说话:

package com.jasic;

import org.junit.Test;

import java.net.*;

/**
 * User: Jasic
 * Date: 12-12-5
 */
public class MultiUdpIo {

    /**
     * 组播的地址是保留的D类地址从224.0.0.0—239.255.255.255
     * 224.0.0.0—244.0.0.255 只能用于局域网中路由器是不会转发的,
     * 并且224.0.0.1是所有主机的地址,224.0.0.2所有路由器的地址,
     * 224.0.0.5所有ospf路由器的地址,224.0.13是PIMv2路由器的地址;
     * 239.0.0.0—239.255.255.255 私有地址(和192.168.x..x这类地址类似);
     * 224.0.1.0—238.255.255.255 用与Internet上的。
     */
    public static String mul_ip1 = "239.0.0.1";
    public static int mul_port1 = 4441;

    public static String mul_ip2 = "239.0.0.2";
    //这里可以指定不同的端口,改的同时如果还需要接收到消息2,则需要改testMultiRec中multicastSocket的端口咯
    public static int mul_port2 = mul_port1;

    /**
     * 组播发送测试
     */
    @Test
    public void testMultiSend(){
        try {
            // 组播ip1
            InetAddress a1 = InetAddress.getByName(mul_ip1);
            // 组播ip2
            InetAddress a2 =InetAddress.getByName(mul_ip2);

            // socket组(随意绑定一个空闲udp端,这里也可以使用匿名构造函数)
            MulticastSocket multicastSocket = new MulticastSocket();

            multicastSocket.joinGroup(a1);
            multicastSocket.joinGroup(a2);

            String content1 = "a1:你在哪里,亲。。。?";
            String content2 = "a2:你死哪里去了,亲。。。?";

            DatagramPacket packet1 = new DatagramPacket(content1.getBytes(),content1.getBytes().length,a1,mul_port1);
            DatagramPacket packet2 = new DatagramPacket(content2.getBytes(),content2.getBytes().length,a2,mul_port2);

            multicastSocket.send(packet1);
            System.out.println("数据[" + content1 + "]发送给组播地址:" + a1 + ":" + mul_port1 + ", 接收端需指定这个ip与端口");

            multicastSocket.send(packet2);
            System.out.println("数据[" + content2 + "]发送给组播地址:" + a2 + ":" + mul_port2 + ", 接收端需指定这个ip与端口");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 组播接收测试
     */
    @Test
    public void testMultiRec(){

        try {
            // 组播ip1
            InetAddress a1 = InetAddress.getByName(mul_ip1);
            // 组播ip2
            InetAddress a2 =InetAddress.getByName(mul_ip2);

            // 一个MulticastSocket只能监听一个udp端口喔
            // socket组(随意绑定一个空闲udp端,这里也可以使用匿名构造函数)
            MulticastSocket multicastSocket = new MulticastSocket(mul_port1);

            // 同一个端口监听两个组网地址
            multicastSocket.joinGroup(a1);
            multicastSocket.joinGroup(a2);

            byte[] buffer = new byte[8*1024];
            DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
          while (true){
              multicastSocket.receive(packet);
              System.out.println("接收到长度:" + packet.getLength());
              System.out.println("接收内容:" + new String(packet.getData(),0,packet.getLength()));
          }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


8、Java NIO的UDP接收

public class UdpNio {
    private static int port = 4444;
    @Test
    public void rec() {
        Selector selector = null;
        DatagramChannel dc = null;
        try {
            selector = Selector.open();
            dc = DatagramChannel.open();

            InetSocketAddress address = new InetSocketAddress(port);
            dc.bind(address);
            dc.configureBlocking(false);
            dc.register(selector, SelectionKey.OP_READ);

            while (true) {

                int count = selector.select();

                if (count <= 0) {
                    continue;
                }

                Iterator<SelectionKey> it = selector.selectedKeys().iterator();

                while (it.hasNext()) {

                    SelectionKey key = it.next();
                    it.remove();
                    if (key.isReadable()) {

                        DatagramChannel datagramChannel = (DatagramChannel) key.channel();

                        ByteBuffer temp = ByteBuffer.allocate(10);
                        int l = 0;

                        System.out.println("通道是否打开:" + datagramChannel.isOpen());
                        System.out.println("通道是否连接:" +datagramChannel.isConnected());

                        ByteBuffer bu = ByteBuffer.allocate(8 * 1024);
                        System.out.println("数据包源地址" + datagramChannel.receive(bu));
                        int pos = bu.position();
                        bu.flip();
                        System.out.println(new String(bu.array(),0, pos));

                        //记录NotYetConnectedException
                        System.out.println("这里不能使用read(temp),否则会抛NotYetConnectedException");
                        // 在这里我直接copy,TCP的测试代码,后来出错才发觉,UDP不能使用read,因为它根本不存连接概念。
                        //while ((l = datagramChannel.read(temp)) != -1 && l!=0) {
//                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


 

9、 其实在UDP的NIO中与TCP的基本相同,但记住一点,它是没连接的概念的。


DatagramChannel的write()与send()方法的区别在于:

(1)write()方法要求DatagramChannel已经建立连接,也就是说,程序在调用DatagramChannel的write()方法之前,要求先调用connect()方法使通道与特定的远程接收方连接。而send()方法则没有这一限制。

(2)在非阻塞模式下,write()方法不保证把ByteBuffer内的所有剩余数据作为一个数据报发送。假如ByteBuffer的剩余数据为r,实际发送的字节数为n,那么0<=n<=r。而send()方法总是把ByteBuffer内的所有剩余数据作为一个数据报发送。      

 


DatagramChannel的read()与receive()方法的区别在于:

read()方法要求DatagramChannel已经建立连接,也就是说,程序在调用DatagramChannel的read()方法之前,要求先调用connect()方法使通道与特定的远程发送方连接。

而receive()方法则没有这一限制。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值