【Java TCP/IP Socket编程】----套接字----UDP Socket

目录

 

简介

UDP通信

UDP通信案例

UDP套接字注意点


简介

UDP是面向无连接的协议,在数据传输时,数据的发送端和接收端不建立逻辑上的连接。当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,同样接收端接收到数据时,也不会发送反馈给发送端。实际上UDP协议只实现了两个功能:

      1)在IP协议的基础上添加了另一层地址(端口)。

      2)对数据传输过程中可能产品的数据错误进行了检测,并抛弃已经损坏的数据。

UDP协议开销比较小,并且传输效率比较高,经常用于传送音频,视频或者普通文件,即使存在丢失一两个包的情况,影响不会太大。

Java中提供了DatagramPacket类和DatagramSocket类来使用UDP套接字,客户端和服务器端都使用的是DatagramSocket来发送数据,使用DatagramPacket来接收数据。

发送信息时,需要创建一个包含了待发送信息的DatagramPacket实例,并将其作为参数传递给DatagramSocket类的send()方法。

接收信息时,同样需要创建一个DatagramPacket实例,该实例中预先分配了一些空间(一个字节数组byte[]),并将接收到的信息存放在该空间中,然后将该实例作为参数传递给DatagramSocket类的receive()方法。

需要注意的是:除了输出的信息外,DatagramPacket实例里面还包含了地址和端口信息,如果是发送数据报文,DatagramPacket里面地址指明了目的地址和端口号,如果是接收到的数据报文,DatagramPacket实例中的地址指明了所接收信息的源地址。

UDP通信

UDP客户端:

UDP客户端首先向被动等待联系的服务器端发送一个数据报文。一个典型的UDP客户端主要执行以下三步

      1.创建一个DatagramSocket实例,可以选择对本地地址和端口号进行设置。

      2.使用DatagramSocket类的send()和receive()方法来发送和接收DatagramPacket实例,进行通信。

      3.通信完成后,使用DatagramSocket类的close()方法来销毁该套接字。

UDP服务器端

与TCP服务器一样,UDP服务器的工作是建立一个通信终端,并被动等待客户端发起连接。但由于UDP是无连接的,UDP通信通过客户端的数据报文初始化,并没有TCP中建立连接那一步。典型的UDP服务器要执行以下三步

      1.创建一个DatagramSocket实例,指定本地端口号,并可以选择指定本地地址。此时服务器已经准备好从任何客户端接收数据报文。

      2.使用DatagramSocket类receive()方法来接收一个DatagramPacket实例。当receive()方法返回时,数据报文里面包含了客户端的地址,由此可知反馈信息的目的地。

      3.使用DatagramSocket类的send()和receive()方法来发送和接收DatagramPacket实例,进行通信。

UDP通信案例

public class UDPEchoClient {
  private static final int MAXTRIES =5;
  private static final String INFO = "hello server";

  public static void main(String[] args) throws IOException {
    DatagramSocket socket = new DatagramSocket(4321);
    socket.setSoTimeout(1000);
    byte bytes[] = new byte[1024];
    InetAddress address = InetAddress.getLocalHost();
    DatagramPacket sendPacket = new DatagramPacket(INFO.getBytes(), INFO.length(), address, 1234);
    DatagramPacket receivePacket = new DatagramPacket(bytes, 1024);
    boolean receiveResponse = false;
    int tries = 0;
    do {
      socket.send(sendPacket);
      try {
        socket.receive(receivePacket);
        if (!receivePacket.getAddress().equals(address)) {
          throw new IOException("Receive Packet from unknown source");
        }
        receiveResponse = true;
      } catch (Exception e) {
        tries+=1;
        System.out.println("time out,"+(MAXTRIES-tries)+"more tries");
      }
    } while ((tries < 5) && (!receiveResponse));
    if (receiveResponse) {
      System.out.println("Received from Server:");
      System.out.println(new String(receivePacket.getData(),0,receivePacket.getLength())+" from:"+receivePacket.getAddress().getHostAddress()+"---"+receivePacket.getPort());
      receivePacket.setLength(1024);
    }else {
      System.out.println("no response -----giving up");
    }
    socket.close();
  }
}
public class UDPEhoServer {
  private static final int LENGTH = 255;
  private static final String MESSAGE = "hello client";

  public static void main(String[] args) throws IOException {
    DatagramSocket socket = new DatagramSocket(1234);
    byte[] buffer = new byte[LENGTH];
    DatagramPacket receivePacket = new DatagramPacket(buffer, LENGTH);
    while (true) {
      socket.receive(receivePacket);
      System.out.println("Received from Client:");
      System.out.println(new String(receivePacket.getData(), 0, receivePacket.getLength()) + " from :"
          + receivePacket.getAddress().getHostAddress() + "---" + receivePacket.getPort());
      DatagramPacket sendPacket = new DatagramPacket(MESSAGE.getBytes(), MESSAGE.length(), receivePacket.getAddress(), 4321);
      socket.send(sendPacket);
      receivePacket.setLength(LENGTH);
    }
  }
}

运行结果:

Received from Server:
hello client from:10.170.234.245---1234

Received from Client:
hello server from :10.170.234.245---4321

可见UDP服务器端要显式设置本地端口,并使客户端知道该端口。设置DatagramPacket参数的端口是为了发送到服务器端的指定端口,服务器端设置DatagramSocket的参数为对应端口以接收指定端口过来的数据。

UDP套接字注意点

1.UDP套接字与TCP套接字重要但微小区别是:UDP协议保留了消息的边界信息。

      DatagramSocket的每一次receive()调用最多接收调用一次send()方法发送的数据,不同的receive()方法调用绝不会返回同一个send()方法调用所发送的数据。

      当在TCP套接字的输出流上调用write()方法返回后,所有调用者都知道数据已经被复制到一个传输缓存区中,实际上此时数据可能已经被发送,也有可能还没有被传送,而UDP协议没有提供从网络错误中恢复的机制,因此,并不对可能需要重传的数据进行缓存。这就意味着,当send()方法调用返回时,消息已经被发送到了底层的传输信道中。

2.为了保证已接收但未到传送的数据不被丢弃,接收端提供缓存空间应为UDP数据报文最大负载量(65507字节)

       消息从网络中到达后,其所包含的数据被read()方法或receive()方法返回前,数据存储在一个先进先出的接收数据队列中。对于已连接的TCP套接字来说,所有已接受但还未传送的字节都看作是一个连续的字节序列。然而,对于UDP套接字来说,接收到的数据可能来自不同的发送者,一个UDP套接字所接收的数据存放在一个消息队列中,每个消息都关联了其源地址信息,每次receive()调用只返回一条消息。如果receive()方法在一个缓存区大小为n的DatagramPacket实例中调用,而接收队列里中的第一条消息的长度大于n,则receive()方法只返回这条消息的前n个字节,超出部分会被自动放弃,而且对接收程序没有任何消息丢失的提示。

       出于这个原因,接受者应该提供一个有足够大的缓存空间的DatagramPacket实例,以完整地存放调用receive()方法时应用程序协议所允许的最大长度的消息。一个DatagramPacket实例中所允许传输的最大数据量为65507个字节,也即是UDP数据报文所能负载的最多数据。因此,可以用一个65600字节左右的缓存数组来接受数据。

3.DatagramPacket包含一个内部消息的长度值,该实例一接收消息,此长度值就会变化,因此每次调用receive()方法前将内部消息的长度值重置为缓存区的实际长度。

      如果一个应用程序多次使用同一个DatagramPacket实例多次调用receive()方法,每次调用前显式将消息的内部长度重置为缓冲区的原始的长度。如:

      packet.setLength(LENGTH);

4.DatagramPacket类的getData()方法总是返回缓冲区原始大小,忽略了实际数据的内部偏移量和长度信息。

        由于DatagramPacket的getData()方法总是返回缓冲数组的原始大小,因此如果我们要获取接收到的数据,就必须截取getData()方法返回的数组中只含接收到的数据的那一部分。JDK1.6中可以使用Arrays.copyOfRange()方法来实现。

byte[] destbuf = Arrays.copyOfRange(dp_receive.getData(),dp_receive.getOffset(),

dp_receive.getOffset() + dp_receive.getLength());

猜你喜欢

转载自blog.csdn.net/lili13897741554/article/details/82995571