java的UDP(一)

文章目录

  • 1. 简介
  • 2. UDP客户端
  • 3. UDP服务器
  • 4. DatagramPacket类

1. 简介

Java中的UDP实现分为两个类:DatagramPacket和DatagramSocket。DatagramPacket类将数据字节填充到UDP包汇总,这称为数据报,由你来解包接收的数据报。DatagramSocket可以收发UDP数据报。为发送数据,要将数据放到DatagramPacket中,使用DatagramPacket来发送这个包。要接受数据,可以从DatagramSocket中接受一个DatagramSocket对象,然后检查这个包的内容。Socket本身非常简单,在UDP种,关于数据报的所有信息(包括发送的目标地址)都包含在包本身中。Socket只需要了机在哪个本地端口监听或发送。这种职责划分和TCP使用的Socket和ServerSocket有所不同,首先,UDP没有两台主机间唯一连接的概念。一个Socket会收发所有指向指定端口的数据,而不需要知道对方时哪一个远程主机。一个DatagramPacket可以从多个独立主机收发数据。与TCP不同这个Socket并不专用于一个连接。事实上,UDP没有任何两台主机之间连接的概念,它只知道单个数据报。要确定由谁发送什么数据,这个时应用程序的责任。其次,TCP socket把网络连接看作流:通过从Socket得到的输入和输出流来接受数据,其次TCP socket把网络节点看作是流:通过从Socket得到的输入和输出流来收发数据。UDP不支持这一点,你处理的总是单个数据报包,填充在一个数据报的所有数据会以一个包的形式进行发送,这些数据作为一个组要么全部接受,要么完全丢失。一个包不一定与下一个包相关。给定两个包,数据报回尽可能地传递到接收方。

2. UDP客户端

这里我们还是拿美国国家标准与技术研究院的daytime服务器举例子。这里使用的传输层协议是UDP。首先在端口0打开一个Socket

DatagramSocket socket= new DatagramSocket(0);

只需要指定一个本地端口,Socket并不知道远程服务器是什么。通过指定端口为0,就是在请求Java为你随机选择一个可用的端口。下面使用SetTimeout()方法在连接上设置一个超时时间。单位为毫秒

socket.setSoTimeout(10000);

超时对于UDP比TCP更重要,因此TCP中会导致IOException异常的很多问题在UDP中只会悄无声息地失败。接下来需要建立数据包。需要建立两个数据包,一个是要发送的数据包,另一个是需要接受的数据包。

InetAddress host=InetAddress.getByName("time.nist.nov");
DatagramPacket request=new DatagramPacket(new byte[1],1,host,13);
//如果接受的数据大小超过1kb,多出的数据会被自动截断
byte[] data=new byte[1024];
DatagramPacket response= new DatagramPacket(data,data.length);

现在已经准备就绪,首先在这个Socket发送数据包,然后接受响应:

socket.send(request);
socket.receive(response);

最后从响应中提取字节,将它们转换为可以显示给最终用户的字符串:

String daytime=new String(response.getData(),0,response.getLength,"US-ASCII");
System.out.println(daytime);

构造函数以及send()和receive()方法都可能抛出一个IOException,且DatagramSocket实现了Autocloseable,下面是完整代码:

public class QuizCardBuilder {
    public static void main(String[] args) {
        try(DatagramSocket socket=new DatagramSocket(0)){
            socket.setSoTimeout(10000);
            InetAddress address=InetAddress.getByName("time.nist.gov");
            DatagramPacket request=new DatagramPacket(new byte[1],1,address,13);
            DatagramPacket response=new DatagramPacket(new byte[1024],1024);
            socket.send(request);
            socket.receive(response);
            String result=new String(response.getData(),0,response.getLength(),"US-ASCII");
            System.out.println(result);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

3. UDP服务器

UDP服务器几乎遵循与UDP客户端同样的模式,只不过通常在发送之前会先接收,而且不会选择绑定的匿名端口,与TCP不同,并没有单独的DatagramServerSocekt类。现在我们实现一个上面的简单的daytime服务器,首先在一个已知的端口上打开一个数据报Socket。对于daytime协议,这个端口为13:

DatagramSocket socket=new DatagramSocket(13);

接下来创建一个将接收请求的数据包,要提供一个将存储如站数据的byte数组,数组中的偏移量,以及要存储的字节数,

DatagramPacket request =new DatagramPacket(new byte[1024],0,1024);

然后接收这个数据包

socket.receive(request);

这个调用会被无限阻塞,直到一个UDP数据包到达13端口,如果有UDP数据包到达,java会将这个数据填充到byte数组,receive()方法返回。然后创建一个响应包,包括四个部分:要发送的原始数据、待发送的原始数据的字节数、要发送到的主机,以及发送到该主机上哪个端口。

String daytime= new Data().toString()+"\r\n";
byte[] data=daytime.getBytes("US-ASCII");
InetAddress host=request.getAddress();
int port = request.getPort();
DatagramPacket response=new DatagramPacket(data,data.length,host,port);

最后发送数据即可,下面是完整的服务器代码

public class QuizCardBuilder {
    public static void main(String[] args) throws InterruptedException {
         Thread server= new Thread(new server());
         Thread client= new Thread(new client());
         server.start();
         Thread.sleep(1000);
         client.start();
    }
}
class client implements Runnable{

    @Override
    public void run() {
        try(DatagramSocket socket=new DatagramSocket(0)){
            socket.setSoTimeout(10000);
            InetAddress address=InetAddress.getByName("localhost");
            DatagramPacket request=new DatagramPacket(new byte[1],1,address,8080);
            DatagramPacket response=new DatagramPacket(new byte[1024],1024);
            socket.send(request);
            socket.receive(response);
            String result=new String(response.getData(),0,response.getLength(),"US-ASCII");
            System.out.println(result);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
class server implements Runnable{

    @Override
    public void run() {
        try(DatagramSocket socket=new DatagramSocket(8080)){
           while(true){
               DatagramPacket request=new DatagramPacket(new byte[1024],1024);
               socket.receive(request);
               String daytime=new Date().toString();
               byte[] data=daytime.getBytes("US-ASCII");
               DatagramPacket datagramPacket=new DatagramPacket(data,data.length,request.getAddress(),request.getPort());
               socket.send(datagramPacket);
           }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
这个例子可以看出,UDP服务器与TCP服务器不同,往往不是多线程的,它们通常不会对某一个客户做太多工作,而且不会阻塞来等待另一端响应,因为UDP从来不会报告错误。对于UDP服务器来说,除非为了准备响应需要做大量耗费时间的工作,否则使用一种迭代方法就可以了。

4. DatagramPacket类

UDP数据报是基于IP数据报建立的,只向其底层IP数据报添加了很少的一点内容。如下图,UDP首部只向IP首部天际了8个字节。UDP首部包括源和目标端口号,IP首部之后所有内容的长度,以及一个可选的校验和。由于端口号以2字节无符号整数给出,因此每台主机有65536个不同的UDP端口可以使用。它们与每台主机的65536不同的TCP端口截然不同。因为长度也是一2字节无符号整数给出,所以数据报中的字节数不能超过65536-8字节。不过,这与IP首部中的数据报的长度字段是冗余的,它将数据报限制为65467-65507之间(具体大小取决于IP首部)。检验和字段是可选的,应用层程序不使用这个校验和,页无法访问这个校验和。如果数据的校验失败,那么底层网络软件会悄悄丢掉这个数据报。发送方或接受方都不会得到这个通知。毕竟UDP是不可靠的。在Java中,UDP数据报用品DatagramPacket类的实例表示:

在这里插入图片描述

  • 构造函数

取决于数据包用于发送数据还是接收数据。在这里6个构造函数都接受两个参数,一个时保存数据报数据的byte数组,另一个参数时该数组中用于数据报数据的字节数。希望接收数据报时,只需要提供这两个参数。当Socket从网络接收数据报时,它将数据报的数据存储在DatagramPacket对象的缓存区数组中,直到达到你指定的长度。第二组DatagramPacket构造函数用于创建通过网络发送的数据报。与前一组一样,这些构造函数需要一个缓冲区数组和一个长度,另外还需要指定数据包发去的地址和端口。

 接收数据报的构造函数
public DatagramPacket(byte[] buffer, int length)
public DatagramPacket(byte[] buffer, int offset, int length)

构造函数不关心缓冲区多大,甚至它希望你创建几M得DatagramPacket。不过,底层网络软件却不那么宽容,大多数底层UDP实现都不支持超过8192字节数据的数据报。事实上,很多操作系统不支持超过8KB的UDP数据报,否则就会将更大的数据报截断、分解或丢掉。如果数据报太大,而导致网络将其截断或者丢弃,java会收不到任何通知。

发送数据的构造函数
public DatagramPacket(byte[] data, int length, InetAddress destination ,int port)
public DatagramPacket(byte[] data, int offset, int length, InetAddress destination , int port)
public DatagramPacket(byte[] data, int length, SocketAddress destination)
public DatagramPacket(byte[] data, int offset,  SocketAddress destination)

每个构造函数都创建一个发往另一台主机的DatagramPacket。

在一个包中填充多少数据才合适?
这其实取决于实际情况,有些协议规定了包大小。如果网络非常不可靠,如分组无线电网络,则要选择较小的包,因为这样可以减少在传输中被破坏的可能性。另一方面,非常快速而可靠的LAN应当使用尽可能大的包。对于很多类型的网络,8KB字节往往是一个很好的折中方案。

  • get方法

DatagramPacket有6个获取数据报不同部分的方法:这些部分包括具体的数据以及首部的几个字段。这些方法主要用于从网络接收的数据报:

 public InetAddress getAddress()

该方法返回一个InetAddress对象,其中包含远程主机的地址。如果数据报时从Internet接收的,返回的地址是发送该数据报的机器地址(源地址)。另一方面,如果数据报是本地创建的,要发送到一个远程机器,那么这个返回会返回数据报将发往的那个主机地址。这个方法常用于确定发送UDP数据报的主机地址,使接收方可以回复

 public int getPort()

该返回返回远程端口。如果数据从Internet接收,这就是发送包的主机上的端口。如果数据报是本地创建的,要发送一个远程主机,那么这就是远程机器上包发往的目标端口

 public SocketAddress getSocketAddress()

该方法返回一个SocketAddress对象,包含远程主机的IP地址和端口。如果数据报从Internet接收的,返回的地址就是发送该数据报的机器的地址。如果是本地创建的,要发送到远程主机,这个返回数据报发往的主机地址。此外,如果你使用非阻塞I/O,DatagramChannel类可以接收一个SocketAddress,而不接收单独的InetAddress和端口

 public byte[] getData()

返回一个byte数组,其中包含数据报中的数据。为了能够在你的程序中使用,通常必须将这些字节转换为其他的某种数据形式。一种方法是将byte数组转换为一个String。

String s=new String(dp.getData() ,"UTF-8")

如果数据报不包含文本,那么将它转换为java数据会更加困难。一种方法是将getData()返回的Byte数组转换一个ByteArrayInputStream。

//指定offset和length的原因是,返回的数组可能有额外的空间没利用到
InputStream in= new ByteArrayInputStream(packet.getData(),packet.getOffset(),packet.getLength());

然后ByteArrayInputStream可以串链到DataInputStream,接下来可以使用DataInputStream得readInt()、readLong()、readChar()及其他方法读取数据。

public int getLength()

该方法返回数据报中数据的字节数。

 public int getOffset()

对于getData()返回的数组,这个方法会返回该数组的一个位置,即开始填充数据报数据的那个位置。

下面的程序使用了上面介绍的所有get方法

 public static void main(String[] args) throws InterruptedException {
         String s="This ia a test";
         try{
             byte[] data =s.getBytes("UTF-8");
             InetAddress ia=InetAddress.getByName("www.ibiblio.org");
             int port=7;
             DatagramPacket dp=new DatagramPacket(data,data.length,ia,port);
             System.out.println("This packet is adderssed to "+ dp.getAddress()+"  on port "+dp.getPort());
             System.out.println("There are "+dp.getLength()+" bytes of data in the packet");
             System.out.println(new String(dp.getData(),dp.getOffset(),dp.getLength(),"UTF-8"));

         } catch (UnsupportedEncodingException e) {
             throw new RuntimeException(e);
         } catch (UnknownHostException e) {
             throw new RuntimeException(e);
         }
    }

在这里插入图片描述

  • Set方法

Java还提供了几个方法,可以在创建数据报之后改变数据、远程地址和远程端口。如果创建和垃圾回收新DatagramPacket对象的时间会严重影响性能,这些方法就很重要。

public void setData(byte[] data)

该方法可以改变UDP数据报的有效载荷。如果要向远程主机发送大文件,可能就用到这个方法。你可以重复地发送相同的DatagramPacket对象,每次只改变数据

public void setData(byte[] data, int offset, int length)

这个重载的setData方法提供了另一个途径来发送大量的数据。与发送大量新数组不同,可以将所有数据放到一个数组中,每次发送一个部分。如下:

int offset=0;
DatagramPacket dp= new DatagramPacket(bigarray, offset, 512);
int bytesSent=0;
while(bytesSent < bigarray,length){
	socekt.send(dp);
	bytesSent+=dp.getLength();
	int bytesToSend=bigarray.length-bytesSent;
	dp.setData(bigarray,bytesSent,size);
 public void setAddress(InetAddress remote)

该方法会修改数据报发往的地址,这允许你将同一个数据报发送多个不同的接收方。

String s="Really Important Message";
byte[] data= s.getBytes("UTF-8);
DatagramPacket dp=new DatagramPacket(data ,data.length);
dp.setPort(2000);
int network="128.238.5.";
for (int host= 1;host <255 ;host++)
{
	try{
		InetAddress remote=InetAddress.getByName(network+host);
		dp.setAddress(remote);
		socket.send(dp);
	}catch(IOException ex)
	{
	 }
}
 public void setPort(int port)

该方法会改变数据报发往的端口。

public void setAddress(SocketAddress remote)

该方法会改变数据包要发往的地址和端口,在回复时可以使用这个方法,例如下面代码将接收一个数据报包,用包含字符串的包响应同一个地址

DatagramPacket input= new DatagramPacket(new byte[8192] ,8192);
socekt.receive(input);
DatagramPacket output=new DatagramPacket(" hello there".getBytes("UTF-8"),11) ;
SocketAddress address=input.getSocketAddress();
output.setAddress(address);
socket.send(output);
 public void setLength(int length)

该方法会改变内部缓冲区汇总包含实际数据报数据的字节数,而不包括未填充的数据的空间。这个方法在接收数据报时很有用,当接收到数据报时,其长度设置为入站数据的长度。这表示如果试图在同一个DatagramPacket中接收另一个数据报,那么会限制第二个数据报的字节数不能对于第一个数据报的字节数。也就是说,一旦接受了一个10字节的数据报,所有后续的数据报都将截断为10字节。一旦接受到9个字节数据报,所有后续的数据报都会截断到9字节。有了这个方法,我们可以修改缓冲区的长度,这样使用相同的Datagrampacket接受其他数据报时不会截断数据报

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/25325.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Day57【动态规划】647.回文子串、516.最长回文子序列

647.回文子串 力扣题目链接/文章讲解 视频讲解 1、确定 dp 数组下标及值含义 dp[i][j]&#xff1a;表示区间范围为 [i, j] 的子串是否为回文串&#xff08;j > i&#xff09; 这样定义才方便我们的递推&#xff01;怎么想到的&#xff1f;回文串需要对比串的两端&#…

用可编程逻辑器件FPGA LCMXO2-4000HC-6MG132I 实现智能汽车解决方案设计

LCMXO2-4000HC-6MG132I lattice莱迪斯深力科 MachXO2 可编程逻辑器件 (PLD) 由六个超低功耗、即时启动、非易失性 PLD 组成&#xff0c;可提供 256 至 6864 个查找表 (LUT) 的密度。 MachXO2 系列 PLD 提供多种特性&#xff0c;例如嵌入式块 RAM (EBR)、分布式 RAM 和用户闪存 …

基于html+css的图片展示93

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

Spring Boot注解@Async与线程池的配置

目录 使用异步注解创建异步任务 Async注解 使用Demo 线程池配置 Spring Boot默认用于异步任务线程池配置 线程池配置 线程池隔离 为什么需要线程池隔离&#xff1f; 线程池隔离实现Demo 线程池配置&#xff1a; 异步任务&#xff1a; 测试demo 参考内容&#xff1a; 使…

Modern CSV:大型 CSV 文件编辑器/查看器 Crack

Modern CSV用于快速查看大型 CSV 文件 适用于 Windows、Mac 和 Linux 的复杂 CSV 编辑器/查看器 被使用 电子商务运营商。数据科学家。会计师。 IT 专业人员。学生。医学研究人员。数字营销人员。生物学家。工程师。 现代 CSV 是适用于 Windows、Mac 和 Linux 的功能强大的表格…

面向对象编程 实验三 sduwh 子窗口与控件的基本用法、资源的使用 参考实验报告1

源自网络收集&#xff0c;仅供参考 实验三收集到两份完整报告&#xff0c;这是其一&#xff0c;另一份见本专栏下一篇文章。 实验题目 《面向对象程序设计》 实验三 实验题目&#xff1a;子窗口与控件的基本用法、资源的使用 整体目的&#xff1a;理解、窗口之间的消息传送…

【Netty】Netty中的超时处理与心跳机制(十九)

文章目录 前言一、超时监测二、IdleStateHandler类三、ReadTimeoutHandler类四、WriteTimeoutHandler类五、实现心跳机制5.1. 定义心跳处理器5.2. 定义 ChannelInitializer5.3. 编写服务器5.4. 测试 结语 前言 回顾Netty系列文章&#xff1a; Netty 概述&#xff08;一&#…

内存栈与CPU栈机制

1. 内存栈: 先入后出,LIFO(LAST IN FIRST OUT) 入栈:将一个新的元素放到栈顶 出栈:从栈顶取出一个元素 栈顶元素总是最后一个入栈,需要时出栈. 2.CPU栈机制 8086CPU提供相关指令以栈方式来访问内存空间.相当于将一段内存当做栈来使用 8086CPU提供的入栈指令为:PUSH ,出栈指令为…

JointJS+ v3.7 Crack

JointJS v3.7 改进了对 SVG 上下文中的外部对象的支持。 2023 年 5 月 30 日 - 16:00 新版本 特征 改进了对外部对象 (HTML) 的支持- 外部对象已成为 Web 开发的标准&#xff0c;JointJS 现在已经在 SVG 上下文中引入了对外部对象的全面且功能齐全的支持。这意味着您现在可以在…

Elasticsearch 8.8.0 发布

Elasticsearch 是一个基于 Lucene 库的搜索引擎。它提供了一个分布式、支持多租户的全文搜索引擎&#xff0c;具有 HTTP Web 接口和无模式 JSON 文档。Elasticsearch 基于 Java 开发&#xff0c;并在 SSPL Elastic License 双重授权许可下作为开源软件发布。 Elasticsearch 8…

win10系统如何设置虚拟回环

在日常生活中&#xff0c;人们(特别是IT行业者)通常需要在一台机上进行软件测试&#xff0c;而同一台计算上通常只能使用一个地址&#xff0c;而在需要同时使用两个地址进行测试的时候就显得捉襟见肘。此方法通过配置window10自带的环回适配器&#xff0c;达到上述目的。 win1…

如何使用Kali进行信息收集?

渗透测试即模拟黑客入侵的手段对目标网络进修安全测试&#xff0c;从而发现目标网络的漏洞&#xff0c;对目标网络进行安全加固与漏洞修复。 Kali 是一个基于 debian 的渗透测试平台&#xff0c;其中集成了很多常见的和不常见的渗透测试工具&#xff0c;如下图&#xff1a; 工…

基于SSM的服装设计供需系统设计与实现

摘 要&#xff1a;作为服装设计的重要形式之一&#xff0c;服装具有显著的审美性&#xff0c;是人类情感表达不可忽视的代表形态。但在新时期背景下&#xff0c;随着服装设计的进一步优化&#xff0c;服装设计创新融合强度也随之增强。本文就服装设计供需系统进行深入探究。 服…

Linux之命令搜索

目录 Linux之命令搜索 Whereis命令 定义 基本信息 举例 which命令 定义 与whereis命令的区别 基本信息 举例 locate 命令 定义 优点 缺点 基本信息 案例 Linux之命令搜索 Whereis命令 定义 whereis --- 搜索系统命令的命令&#xff08;像绕口令一样&#xff09…

数据库新闻速递 明白3中主流的数据迁移方法 (译)

头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共8…

ShardingSphere笔记(三):自定义分片算法 — 按月分表·真·自动建表

ShardingSphere笔记&#xff08;二&#xff09;&#xff1a;自定义分片算法 — 按月分表真自动建表 文章目录 ShardingSphere笔记&#xff08;二&#xff09;&#xff1a;自定义分片算法 — 按月分表真自动建表一、 前言二、 Springboot 的动态数据库三、 实现我们自己的动态数…

MySQL查询当前数据和上一行数据比较、业务数据的趋势分析、数据变动的监控和报警

标题: 使用MySQL查询当前数据和上一行数据比较的场景 在MySQL中&#xff0c;我们经常需要对数据进行比较和分析。其中一种常见的需求是查询数据列表并与前一行的数据进行比较。这种场景可以通过使用窗口函数或连接来实现。本文将介绍使用MySQL查询比较数据和上一行数据的场景&a…

计算机组成原理-指令系统-指令格式及寻址方式

目录 一、指令的定义 1.1 扩展操作码指令格式 二、指令寻址方式 2.1 顺序寻址 2.2 跳跃寻址 三、 数据寻址 3.1 直接寻址 3.2 间接寻址 3.3 寄存器寻址 ​ 3.4 寄存器间接寻址 3.5 隐含寻址 3.6 立即寻址 3.7 偏移地址 3.7.1 基址寻址 3.7.2 变址寻址 3.7.3 相对寻址…

【C++】右值引用和移动语义(详细解析)

文章目录 1.左值引用和右值引用左值引用右值引用 2.左值引用和右值引用的比较左值引用总结右值引用总结 3.右值引用的使用场景和意义知识点1知识点2知识点3知识点4总结 4.完美转发万能引用见识完美转发的使用完美转发的使用场景 1.左值引用和右值引用 传统的C语法中就有引用的…

【SpringCloud】SpringAMQP总结

文章目录 1、AMQP2、基本消息模型队列3、WorkQueue模型4、发布订阅模型5、发布订阅-Fanout Exchange6、发布订阅-DirectExchange7、发布订阅-TopicExchange8、消息转换器 1、AMQP Advanced Message Queuing Protocol&#xff0c;高级消息队列协议。是用于在应用程序之间传递业务…