Java IO与NIO的UDP开发

来源:互联网 发布:淘宝九块九包邮在哪里 编辑:程序博客网 时间:2024/05/29 17:26

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()方法则没有这一限制。

 

 

原创粉丝点击