《Android智能穿戴设备开发指南》——第6章,第6.3节使用UDP协议传递数据

本节书摘来自异步社区《Android智能穿戴设备开发指南》一书中的第6章,第6.3节使用UDP协议传递数据,作者 王长青,更多章节内容可以访问云栖社区“异步社区”公众号查看

6.3 使用UDP协议传递数据
Android智能穿戴设备开发指南
Java为我们提供了DatagramSocket对象作为基于UDP协议的Socket,可以使用DatagramPacket代表DatagramSocket发送或接收的数据报。本节将详细讲解使用UDP协议传递数据的内容。

6.3.1 使用DatagramSocket进行数据交互
DatagramSocket本身只是码头,不能产生I/O流,其唯一的功能是接收和发送数据报。Java语言使用DatagramPacket代表数据报,DatagramSocket的接收和发送数据功能都是通过DatagramPacket对象实现的。

DatagramSocket中有如下3个构造器。

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

② DatagramSocket(int prot)。负责创建一个DatagramSocket实例,并将该对象绑定到本机默认IP地址、指定端口。

③ DatagramSocket(int port, InetAddress laddr)。负责创建一个DatagramSocket实例,并将该对象绑定到指定IP地址、指定端口。

在Java程序中,通过上述任意一个构造器即可创建一个DatagramSocket实例。在创建服务器时必须创建指定端口的DatagramSocket实例,目的是保证其他客户端可以将数据发送到该服务器。一旦得到了DatagramSocket实例,就可以通过下面的两个方法接收和发送数据。

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

② send(DatagramPacket p)。从该DatagramSocket对象向外发送数据报。

在使用DatagramSocket发送数据报时,DatagramSocket并不知道将该数据报发送到哪里,而是由DatagramPacket自身决定数据报的目的地。就像码头并不知道每个集装箱的目的地,码头只是将这些集装箱发送出去,而集装箱本身包含了自身的目的地。

当Client/Server程序使用UDP协议时,实际上并没有明显的服务器和客户端,因为两方都需要先建立一个DatagramSocket对象,用来接收或发送数据报,然后使用DatagramPacket对象作为传输数据的载体。通常固定IP、固定端口的DatagramSocket对象所在的程序被称为服务器,因为该DatagramSocket可以主动接收客户端数据。

DatagramPacket中包含如下常用的构造器。

① DatagramPacket(byte buf[],int length)。以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据。

② 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 address, int port)。创建一个用于发送的DatagramPacket对象,也多指定了一个offset参数。

在接收数据前,应该采用上面的第一个或第三个构造器生成一个DatagramPacket对象,给出接收数据的字节数组及其长度。然后调用DatagramSocket中的receive()方法等待数据报的到来,此方法将一直等待(也就是说会阻塞调用该方法的线程),直到收到一个数据报为止。例如下面的代码。

//创建接收数据的DatagramPacket对象
DatagramPacket packet=new DatagramPacket(buf, 256);
//接收数据
socket.receive(packet);
在发送数据之前,调用第二个或第四个构造器创建DatagramPacket对象,此时的字节数组里存放了想发送的数据。除此之外,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的send()方法实现的,send()方法根据数据报的目的地址来寻径以传递数据报。例如下面的代码。

//创建一个发送数据的DatagramPacket对象
DatagramPacket packet = new DatagramPacket(buf, length, address, port);
//发送数据报
socket.send(packet);
接着DatagramPacket为我们提供了getData()方法,此方法可以返回DatagramPacket对象中封装的字节数组。

当服务器(也可以客户端)接收到一个DatagramPacket对象后,如果想向该数据报的发送者“反馈”一些信息,但由于UDP是面向非连接的,所以接收者并不知道每个数据报由谁发送过来,但程序可以调用DatagramPacket的如下3个方法来获取发送者的IP和端口信息。

① InetAddress getAddress()。返回某台机器的IP地址,当程序准备发送此数据报时,该方法返回此数据报的目标机器的IP地址;当程序刚刚接收到一个数据报时,该方法返回该数据报的发送主机的IP地址。

② int getPort()。返回某台机器的端口,当程序准备发送此数据报时,该方法返回此数据报的目标机器的端口;当程序刚刚接收到一个数据报时,该方法返回该数据报的发送主机的端口。

③ SocketAddress getSocketAddress()。返回完整SocketAddress,通常由IP地址和端口组成。当程序准备发送此数据报时,该方法返回此数据报的目标SocketAddress;当程序刚刚接收到一个数据报时,该方法返回该数据报是源SocketAddress。

上述getSocketAddress方法的返回值是一个SocketAddress对象,该对象实际上就是一个IP地址和一个端口号,也就是说SocketAddress对象封装了一个InetAddress对象和一个代表端口的整数,所以使用SocketAddress对象可以同时代表IP地址和端口。

下面是一段实现UDP协议的服务器端代码。

源码路径:daima\6\tcpudp\src\UdpServer.java。

public class UdpServer
{
  public static final int PORT = 30000;
  //定义每个数据报的最大大小为4K
  private static final int DATA_LEN = 4096;
  //定义该服务器使用的DatagramSocket
  private DatagramSocket socket = null;
  //定义接收网络数据的字节数组
  byte[] inBuff = new byte[DATA_LEN];
  //以指定字节数组创建准备接收数据的DatagramPacket对象
  private DatagramPacket inPacket = 
    new DatagramPacket(inBuff , inBuff.length);
  //定义一个用于发送的DatagramPacket对象
  private DatagramPacket outPacket;
  //定义一个字符串数组,服务器发送该数组的的元素
  String[] books = new String[]
  {
    "AAA",
    "BBB",
    "CCC",
    "DDD"
  };
  public void init()throws IOException
  {
    try
    {
      //创建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);  
      }
    }
    //使用finally块保证关闭资源
    finally
    {
      if (socket != null)
      {
        socket.close();
      }
    }
  }
  public static void main(String[] args) 
    throws IOException
  {
    new UdpServer().init();
  }
}

上述代码使用DatagramSocket实现了Server/Client结构的网络通信程序,其中服务器端使用循环1 000次来读取DatagramSocket中的数据报,每当读到内容之后便向该数据报的发送者送回一条信息。

接下来看客户端的实现代码,客户端代码与服务器端类似,也是采用循环不断地读取用户键盘输入,每当读到用户输入内容后就将该内容封装成DatagramPacket数据报,再将该数据报发送出去。然后把DatagramSocket中的数据读入接收用的DatagramPacket中(实际上是读入该DatagramPacket所封装的字节数组中)。下面是一段实现UDP协议的客户端代码。

源码路径:daima\6\tcpudp\src\UdpClient.java。

public class UdpClient{
  //定义发送数据报的目的地
  public static final int DEST_PORT = 30000;
  public static final String DEST_IP = "127.0.0.1";
  //定义每个数据报的最大大小为4KB
  private static final int DATA_LEN = 4096;
  //定义该客户端使用的DatagramSocket
  private DatagramSocket socket = null;
  //定义接收网络数据的字节数组
  byte[] inBuff = new byte[DATA_LEN];
  //以指定字节数组创建准备接收数据的DatagramPacket对象
  private DatagramPacket inPacket = 
    new DatagramPacket(inBuff , inBuff.length);
  //定义一个用于发送的DatagramPacket对象
  private DatagramPacket outPacket = null;
  public void init()throws IOException{
    try
    {
      //创建一个客户端DatagramSocket,使用随机端口
      socket = new DatagramSocket();
      //初始化发送用的DatagramSocket,它包含一个长度为0的字节数组
      outPacket = new DatagramPacket(new byte[0] , 0 ,
        InetAddress.getByName(DEST_IP) , DEST_PORT);
      //创建键盘输入流
      Scanner scan = new Scanner(System.in);
      //不断读取键盘输入
      while(scan.hasNextLine())
      {
        //将键盘输入的一行字符串转换字节数组
        byte[] buff = scan.nextLine().getBytes();
        //设置发送用的DatagramPacket里的字节数据
        outPacket.setData(buff);
        //发送数据报
        socket.send(outPacket);
        //读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。
        socket.receive(inPacket);
        System.out.println(new String(inBuff , 0 , 
          inPacket.getLength()));
      }
    }
    //使用finally块保证关闭资源
    finally
    {
      if (socket != null)
      {
        socket.close();
      }
    }
  }
  public static void main(String[] args) 
    throws IOException
  {
    new UdpClient().init();
  }
}

上述代码通过DatagramSocket实现了发送并接收DatagramPacket的功能,具体实现与服务器的实现代码基本相似。而客户端与服务器端的唯一区别是服务器所在IP地址和端口是固定的,所以客户端可以直接将该数据报发送给服务器,而服务器需要根据接收到的数据报决定将“反馈”数据报的目的地。

6.3.2 使用MulticastSocket
DatagramSocket只允许将数据报发送给指定的目标地址,而MulticastSocket可以将数据报以广播地方式发送到数量不等的多个客户端。如果要使用多点广播,需要让一个数据报标有一组目标主机地址,当发出数据报后,整个组的所有主机都能收到该数据报。IP多点广播(或多点发送)实现可以将单一信息发送到多个接收者,功能是设置一组特殊网络地址作为多点广播地址,每一个多点广播地址都被看成一个组,当客户端需要发送、接收广播信息时,只需加入到该组即可。

IP协议为多点广播提供了这些特殊的IP地址,这些IP地址的范围是226.0.0.0至236.255.255.255。

类MulticastSocket既可以将数据报发送到多点广播地址,也可以接收其他主机的广播信息。类MulticastSocket是DatagramSocket类的一个子类,当要发送一个数据报时,可使用随机端口创建MulticastSocket,也可以在指定端口创建MulticastSocket。

在类MulticastSocket中提供了如下3个构造器。

① public MulticastSocket()。使用本机默认地址、随机端口来创建一个MulticastSocket对象。

② public MulticastSocket(int portNumber)。使用本机默认地址、指定端口来创建一个MulticastSocket对象。

③ public MulticastSocket(SocketAddress bindaddr)。使用本机指定IP地址、指定端口来创建一个MulticastSocket对象。

在创建一个MulticastSocket对象后,需要将该MulticastSocket加入到指定的多点广播地址。在MulticastSocket中使用方法jionGroup()加入到一个指定的组,使用方法leaveGroup()从一个组中脱离出去。这两个方法的具体说明如下。

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

② leaveGroup(InetAddress multicastAddr):让该MulticastSocket离开指定的多点广播地址。

在某些系统中可能有多个网络接口,这可能会对多点广播带来问题,此时程序需要在一个指定的网络接口上监听,通过调用setInterface可选择MulticastSocket所使用的网络接口,也可以使用getInterface方法查询MulticastSocket监听的网络接口。

如果创建只发送数据报的MulticastSocket对象,只需使用默认地址和随机端口即可。如果创建接收用的MulticastSocket对象,则该MulticastSocket对象必须具有指定端口,否则发送方无法确定发送数据报的目标端口。

虽然MulticastSocket实现发送/接收数据报的方法与DatagramSocket的完全一样,但是MulticastSocket比DatagramSocket多了下面的方法。

setTimeToLive(int ttl)
参数“ttl”设置数据报最多可以跨过多少个网络,具体说明如下。

① 为0时:指定数据报应停留在本地主机。

② 为1时:指定数据报发送到本地局域网。

③ 为32时:只能发送到本站点的网络上。

④ 为64时:数据报应保留在本地区。

⑤ 为128时:数据报应保留在本大洲。

⑥ 为255时:数据报可发送到所有地方。

⑦ 为1时:是默认值。

在使用MulticastSocket实现多点广播时,所有通信实体都是平等的,都将自己的数据报发送到多点广播IP地址,并使用MulticastSocket接收其他人发送的广播数据报。例如,在下面的代码中,使用MulticastSocket实现了一个基于广播的多人聊天室,程序只需要一个MulticastSocket,两条线程,其中MulticastSocket既用于发送,也用于接收,其中一条线程分别负责接收用户键盘输入,并向MulticastSocket发送数据,另一条线程则负责从MulticastSocket中读取数据。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值