zoukankan      html  css  js  c++  java
  • 基于UDP协议的网络编程

    UDP协议基础:

     UDP(User Datagram Protocol)协议,是用户数据报协议,主要用来支持那些需要在计算机之间传输数据的网络连接。它是一种面向非连接的协议,面向非连接指的是双方在正式通信前不必于对方先建立连接关系,不管对方状态就直接发送。至于对方是否可以接收到这些数据内容,UDP协议无法控制。因此UDP协议是一种不可靠的协议,适用于一次只传送少量数据,对可靠性要求不高的应用环境。

     UDP协议直接位于IP协议之上,和TCP协议一样输入传输层协议。

     UDP协议与TCP协议的简单对比:

      TCP协议:可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大

      UDP协议:不可靠,差错控制开销小,传输大小限制在64KB以下,不需要建立连接。

    使用DatagramSocket 发送,接收数据:

      JAVA使用DatagramSocket代表UDP协议的Socket,它的唯一作用是接收和发送数据报。

      构造方法如下:

        DatagramSocket(): 创建一个DatagramSocket实例,并将对象绑定到本地计算机默认的IP地址,本机所有可用端口中随机选择某个端口

        DatagramSocket(int port) :创建一个指定端口,本机默认IP地址的DatagramSocket实例

        DatagramSocket(int port,InetAddress laddr):创建一个指定ip地址,指定端口的DatagramSocket实例

      DatagramSocket主要方法:

        receive(DatagramPacket p):从该DatagramSocket对象接收数据报

        send(DatagramPacket p):以该DatagramSocket对象向外发送数据报

    DatagramPacket:

      构造方法如下:

        DatagramPakcet(byte[] buf,int length):以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据放入buf中,最多放入length个字节。

        DatagramPacket(byte[] buf,int length,InetAddress addr,int port): 以一个包含数据的数组来创建DatagramPacket发送对象,创建该DatagramPacket对象时还指定了IP指定和端口---这就决定了该数据包的目的地。

        DatagramPacket(byte[] buf,int offset,int length): 以一个空数组来创建DatagramPacket对象,并指定接收到的数据放入buf数组中从offset开始,最多放length个字节。

        DatagramPacket(byte[] buf,int offset,int length,InetAddress addr,int port):创建DatagramPacket发送对象,指定发送buf数组中从offset开始,总共length字节的数组。

      当接收端接收到了一个DatagramPacket对象后,相向该数据报的发送者”反馈“一些信息,但由于UDP协议是面向非连接的,所以接收者并不知道每个数据报由谁发送过来的,但程序可以调用DatagramPacket的如下方法来获取发送者的IP地址和端口

       1. InetAddress getAddress(): 当程序准备发送此数据报时,该方法返回此数据报对应目标机器的IP地址;当程序刚收到一份数据报时,该该方法返回该数据报发送者的IP地址。

       2. int getPort(): 与getAddress类似,不过getAddress返回的是IP地址,而getPort返回的是端口.

       3. SocketAddress getSocketAddress(): 与getAddress类型,不过getSocketAddress()返回的是SocketAddress对象。该对象实际上就是一个IP地址和一个端口号。

    尽管UDP协议没有明确的区分服务端和客户端,事实上,我们通常将固定ip以及固定端口的DatagramSocket对象所在的程序被成为服务端,因为该DatagramSocket可以主动接收客户端数据。

     以下代码使用DatagramSocket实现UDP协议通信

    通信实体1 ,用于接收其他通信实体的数据报,以及对发送数据报的目的地址反馈

    class UDPServer{
        public static final int PORT = 30000;
        //定义每个数据报的大小最大为4KB
        public static final int DATA_LEN = 4096;
        //定义接收网络数据的字节数组
        byte[] inBuff = new byte[DATA_LEN];
        //以指定字节数组创建准备接收数据的DatagramPacket对象
        private DatagramPacket inPacket = new DatagramPacket(inBuff,inBuff.length);
        //定义一个用于发送的DatagramPacket
        private DatagramPacket outPacket;
        //定义一个字符串数组,服务端发送该数组的元素
        String[] books = new String[]{"book_1","book_2","book_3","book_4"};
        public void init(){
            try {
                //创建DatagramSocket对象
                DatagramSocket socket  = new DatagramSocket(PORT);
                //采用循环接收数据
                for(int i=0;i<1000;i++){
                    //读取Socket中的数据,读到的数据放入inPacket封装的数组里
                    socket.receive(inPacket);
                    //判断inPacket.getData()和inBuff是否是同一数组
                    System.out.println(inBuff == inPacket.getData());
                    //将接收到的内容转换成字符串后输出
                    System.out.println(new String(inBuff,0,inPacket.getLength()));
                    //从字符串数组中取出一个元素作为发送数据
                    byte[] sendData = books[i%4].getBytes();
                    /**
                     * 以指定的字节数组作为发送数据,
                     * 以刚接收到的DatagramPacket的源SocketAddress作为目标SocketAddress创建DatagramPacket
                     * */
                    outPacket = new DatagramPacket(sendData,sendData.length,inPacket.getSocketAddress());
                    socket.send(outPacket);
                }
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    public class Net{
      public static void main(String[] args){
                //UDP协议
                System.out.println("启动通信实体(服务端)");
                new UDPServer().init();
      }
    }

    通信实体2,用于对指定ip以及指定端口号的通信实体进行发送数据报,并接收来自指定IP地址和端口号的反馈数据报

    class UDPClient{
        //定义发送数组的目的地(ip地址与端口)
        public static final int DEST_ROPT = 30000;
        public static final String DEST_IP = "127.0.0.1";
        //定义每一个发送数据的大小,为4KB
        private static final int DATA_LEN = 4096;
        byte[] inBuff = new byte[DATA_LEN];
        //以指定字节数组创建准备接收数据的DatagramPacket对象
        private DatagramPacket inPacket = new DatagramPacket(inBuff,inBuff.length);
        //定义一个用于发送的DatagramPacket
        private DatagramPacket outPacket = null;
    
        public void init(){
            try {
                //创建一个DatagramSocket,使用随机端口
                DatagramSocket datagramSocket = new DatagramSocket();
                //初始化发送用的DatagramPacket,它包含一个长度为0的字节数组,指定端口与IP地址
                outPacket =new DatagramPacket(new byte[0],0,InetAddress.getByName(DEST_IP),DEST_ROPT);
                //创建键盘输入流
                Scanner scan = new Scanner(System.in);
                //不断地读取键盘输入
                while(scan.hasNextLine()){
                    //将键盘输入的一行字符串转成字节数组
                    byte[] sendByte =scan.nextLine().getBytes();
                    //设置发送用的DatagramPacket对象
                    outPacket.setData(sendByte);
                    //发送数据报
                    datagramSocket.send(outPacket);
                    //读取Socket中的数据,读的数据放入在inPacket所封装的字节数组中
                    datagramSocket.receive(inPacket);
                    //打印数据
                    System.out.println(new String(inBuff,0,inPacket.getLength()));
                }
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    public class MyClient {
      public static void main(String[] args){
                System.out.println("启动通信实体(客户端)");
            new UDPClient().init();
      }  
    }

    运行通信实体1,以及运行多个通信实体2,并在多个通信实体2控制台中输入字符串,通信实体1控制台会打印接收来自各个不同通信实体2的数据,同时各个通信实体2也会收到来自通信实体的反馈。

    使用MulticastSocket实现多点发送(多点广播)

    在上述程序中,我们可以看到通信实体1对应着多个通信实体2,并且单个通信实体2发送数据给通信实体1,是不会让其他通信实体2所得知的。倘若我们想让所有的通信实体2知道某个通信实体发送给通信实体1的数据,我们可以通过JAVA的MulticastSocket类来实现

     MulticastSocket:该类是DatagramSocket的子类,作用是可以将数据报以广播方式发送到多个通信实体。若要使用多点发送(多点广播),则需要让一个数据报标有一组目标主机地址,当数据报发出后,整个组的所有主机都能收到该数据报,不仅如此,MulticastSocket既可以接收广播也可以发送广播。

     IP多点发送(多点广播)实现了将单一信息发送到多个通信实体(接收者),其思想是设置一组特殊网络地址作为多点广播地址,每一个多点广播地址都被看做一个组,通信实体需要在发送或者接收广播信息之前,加入到该组即可。同时IP协议为多点广播提供了这批特殊的IP地址,这些IP地址的范围是224.0.0.0 至 239.255.255.255,如下示意图:

      

      MulticastSocket的构造方法:

       1. MulticastSocket() :使用本机默认IP地址,随机端口来创建MulticastSocket对象

       2. MulticastSocket(int port):使用本机默认IP地址以及指定端口来创建MulticastSocket对象

       3. MulticastSocket(SocketAddress socketAddress):使用指定IP地址以及指定端接口来创建MulticastSocket对象

      Ps:若创建仅用于发送数据报的MulticastSocket对象,则使用默认IP地址,随机端口即可;反之,若创建用于接收数据报的MulticastSocket对象,则必须指定端口,否则发送方无法确定发送数据报的目标端口 

     MulticastSocket的方法:

       1.joinGroup(InetAddress multicastAddr):将该MulticastSocket加入指定的多点广播地址

       2.leaveGroup(InetAddress multicastAddr):让MulticastSocket离开指定的多点广播地址

       3.setInterface():强制MulticastSocket使用指定的网络端口

       4.getInterface():查询MulticastSocket监听的网络端口

       5. seTimeToLive(int ttl):设置数据报最多可以跨国多少个网络,以下是ttl值与对应网络

          ttl = 0   , 数据报停留在本地

          ttl = 1   , 数据报停留在本地局域网,默认

          ttl = 32 , 数据报停留在本站点的网络上

          ttl = 64 , 数据报停留在本地区的网络

          ttl = 128 ,数据报停留在本大洲的网络

          ttl = 255 ,数据报可以发送所有的地方

     Ps:创建完MulticastSocket对象后,还需要将该MulticastSocket加入只当的多点广播地址中,方法一为加入指定组,方法二为离开指定组。同时MulticastSocket用于发送与接收的方法与DatagramSocket的发送与接受方法一致。

    通过MulticastSocket实现多人聊天室。实现该聊天室只需用到一个MulticastSocket,以及两个线程。MulticastSocket实现接收与发送数据报,一个线程用于接收广播数据报,另外一个线程用于向MulticastSocket发送数据报,代码如下:

    class MulticastSocketThread implements Runnable{
    
        //使用常量作为本程序的多点广播IP地址
        private static final String BROADCAST_IP = "230.0.0.1";
        //使用常量作为本程序的多点广播目的地端口
        private static final int BROADCAST_PORT = 30000;
        //定义每个数据报的大小最大为4KB
        public static final int DATA_LEN = 4096;
        //定义本程序的MulticastSocket实例
        private MulticastSocket multicastSocket = null;
        private InetAddress broadcastAddress = null;
        private Scanner scan = null;
        //定义接收网络数据的字节数组
        byte[] inBuff = new byte[DATA_LEN];
        //定义以指定字节数组来创建接收数据的DatagramPacket对象
        private DatagramPacket inPack = new DatagramPacket(inBuff, inBuff.length);
        //定义一个用于发送的DatagramPacket对象
        private DatagramPacket outPacket = null;
        
        public void init(){
            try{
                //创建键盘输入流
                Scanner scan = new Scanner(System.in);
                //创建用于发送,接收数据的MulticastSocket对象
                multicastSocket = new MulticastSocket(BROADCAST_PORT);
                broadcastAddress = InetAddress.getByName(BROADCAST_IP);
                //将multicastSocket加入多点广播地址
                multicastSocket.joinGroup(broadcastAddress);
                //设置本MulticastSocket发送的数据报会被会送到本身,该方法设置true时,该程序接收不到任何信息,
           //设置false时,与不调用该方法的运行结果是一致的,因此不太清楚该方法的用法
    multicastSocket.setLoopbackMode(false); //初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0],0,broadcastAddress,BROADCAST_PORT); //启动线程 new Thread(this).start(); //不断读取键盘输入 while(scan.hasNextLine()){ //将从键盘输入的字符串转化成字节数据 String line = scan.nextLine(); byte[] buff = line.getBytes(); //设置发送用的DatagramPacket里的字节数据 outPacket.setData(buff); //发送数据报 multicastSocket.send(outPacket); } }catch(Exception e){System.out.println("是否用异常");}finally { multicastSocket.close(); } } @Override public void run() { // TODO Auto-generated method stub try{ while(true){ //读取multicastSocket中的数据,读到的数据放在inPacket所封装的字节数组里 multicastSocket.receive(inPack); //打印输出从socket中读取的内容 System.out.println("聊天信息:"+new String(inPack.getData(),0,inPack.getLength())); } }catch(IOException e){ e.printStackTrace(); try{ if(multicastSocket != null){ //让该muticastSocket离开多点IP广播地址 System.out.println("离开"); multicastSocket.leaveGroup(broadcastAddress); multicastSocket.close(); //关闭该muticastSocket对象 } }catch(IOException e1){e1.printStackTrace();} } } } public class Net{   public static void main(String[] args){ System.out.println("MulticastSocket实现多点广播"); new MulticastSocketThread().init();   } }

    多运行以上程序,运行结果如下:

    1.打开3个控制台,3个控制台的初始状态如下:

    2.对第一个控制台,输入任意字符后回车,运行状态如下: 

     控制台1:  控制台2    控制台3 

    3. 对第三个控制台,输入任意字符后回车,运行状态如下:

    控制台1: 控制台2    控制台3

    总结: 无论是DatagramSocket还是MuticastSocket都可以实现UDP协议的通信,其DatagramSocket适用于私信,而MuticastSocket适用于群体广告。

    全部代码如下:

    // Net类:
    /**
     * UDP协议
     * */
    class UDPServer{
        public static final int PORT = 30000;
        //定义每个数据报的大小最大为4KB
        public static final int DATA_LEN = 4096;
        //定义接收网络数据的字节数组
        byte[] inBuff = new byte[DATA_LEN];
        //以指定字节数组创建准备接收数据的DatagramPacket对象
        private DatagramPacket inPacket = new DatagramPacket(inBuff,inBuff.length);
        //定义一个用于发送的DatagramPacket
        private DatagramPacket outPacket;
        //定义一个字符串数组,服务端发送该数组的元素
        String[] books = new String[]{"book_1","book_2","book_3","book_4"};
        public void init(){
            try {
                //创建DatagramSocket对象
                DatagramSocket socket  = new DatagramSocket(PORT);
                //采用循环接收数据
                for(int i=0;i<1000;i++){
                    //读取Socket中的数据,读到的数据放入inPacket封装的数组里
                    socket.receive(inPacket);
                    //判断inPacket.getData()和inBuff是否是同一数组
                    System.out.println(inBuff == inPacket.getData());
                    //将接收到的内容转换成字符串后输出
                    System.out.println(new String(inBuff,0,inPacket.getLength()));
                    //从字符串数组中取出一个元素作为发送数据
                    byte[] sendData = books[i%4].getBytes();
                    /**
                     * 以指定的字节数组作为发送数据,
                     * 以刚接收到的DatagramPacket的源SocketAddress作为目标SocketAddress创建DatagramPacket
                     * */
                    outPacket = new DatagramPacket(sendData,sendData.length,inPacket.getSocketAddress());
                    socket.send(outPacket);
                }
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    class MulticastSocketThread implements Runnable{
    
        //使用常量作为本程序的多点广播IP地址
        private static final String BROADCAST_IP = "230.0.0.1";
        //使用常量作为本程序的多点广播目的地端口
        private static final int BROADCAST_PORT = 30000;
        //定义每个数据报的大小最大为4KB
        public static final int DATA_LEN = 4096;
        //定义本程序的MulticastSocket实例
        private MulticastSocket multicastSocket = null;
        private InetAddress broadcastAddress = null;
        private Scanner scan = null;
        //定义接收网络数据的字节数组
        byte[] inBuff = new byte[DATA_LEN];
        //定义以指定字节数组来创建接收数据的DatagramPacket对象
        private DatagramPacket inPack = new DatagramPacket(inBuff, inBuff.length);
        //定义一个用于发送的DatagramPacket对象
        private DatagramPacket outPacket = null;
        
        public void init(){
            try{
                //创建键盘输入流
                Scanner scan = new Scanner(System.in);
                //创建用于发送,接收数据的MulticastSocket对象
                multicastSocket = new MulticastSocket(BROADCAST_PORT);
                broadcastAddress = InetAddress.getByName(BROADCAST_IP);
                //将multicastSocket加入多点广播地址
                multicastSocket.joinGroup(broadcastAddress);
                //设置本MulticastSocket发送的数据报会被会送到本身
                multicastSocket.setLoopbackMode(false);
                //初始化发送用的DatagramSocket,它包含一个长度为0的字节数组
                outPacket = new DatagramPacket(new byte[0],0,broadcastAddress,BROADCAST_PORT);
                //启动线程
                new Thread(this).start();
                //不断读取键盘输入
                while(scan.hasNextLine()){
                    //将从键盘输入的字符串转化成字节数据
                    String line = scan.nextLine();
                    byte[] buff = line.getBytes();
                    //设置发送用的DatagramPacket里的字节数据
                    outPacket.setData(buff);
                    //发送数据报
                    multicastSocket.send(outPacket);
                }
            }catch(Exception e){System.out.println("是否用异常");}finally {
                multicastSocket.close();
            }
        }
        
        @Override
        public void run() {
            // TODO Auto-generated method stub
            try{
                while(true){
                    //读取multicastSocket中的数据,读到的数据放在inPacket所封装的字节数组里
                    multicastSocket.receive(inPack);
                    //打印输出从socket中读取的内容
                    System.out.println("聊天信息:"+new String(inPack.getData(),0,inPack.getLength()));
                }
            }catch(IOException e){
                e.printStackTrace();
                try{
                    if(multicastSocket != null){
                        //让该muticastSocket离开多点IP广播地址
                        System.out.println("离开");
                        multicastSocket.leaveGroup(broadcastAddress);
                        multicastSocket.close(); //关闭该muticastSocket对象
                    }
                }catch(IOException e1){e1.printStackTrace();}
            }
        }
    }
    public class Net{
      public static void main(String[] args){
                //UDP协议
    //            System.out.println("启动通信实体(服务端)");
    //            new UDPServer().init();
                System.out.println("MulticastSocket实现多点广播");
                new MulticastSocketThread().init();    
      }
    }
    
    //MyClient类
    /**
     * UDP协议网络
     * */
    class UDPClient{
        //定义发送数组的目的地(ip地址与端口)
        public static final int DEST_ROPT = 30000;
        public static final String DEST_IP = "127.0.0.1";
        //定义每一个发送数据的大小,为4KB
        private static final int DATA_LEN = 4096;
        byte[] inBuff = new byte[DATA_LEN];
        //以指定字节数组创建准备接收数据的DatagramPacket对象
        private DatagramPacket inPacket = new DatagramPacket(inBuff,inBuff.length);
        //定义一个用于发送的DatagramPacket
        private DatagramPacket outPacket = null;
    
        public void init(){
            try {
                //创建一个DatagramSocket,使用随机端口
                DatagramSocket datagramSocket = new DatagramSocket();
                //初始化发送用的DatagramPacket,它包含一个长度为0的字节数组,指定端口与IP地址
                outPacket =new DatagramPacket(new byte[0],0,InetAddress.getByName(DEST_IP),DEST_ROPT);
                //创建键盘输入流
                Scanner scan = new Scanner(System.in);
                //不断地读取键盘输入
                while(scan.hasNextLine()){
                    //将键盘输入的一行字符串转成字节数组
                    byte[] sendByte =scan.nextLine().getBytes();
                    //设置发送用的DatagramPacket对象
                    outPacket.setData(sendByte);
                    //发送数据报
                    datagramSocket.send(outPacket);
                    //读取Socket中的数据,读的数据放入在inPacket所封装的字节数组中
                    datagramSocket.receive(inPacket);
                    //打印数据
                    System.out.println(new String(inBuff,0,inPacket.getLength()));
                }
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    public class MyClient{
    public static void main(String[] args){
            //UDP
    //        System.out.println("启动通信实体(客户端)");
    //        new UDPClient().init();
    }
    }
    View Code
  • 相关阅读:
    grid 布局
    mongoose
    Nestjs 上传文件
    Nestjs 设置静态文件,public
    Centos 为Nginx 搭建https
    react组件
    namecheap 添加二级域名
    electron+react
    遍历文件,读取.wxss文件,在头部添加一条注释
    react 中的绑定事件
  • 原文地址:https://www.cnblogs.com/hjlin/p/11421488.html
Copyright © 2011-2022 走看看