基于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

 

posted @ 2019-08-27 23:37  HJLのH  阅读(378)  评论(0编辑  收藏  举报