| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 网络协议 -> 第 6 章 UDP 和多播 -> 正文阅读 |
|
[网络协议]第 6 章 UDP 和多播 |
用户数据报协议(?UDP?) 位于 IP 之上,并提供与 TCP 不可靠的对应物。UDP 在网络中的两个节点之间发送单独的数据包。UDP 数据包不知道其他数据包,并且不能保证数据包将实际到达其预定目的地。当发送多个数据包时,无法保证到达顺序。UDP 消息只是简单地发送,然后被遗忘,因为接收方没有发送确认。 UDP 是一种无连接协议。两个节点之间没有消息交换以促进数据包传输。不维护有关连接的状态信息。 UDP 适用于需要高效交付且不需要保证交付的服务。例如,它用于域名系统(?DNS?) 服务、网络时间协议(?NTP?) 服务、IP 语音(?VOIP?)、P2P 网络的网络通信协调以及视频流。如果视频帧丢失,如果丢失不经常发生,观众可能永远不会注意到它。 有几种使用 UDP 的协议,包括:
UDP 数据包由 IP 地址和端口号组成,用于标识其目的地。UDP 数据包具有固定大小,最大可达 65,353 字节。但是,每个数据包至少使用 20 个字节的 IP 标头和 8 个字节的 UDP 标头,将消息的大小限制为 65,507 个字节。如果消息大于该值,则需要发送多个数据包。 UDP 数据包也可以是多播的。这意味着数据包被发送到属于 UDP 组的每个节点。这是将信息发送到多个节点的有效方式,而无需明确针对每个节点。相反,数据包被发送到一个组,该组的成员负责捕获其组的数据包。 在本章中,我们将说明如何使用 UDP 协议:
我们将从 Java 对 UDP 支持的概述开始,并提供更多 UDP 协议详细信息。 Java 对 UDP 的支持 Java 使用DatagramSocket该类在节点之间DatagramPacket形成套接字连接。所述类表示数据的分组。简单的发送和接收方法将通过网络传输数据包。 UDP 使用 IP 地址和端口号来标识节点。UDP 端口号范围从0到65535。端口号分为三种类型:
下表是 UDP 特定端口分配的简短列表。它们说明了 UDP 如何被广泛用于支持许多不同的应用程序和服务。更完整的 TCP/UDP 端口号列表可在https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers找到:
下表列出了已注册的端口及其用途:
TCP 与 UDP TCP 和 UDP 之间有几个区别。这些差异包括:
当使用 TCP 发送数据包时,保证数据包到达。如果丢失,则重新发送。UDP 不提供此保证。如果数据包没有到达,则不会重新发送。 TCP 保留发送数据包的顺序,而 UDP 则不保留。如果 TCP 数据包到达目的地的顺序与其发送顺序不同,则 TCP 将按原始顺序重新组装数据包。使用 UDP 时,不会保留此顺序。 创建数据包时,会附加标头信息以帮助传输数据包。对于 UDP,报头由 8 个字节组成。TCP 报头的通常大小为 32 字节。 UDP 的报头大小更小,并且没有确保可靠性的开销,因此比 TCP 更有效。此外,创建连接所需的工作量更少。这种效率使其成为流媒体的更好选择。 让我们从如何支持传统的客户端/服务器架构开始我们的 UDP 示例。 UDP客户端/服务器UDP 客户端/服务器应用程序在结构上类似于用于 TCP 客户端/服务器应用程序的结构。在服务器端,创建了一个 UDP 服务器套接字,它等待客户端请求。客户端将创建相应的 UDP 套接字并使用它向服务器发送消息。然后服务器可以处理请求并发回响应。 UDP 客户端/服务器将使用 UDP 服务器应用程序接下来定义我们的服务器。该构造函数将执行服务器的工作: 公共类 UDPServer {
??? 公共 UDPServer() {
??????? System.out.println("UDP 服务器启动"); ??????? ...
??????? System.out.println("UDP 服务器终止"); ??? }
??? 公共静态无效主(字符串 [] args){
??????? 新的 UDPServer();
??? }
}
在构造函数的 try-with-resources 块中,我们创建了一个 ??????? 试试 (DatagramSocket serverSocket =
????????????????新数据报套接字(9003)){
??????????? ...
??????????? }
??????? } catch (IOException ex) {
??????????? //处理异常 ??????? }
创建套接字的另一种方法是使用该 ??????? DatagramSocket serverSocket = new DatagramSocket(null);
????????serverSocket.bind(new InetSocketAddress(9003));
这两种方法都将 发送消息的过程包括以下内容:
该过程包含在一个循环中,如下所示,以允许处理多个请求。收到的消息只是简单地回显给客户端程序。该 ??????? 而(真){
??????????? 字节[]接收消息=新字节[1024];
??????????? DatagramPacket receivePacket = new DatagramPacket(
??????????????? 接收消息,接收消息。长度);
??????????? serverSocket.receive(receivePacket);
??????????? ...
???????}
当方法返回时,数据包被转换为字符串。如果发送了某种其他数据类型,则需要进行某种其他转换。然后显示发送的消息: ??????? String message = new String(receivePacket.getData());
??????? System.out.println("从客户端收到:[" + message ?????????????? + "]\n发件人:" + receivePacket.getAddress()); 要发送响应,需要客户端的地址和端口号。这些是分别使用 ??????? InetAddress inetAddress = receivePacket.getAddress();
??????? int port = receivePacket.getPort();
??????? 字节[] 发送消息;
??????? sendMessage = message.getBytes();
??????? DatagramPacket sendPacket =
????????????新数据报包(发送消息,
??????????????? sendMessage.length, inetAddress, port);
??????? serverSocket.send(sendPacket);
定义服务器后,让我们检查客户端。 UDP 客户端应用程序客户端应用程序将提示用户发送消息,然后将消息发送到服务器。它将等待响应,然后显示响应。它在这里声明: 类 UDPClient {
??? 公共 UDPClient() {
??????? System.out.println("UDP 客户端启动"); ??????? ...
??????? }
??????? System.out.println("UDP 客户端终止"); ??? }
??? 公共静态无效主(字符串参数[]){
??????? 新的 UDPClient();
??? }
}
本 ??????? 扫描仪scanner = new Scanner(System.in);
??????? 尝试 (DatagramSocket clientSocket = new DatagramSocket()) {
??????????? ...
??????????? }
??????????? clientSocket.close();
??????? } catch (IOException ex) {
??????????? // 处理异常 ??????? }
使用该 ??????? InetAddress inetAddress =
????????????InetAddress.getByName("localhost");
??????? 字节[] 发送消息;
无限循环用于提示用户输入消息。当用户输入“quit”时,应用程序将终止,如下所示: ??????? 而(真){
????????? ??System.out.print("请输入信息:"); ??????????? 字符串消息=scanner.nextLine();
??????????? 如果(“退出”.equalsIgnoreCase(消息)){
???????????????? 休息;
??????????? }
??????? ...
??????? }
要创建一个 ??????????? sendMessage = message.getBytes();
??????????? DatagramPacket sendPacket = new DatagramPacket(
??????????????? sendMessage,sendMessage.length, ????????????????内网地址,9003);
??????????? clientSocket.send(sendPacket);
要接收响应,会创建一个接收数据包,并以与 ??????????? 字节[]接收消息=新字节[1024];
??????????? DatagramPacket receivePacket = new DatagramPacket(
??????????????????? 接收消息,接收消息。长度);
??????????? clientSocket.receive(receivePacket);
???? ???????String receivedSentence =
????????????????新字符串(receivePacket.getData());
??????????? System.out.println("从服务器接收到[" ????????????????+ 收到的句子 + "]\n来自 " ??????????????? + receivePacket.getSocketAddress());
现在,让我们看看这些应用程序是如何工作的。 UDP 客户端/服务器正在运行服务器首先启动。它将显示以下消息: UDP 服务器启动 接下来,启动客户端应用程序。它将显示以下消息: UDP 客户端启动 输入消息: 输入一条消息,例如以下内容: 输入留言:给你的早晨 服务器将显示它已收到消息,如下所示。您将看到几行空输出。这是用于保存消息的 1024 字节数组的内容。然后将消息回显给客户端: 从客户收到:[早上给你的顶部 ... ] 来自:/127.0.0.1 在客户端,显示响应。在这个例子中,用户然后输入“quit”来终止应用程序: 从服务器收到[早上给你的头顶 ... ] 来自 /127.0.0.1:9003 输入消息:退出 UDP 客户端终止 当我们发送和接收测试消息时,我们可以使用 ??????? System.out.println("从客户端收到:[" ????????????????+ message.trim()
??????????????? + "]\n发件人:" + receivePacket.getAddress()); 输出将更容易阅读,如下所示: 从客户那里收到:[早上的头条给你] 来自:/127.0.0.1 此客户端/服务器应用程序可以通过多种方式进行增强,包括使用线程,以使其能够更好地与多个客户端一起工作。此示例说明了用 Java 开发 UDP 客户端/服务器应用程序的基础知识。在下一节中,我们将看到通道如何支持 UDP。 UDP 通道支持本 所述 为了演示 UDP 回显服务器应用程序UDP 回显服务器应用程序声明遵循并使用端口 公共类 UDPEchoServer {
?? ?公共静态无效主(字符串 [] args){
??????? int 端口 = 9000; ??????? System.out.println("UDP Echo Server 启动"); ??????? 试试 (DatagramChannel channel = DatagramChannel.open();
??????????? DatagramSocket socket = channel.socket();){
??????????????? ...
??????????? }
??????? }
??????? 捕获(IOException ex){
??????????? // 处理异常 ??????? }
??????? System.out.println("UDP Echo Server 终止"); ??? }
}
创建后,我们需要将其与端口相关联。这首先通过创建 ??????????? SocketAddress 地址 = 新的 InetSocketAddress(port); ??????????? 套接字绑定(地址);
的 ??????????? ByteBuffer 缓冲区 = ByteBuffer.allocateDirect(65507); 添加后面的无限循环,它将接收来自客户端的消息,显示该消息,然后将其发送回: ??????????? 而(真){
??????????????? // 获取消息 ??????????????? // 显示消息 ??????????????? // 返回消息 ??????????? }
该 该 ??????? SocketAddress client = channel.receive(buffer);
??????? 缓冲。翻转();
虽然对于回显服务器不是必需的,但接收到的消息会显示在服务器上。这使我们能够验证消息是否已收到,并建议如何修改消息以做更多的事情,而不仅仅是简单地回显消息。 为了显示消息,我们需要使用 但是,该 所有这些都在以下代码序列中执行。该 ??????? 缓冲区。标记();
??????? System.out.print("收到:["); ??????? StringBuilder message = new StringBuilder();
??????? 而 (buffer.hasRemaining()) {
??????????? message.append((char) buffer.get());
??????? }
??????? System.out.println(message + "]");
??????? 缓冲。重置();
最后一步是将字节缓冲区发送回客户端。该 ??????? 通道发送(缓冲区,客户端);
??????? System.out.println("发送:[" + message + "]"); ??????? 缓冲区清除();
当服务器启动时,我们会看到一条这样的消息,如下所示: UDP Echo 服务器启动 我们现在准备看看客户端是如何实现的。 UDP 回显客户端应用程序在执行UDP回应客户端简单,使用下列步骤操作:
客户端的实现细节与服务器的类似。我们从应用程序的声明开始,如下所示: 公共类 UDPEchoClient {
?? ? ????公共静态无效主(字符串 [] args){
??????? System.out.println("UDP Echo 客户端启动"); ??????? 尝试 {
??????????? ...
??????? }
??????? 捕获(IOException ex){
??????????? // 处理异常 ??????? }
??????? System.out.println("UDP Echo 客户端终止"); ??? }
}
在服务器中,单参数 ??????? SocketAddress 远程 = ????????????新的 InetSocketAddress("127.0.0.1", 9000);
然后使用该 ??????? DatagramChannel channel = DatagramChannel.open();
??????? 通道连接(远程);
在下一个代码序列中,创建消息字符串,并分配字节缓冲区。缓冲区的大小设置为字符串的长度。 ??????? String message = "消息"; ??????? ByteBuffer 缓冲区 = ByteBuffer.allocate(message.length()); ??????? buffer.put(message.getBytes());
在我们将缓冲区发送到服务器之前, ??????? 缓冲。翻转();
要将消息发送到服务器,将 ??????? 通道写(缓冲区);
??????? System.out.println("发送:[" + message + "]"); 接下来,缓冲区被清除,允许我们重用缓冲区。该 ??????? 缓冲区清除();
??????? 通道读取(缓冲区);
??????? 缓冲。翻转();
??????? System.out.print("收到:["); ??????? while(buffer.hasRemaining()) {
??????????? System.out.print((char)buffer.get());
??????? }
??????? System.out.println("]");
我们现在准备好将客户端与服务器结合使用。 UDP 回显客户端/服务器在运行需要先启动服务器。我们将看到初始服务器消息,如下所示: UDP Echo 服务器启动 接下来,启动客户端。将显示以下输出,显示客户端发送消息,然后显示返回的消息: UDP Echo 客户端启动 发送:【消息】 收到:【消息】 UDP Echo 客户端终止 在服务器端,我们将看到消息被接收然后被发送回客户端: 收到:【消息】 发送:【消息】 使用 UDP多播多播是同时向多个客户端发送消息的过程。每个客户端都会收到相同的消息。为了参与这个过程,客户端需要加入一个多播组。当一个消息被发送时,它的目的地址表明它是一个多播消息。组播组是动态的,客户端随时进入和离开组。 多播是旧的 IPv4 CLASS D 空间, UDP 多播服务器接下来声明服务器应用程序。这个服务器是一个时间服务器,它会每秒广播当前的数据和时间。这是多播消息的一个很好的用途,因为可能有多个客户端对相同的信息感兴趣,并且可靠性不是问题。try 块将在发生异常时进行处理: 公共类 UDPMulticastServer {
??? 公共 UDPMulticastServer() {
??????? System.out.println("UDP 多播时间服务器启动"); ??????? 尝试 {
??????????? ...
??????? } catch (IOException | InterruptedException ex) {
??????????? // 处理异常 ??????? }
??????? System.out.println(
??????????? "UDP 多播时间服务器终止"); ??? }
?? ? ????公共静态无效主(字符串参数[]){
??????? 新的 UDPMulticastServer();
??? }
}
该实例 ??? MulticastSocket multicastSocket = new MulticastSocket();
??? InetAddress inetAddress = InetAddress.getByName("228.5.6.7");
??? 多播Socket.joinGroup(inetAddress);
为了发送消息,我们需要一个字节数组来保存消息和一个数据包。这些声明如下所示: ??? 字节[]数据;
??? DatagramPacket 数据包; 服务器应用程序将使用无限循环每秒广播一个新的日期和时间。线程暂停一秒钟,然后使用 ??? 而(真){
??????? 线程睡眠(1000);
??????? String message = (new Date()).toString();
??????? System.out.println("发送:[" + message + "]"); ??????? 数据 = message.getBytes();
??????? packet = new DatagramPacket(data, message.length(),
????????????????内网地址,9877);
??????? 多播Socket.send(数据包);
??? }
接下来讨论客户端应用程序。 UDP 多播客户端此应用程序将加入由地址定义的多播组 公共类 UDPMulticastClient {
??? 公共 UDPMulticastClient() {
??????? System.out.println("UDP 多播时间客户端启动"); ??????? 尝试 {
??????????? ...
??????? } catch (IOException ex) {
?????????? ?ex.printStackTrace();
??????? }
?????? ? ????????System.out.println(
??????????? "UDP 多播时间客户端终止"); ??? }
?? ? ????公共静态无效主(字符串 [] args){
??????? 新的 UDPMulticastClient();
??? }
}
??? MulticastSocket multicastSocket = new MulticastSocket(9877);
??? InetAddress inetAddress = InetAddress.getByName("228.5.6.7");
??? 多播Socket.joinGroup(inetAddress);
??? 字节[]数据=新字节[256];
??? DatagramPacket packet = new DatagramPacket(data, data.length);
客户端应用程序然后进入一个无限循环,在那里它阻塞在 ??? 而(真){
??????? 多播Socket.receive(数据包);
??????? 字符串消息 = 新字符串(
??????????? packet.getData(), 0, packet.getLength());
??????? System.out.println("消息来自:" + packet.getAddress() ????????????+ " 消息:[" + 消息 + "]"); ??? }
接下来,我们将演示客户端和服务器如何交互。 运行中的 UDP 多播客户端/服务器启动服务器。服务器的输出将与以下类似,但日期和时间会有所不同: UDP 多播时间服务器启动 发送: [Sat Sep 19 13:48:42 CDT 2015] 发送: [Sat Sep 19 13:48:43 CDT 2015] 发送: [Sat Sep 19 13:48:44 CDT 2015] 发送: [Sat Sep 19 13:48:45 CDT 2015] 发送:[Sat Sep 19 13:48:46 CDT 2015] 发送: [Sat Sep 19 13:48:47 CDT 2015] ... 接下来,启动客户端应用程序。它将开始接收类似于以下内容的消息: UDP 多播时间客户端启动 消息来自:/192.168.1.7 消息:[Sat Sep 19 13:48:44 CDT 2015] 消息来自:/192.168.1.7 消息:[Sat Sep 19 13:48:45 CDT 2015] 消息来自:/192.168.1.7 消息:[Sat Sep 19 13:48:46 CDT 2015] ... 笔记如果程序在 Mac 上执行,则可能是通过套接字异常。如果发生这种情况,请使用该 如果启动后续客户端,每个客户端都会收到相同系列的服务器消息。 带通道的 UDP 多播我们还可以使用频道进行 所述 ??????? 尝试 {
??????????? 枚举<NetworkInterface> networkInterfaces;
??????????? 网络接口 =
????????????????网络接口.getNetworkInterfaces();
??????????? 对于(网络接口网络接口:
????????????????????Collections.list(networkInterfaces)) {
??????????????? 显示网络接口信息(
?? ?????????????????网络接口);
??????????? }
??????? } catch (SocketException ex) {
??????????? // 处理异常 ??????? }
该 ??? static void displayNetworkInterfaceInformation(
??????????? 网络接口网络接口){
??????? 尝试 {
??????????? System.out.printf("显示名称:%s\n", ????????????????networkInterface.getDisplayName());
??????????? System.out.printf("名称:%s\n", ????????????????networkInterface.getName());
??????????? System.out.printf("支持多播:%s\n", ????????????????networkInterface.supportsMulticast());
??????????? 枚举<InetAddress> inetAddresses =
????????????????networkInterface.getInetAddresses();
??????????? 对于 (InetAddress inetAddress :
????????????????????Collections.list(inetAddresses)) {
??????????????? System.out.printf("Inet 地址:%s\n", ????????????????????inet地址); ??????????? }
??????????? System.out.println();
??????? } catch (SocketException ex) {
??????????? // 处理异常 ??????? }
??? }
执行此示例时,您将获得类似于以下内容的输出: 显示名称:软件环回接口 1 姓名:罗 支持组播:true Inet地址:/127.0.0.1 Inet地址:/0:0:0:0:0:0:0:1 显示名称:Microsoft 内核调试网络适配器 名称:eth0 支持组播:true 显示名称:Realtek PCIe FE 系列控制器 名称:eth1 支持组播:true Inet地址:/fe80:0:0:0:91d0:8e19:31f1:cb2d%eth1 显示名称:Realtek RTL8188EE 802.11 b/g/n Wi-Fi Adapter 名称: wlan0 支持组播:true Inet 地址:/192.168.1.7 Inet 地址:/2002:42be:6659:0:0:0:0:1001 Inet地址:/fe80:0:0:0:9cdb:371f:d3e9:4e2e%wlan0 ... 对于我们的客户端/服务器,我们将使用该 UDP 通道组播服务器UDP 通道多播服务器将:
服务器的定义如下: 公共类 UDPDatagramMulticastServer {
??? 公共静态无效主(字符串 [] args){
??????? 尝试 {
??????????? ...
??????????? }
??????? } catch (IOException | InterruptedException ex) {
??????????? // 处理异常 ??????? }
??? }
}
第一个任务使用 ??????????? System.setProperty(
??????????????? "java.net.preferIPv6Stack", "true");
??????????? DatagramChannel channel = DatagramChannel.open();
??????????? 网络接口 networkInterface =
????????????????NetworkInterface.getByName("eth0");
??????????? channel.setOption(StandardSocketOptions.
?????????????? ?IP_MULTICAST_IF,
????????????????网络接口);
??????????? InetSocketAddress 组 = ????????????????new InetSocketAddress("FF01:0:0:0:0:0:0:FC",
????????????????????????9003);
然后根据消息字符串创建字节缓冲区。缓冲区的大小设置为字符串的长度,并使用 ??????????? String message = "消息"; ??????????? ByteBuffer 缓冲区 = ????????????????ByteBuffer.allocate(message.length());
??????????? buffer.put(message.getBytes());
在 while 循环中,缓冲区被发送给组成员。为了清楚地看到发送的内容,缓冲区的内容使用在UDP 回显服务器应用程序部分中使用的相同代码显示。缓冲区被重置,以便它可以再次使用。应用程序暂停一秒钟以避免此示例中的过多消息: ??????? ????而(真){
??????????????? 通道发送(缓冲区,组);
??????????????? System.out.println("发送多播消息:" ????????????????????+ 消息); ??????????????? 缓冲区清除();
??????????????? 缓冲区。标记();
??????????????? System.out.print("发送:["); ??????????????? StringBuilder msg = new StringBuilder();
??????????????? 而 (buffer.hasRemaining()) {
??????????????????? msg.append((char) buffer.get());
??????????????? }
??????????????? System.out.println(msg + "]");
??????????????? 缓冲。重置();
??????????????? 线程睡眠(1000);
??????? }
我们现在已经为客户端应用程序做好了准备。 UDP 通道组播客户端UDP 通道组播客户端将加入组,接收消息,显示它,然后终止。正如我们将看到的, 该应用程序声明如下。首先,我们指定要使用 IPv6。然后声明网络接口,这与服务器使用的接口相同: 公共类 UDPDatagramMulticastClient {
??? public static void main(String[] args) 抛出异常 { ??????? System.setProperty("java.net.preferIPv6Stack", "true");
??????? 网络接口 networkInterface =
????????????NetworkInterface.getByName("eth0");
??????? ...
??? }
}
在 ??????? DatagramChannel 通道 = DatagramChannel.open() ??????????????? .bind(new InetSocketAddress(9003))
??????????????? .setOption(StandardSocketOptions.IP_MULTICAST_IF,
????????????????????网络接口);
然后根据服务器使用的相同 IPv6 地址创建组,并 ??????? InetAddress 组 = ????????????InetAddress.getByName("FF01:0:0:0:0:0:0:FC");
??????? MembershipKey key = channel.join(group, networkInterface);
??????? System.out.println("加入的组播组:" + key); ??????? System.out.println("等待消息..."); 创建大小为 的字节缓冲区 ??????? ByteBuffer 缓冲区 = ByteBuffer.allocate(1024); ??????? 通道接收(缓冲区);
要显示缓冲区的内容,我们需要翻转它。内容像我们之前一样显示: ??????? 缓冲。翻转();
??????? System.out.print("收到:["); ??????? StringBuilder message = new StringBuilder();
??????? 而 (buffer.hasRemaining()) {
??????????? message.append((char) buffer.get());
??????? }
??????? System.out.println(message + "]");
当我们使用完成员键后,我们应该表明我们不再对使用以下 ??????? 键.drop();
如果有数据包等待套接字处理,消息可能仍会到达。 UDP 通道多播客户端/服务器在运行首先启动服务器。该服务器将每秒显示一系列消息,如下所示: 发送多播消息:消息 发送:【消息】 发送多播消息:消息 发送:【消息】 发送多播消息:消息 发送:【消息】 ... 接下来,启动客户端应用程序。它将显示多播组,等待消息,然后显示消息,如下所示: 加入的组播组:<ff01:0:0:0:0:0:0:fc,eth1> 等待消息... 收到:【消息】 使用通道可以提高UDP 多播消息的性能。 UDP流使用 UDP 流式传输音频或视频很常见。它是高效的,任何数据包丢失或无序数据包都会导致最小的问题。我们将通过直播音频来说明这种技术。UDP 服务器将捕获麦克风的声音并将其发送给客户端。UDP 客户端将接收音频并在系统的扬声器上播放。 UDP 流服务器的想法是将流分解为一系列发送到 UDP 客户端的数据包。客户端然后将接收这些数据包并使用它们来重构流。 为了说明流式音频,我们需要了解一些关于 Java 如何处理音频流的知识。音频由包中的一系列类处理
用于 UDP 音频服务器实现类的声明如下。它使用一个实例作为音频源。它被声明为一个实例变量,因为它在多个方法中使用。构造函数使用一个方法来初始化音频和一个方法来将此音频发送到客户端:? 公共类 AudioUDPServer {
??? 私有最终字节 audioBuffer[] = 新字节 [10000];
??? 私有目标数据线目标数据线;
??? 公共音频UDP服务器(){
??????? 设置音频();
??????? 广播音频();
??? }
??? ...
??? 公共静态无效主(字符串 [] args){
?????? ?新的音频UDP服务器();
??? }
}
下面是方法,在服务器端和客户端都使用它来指定音频流特性。模拟音频信号每秒采样 1,600 次。每个样本都是一个有符号的 16 位数字。该变量分配,这意味着音频是单声道。样本中的字节顺序很重要,并设置为大端:? ??? 私人音频格式 getAudioFormat() {
??????? 浮动采样率 = 16000F;
??????? int sampleSizeInBits = 16;
??????? 整数通道 = 1;
??????? 布尔符号 = 真;
??????? boolean bigEndian = false;
??????? 返回新的 AudioFormat(sampleRate, sampleSizeInBits,
????????????频道,签名,bigEndian);
??? }
Big endian 和 little endian 是指字节的顺序。大端意味着一个字的最高有效字节存储在最小的内存地址,最低有效字节存储在最大的内存地址。小端颠倒这个顺序。不同的计算机体系结构使用不同的顺序。 该 ??? 私有无效 setupAudio() {
??????? 尝试 {
??????????? AudioFormat audioFormat = getAudioFormat();
??????????? DataLine.Info dataLineInfo =
????????????????new DataLine.Info(TargetDataLine.class,
????????????????????????音频格式);
??????????? 目标数据线 = (目标数据线)
????????????????AudioSystem.getLine(dataLineInfo);
??????????? targetDataLine.open(audioFormat);
??????????? targetDataLine.start();
??????? } 捕捉(异常前){ ??????????? ex.printStackTrace();
??????????? System.exit(0);
??????? }
??? }
该 ??? 私有无效广播音频(){
??????? 尝试 {
??????????? DatagramSocket socket = new DatagramSocket(8000);
??????????? InetAddress inetAddress =
????????????????InetAddress.getByName("127.0.0.1");
??????????? ...
??????? } 捕捉(异常前){ ??????????? // 处理异常 ??????? }
??? }
进入无限循环,其中 ??? 而(真){
??????? int count = targetDataLine.read(
??????????? audioBuffer, 0, audioBuffer.length);
??????? 如果(计数> 0){
??????????? DatagramPacket 数据包 = 新的 DatagramPacket( ??????????? 音频缓冲区,audioBuffer.length,inetAddress,9786);
??????????? 套接字。发送(数据包);
??????? }
??? }
执行时,来自麦克风的声音以一系列数据包的形式发送到客户端。 UDP 音频客户端实现
公共类 AudioUDPClient {
??? 音频输入流音频输入流;
??? SourceDataLine sourceDataLine;
??? ...
??? 公共音频UDP客户端(){
??????? 启动音频();
??? }
??? 公共静态无效主(字符串 [] args){
??????? 新的 AudioUDPClient();
??? }
}
该 ??? 私有无效启动音频(){
??????? 尝试 {
??????????? DatagramSocket socket = new DatagramSocket(9786);
??????????? 字节 [] 音频缓冲区 = 新字节 [10000];
??????????? ...
??????? } 捕获(异常 e){ ??????????? e.printStackTrace();
??????? }
??? }
一个无限循环将接收来自服务器的数据包,创建一个 ??? 而(真){
??????? DatagramPacket 数据包 ??????????? = new DatagramPacket(audioBuffer, audioBuffer.length);
??????? socket.receive(数据包); ??????? ...
??? }
接下来,创建音频流。从数据包中提取字节数组。它用作 ??????? 尝试 {
??????????? 字节音频数据[] = packet.getData();
??????????? InputStream byteInputStream =
????????????????新的 ByteArrayInputStream(audioData);
??????????? AudioFormat audioFormat = getAudioFormat();
??????????? 音频输入流 = 新音频输入流(
??????????????? 字节输入流,
????????????????audioFormat, audioData.length /
????????????????audioFormat.getFrameSize());
??????????? DataLine.Info dataLineInfo = new DataLine.Info(
??????????????? SourceDataLine.class, audioFormat);
??????????? sourceDataLine = (SourceDataLine)
????????????????AudioSystem.getLine(dataLineInfo);
??????????? sourceDataLine.open(audioFormat);
??????????? sourceDataLine.start();
??????????? 播放音频();
??????? } 捕获(异常 e){ ??????????? // 处理异常 ??????? }
使用的 ??? 私人无效播放音频(){
??????? 字节[]缓冲区=新字节[10000];
???? ???尝试 {
??????????? 整数计数;
??????????? while ((count = audioInputStream.read(
?????????????????? 缓冲区, 0, 缓冲区.长度)) != -1) {
??????????????? 如果(计数> 0){
??????????????????? sourceDataLine.write(buffer, 0, count);
??????????????? }
??????????? }
??????? } 捕获(异常 e){ ??????????? // 处理异常 ??????? }
??? }
在服务器运行时,启动客户端将播放来自服务器的声音。可以通过在服务器和客户端中使用线程来处理声音的录制和播放来增强播放效果。为了简化示例,已省略此细节。 在这个例子中,连续的模拟声音被数字化并分解成数据包。这些数据包然后被发送到客户端,在那里它们被转换回声音并播放。 在其他几个框架中还提供了对 UDP 流的额外支持。在Java媒体框架(JMF)(http://www.oracle.com/technetwork/articles/javase/index-jsp-140239.html)支持音频和视频媒体的处理。该实时传输协议(RTP)(https://en.wikipedia.org/wiki/Real-time_Transport_Protocol)用于在网络上发送音频和视频数据。 概括在本章中,我们研究了 UDP 协议的性质以及 Java 如何支持它。我们比较了 TCP 和 UDP,以提供一些指导,以确定哪种协议最适合给定的问题。 我们从一个简单的 UDP 客户端/服务器开始来演示如何使用 的 随后讨论了 UDP 多播的工作原理。这提供了一种向组成员广播消息的简单技术。说明了 我们以一个如何使用 UDP 来支持音频流的例子结束。我们详细介绍了 在下一章中,我们将研究可用于提高客户端/服务器应用程序可伸缩性的技术。 |
|
网络协议 最新文章 |
使用Easyswoole 搭建简单的Websoket服务 |
常见的数据通信方式有哪些? |
Openssl 1024bit RSA算法---公私钥获取和处 |
HTTPS协议的密钥交换流程 |
《小白WEB安全入门》03. 漏洞篇 |
HttpRunner4.x 安装与使用 |
2021-07-04 |
手写RPC学习笔记 |
K8S高可用版本部署 |
mySQL计算IP地址范围 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/25 21:19:19- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |