当我们谈到网络时,我们不能不提到 OSI 模型。

就此模型而言,今天我们最感兴趣的是传输层 (4)。

这是我们处理“从 A 点到 B 点”移动数据的级别。传输层的主要任务是确保将消息传递到目的地,同时保持正确的顺序。两种最常见的传输层协议是:TCP 和 UDP。他们在概念上以不同的方式工作,但每个人都有自己的优势,使他们能够解决特定的问题。

首先,让我们看看 TCP 是如何工作的。

TCP(传输控制协议)是一种网络协议,可确保在交换数据之前建立主机之间的连接。

这是一个非常可靠的协议,因为它每次发送另一个数据包时,都必须检查是否收到了前一个数据包。

发送的数据包是有序的,如果某个数据包有问题(即接收方未确认数据包已经到达),则重新发送该数据包。结果,传输速率相对较低,因为需要更多时间进行严格监控和确保正确排序。

这就是它的“兄弟”UDP 协议的用武之地。与 TCP 不同,UDP 并不真正关心每个数据包的顺序和状态。它只是简单地发送数据而无需发送确认。更重要的是,它不建立连接,也不以任何方式依赖于连接状态。

它的目的只是将数据发送到一个地址。这导致了该协议的主要缺点,即可靠性低,因为它可能会丢失数据片段。此外,接收方必须为数据可能无序到达这一事实做好准备。也就是说,该协议也有一个优势,即更高的传输速率,因为该协议仅限于发送数据。

数据本身的传输方式也存在差异。在 TCP 中,数据是流式传输的,这意味着数据没有边界。在 UDP 中,数据以数据报的形式传输并具有边界,接收方检查数据的完整性,但前提是消息被成功接收。

让我们总结一下:

TCP 是一种可靠且准确的协议,可防止数据丢失。消息将始终以最准确的方式传递,或者根本不传递。接收者不需要订购数据的逻辑,因为传入的数据已经订购。 UDP 不那么可靠,但它是一种更快的数据传输协议。发送方和接收方需要一些额外的逻辑才能使用此协议。但让我们以通过网络玩电脑游戏或手机游戏为例,看看它是如何工作的。我们可能不再关心应该在 5 秒前到达的数据包,如果它们没有按时到达,我们可以跳过几个数据包——游戏可能会延迟,但您仍然可以玩!

在 Java 中,为了处理通过 UDP 传输的数据报,我们使用DatagramSocketDatagramPacket类的对象。

为了交换数据​​,发送方和接收方创建数据报套接字,即DatagramSocket类的实例。该类有几个构造函数。它们之间的区别在于创建的套接字将连接到哪里:

数据报套接字 () 连接到本地机器上的任何可用端口
DatagramSocket(整数端口) 连接到本地机器上的指定端口
DatagramSocket(int port, InetAddress 地址) 连接到本地机器地址 (addr) 的指定端口

该类包含许多用于访问和管理套接字参数的方法(稍后我们将查看它们),以及用于接收和发送数据报的方法:

发送(数据报包包) 发送打包成数据包的数据报
接收(数据报包包) 接收打包成数据包的数据报

DatagramPacket是一个表示数据报包的类。数据报包用于实现无连接的包传送服务。每条消息仅根据该数据包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个数据包可能会有不同的路由,并且可能以任何顺序到达。不保证数据包的交付。

构造函数:

DatagramPacket(byte[] buf, int length) 创建一个DatagramPacket以接受长度为 length 的数据包。
DatagramPacket(byte[] buf, int 长度, InetAddress 地址, int 端口) 创建一个数据报包,将长度为 length 的数据包发送指定主机上的指定端口号。
DatagramPacket(byte[] buf, int offset, int length) 创建一个DatagramPacket以接受长度为 length 的数据包,指定缓冲区中的偏移量。
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 创建一个数据报包,将长度为length ,偏移量为offset的数据包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int offset, int length, SocketAddress 地址) 创建一个数据报包,将长度为length ,偏移量为offset的数据包发送到指定主机上的指定端口号。
DatagramPacket(byte[] buf, int length, SocketAddress 地址) 创建一个数据报包,将长度为 length 的数据包发送指定主机上的指定端口号。

我们记得 UDP 方法不建立连接。这些数据包被发送出去,希望收件人期待它们。但是您可以使用DatagramSocket类的connect(InetAddress addr, int port)方法建立连接。

基于地址和端口与主机建立单向连接:发送或接收数据报。可以使用disconnect()方法终止连接。

让我们尝试编写基于DatagramSocket 的服务端代码来接收数据:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

class Recipient {

   public static void main(String[] args) {
       try {
           DatagramSocket ds = new DatagramSocket(1050);

           while (true) {
               DatagramPacket pack = new DatagramPacket(new byte[5], 5);
               ds.receive(pack);
               System.out.println(new String(pack.getData()));
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}

我们创建一个DatagramSocket对象来侦听端口 1050。当它收到一条消息时,它将它打印到控制台。我们将传输单词“Hello”,因此我们将缓冲区大小限制为五个字节。

现在我们将创建发件人类:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

class Sender {
   private String host;
   private int port;

   Sender(String host, int port) {
       this.host = host;
       this.port = port;
   }

   private void sendMessage(String mes) {
       try {
           byte[] data = mes.getBytes();
           InetAddress address = InetAddress.getByName(host);
           DatagramPacket pack = new DatagramPacket(data, data.length, address, port);
           DatagramSocket ds = new DatagramSocket();
           ds.send(pack);
           ds.close();
       } catch (IOException e) {
           System.err.println(e);
       }
   }

   public static void main(String[] args) {
   Sender sender = new Sender("localhost", 1050);
   String message = "Hello";

   Timer timer = new Timer();
   timer.scheduleAtFixedRate(new TimerTask() {
       @Override
       public void run() {
           sender.sendMessage(message);
       }
   }, 1000, 1000);
}

}

sendMessage方法中,我们创建一个DatagramPacketDatagramSocket,并发送我们的消息。请注意,close()方法用于在消息发送后关闭DatagramSocket 。

接收者的控制台每秒显示发送者发送的传入“Hello”消息。这意味着我们的通信都正常工作。