java的UDP(一)

news2024/12/26 23:08:45

文章目录

  • 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.coloradmin.cn/o/602978.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023北京大学首席营销官CMO高级研修班简介

【课程背景】自 2022 年以来&#xff0c;营销新趋势呈现以下变化&#xff1a;消费渠道多元化&#xff0c;社会化媒体 成为信息主要渠道&#xff0c;流量红利消失&#xff0c;数字营销成当下主流&#xff1a;元宇宙数字营销成热点&#xff1b;私域运营更受品牌重视&#xff1b;国…

MySQL主存复制

介绍 配置-主库master 第一步&#xff1a;修改MySQL数据库的配置文件/etc/my.cnf [mysqld] log-binmysql-bin #[必须]启用二进制日志 server-id100 #[必须]服务器唯一id第二部&#xff1a;重启MySQL服务 systemctl restart mysqld第三步&#xff1a;登录MySQL操作&#x…

Talk预告 | 新加坡国立大学张傲:10%成本定制类 GPT-4 多模态大模型

本期为TechBeat人工智能社区第502期线上Talk&#xff01; 北京时间06月01日(周四)20:00&#xff0c;新加坡国立大学在读博士生 — 张傲的Talk将准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “10%成本定制类 GPT-4 多模态大模型 ”&#xff0c;届时将介…

RemoteSensing投稿流程

RemoteSensing投稿流程&#xff0c;底部有官网地址和word模板 RemoteSensing投稿流程&#xff0c;遇到的坑RemoteSensing期刊官网地址&#xff0c;模板 RemoteSensing投稿流程&#xff0c; ##3.16 Submit &#xff08;第一次提交&#xff09; 3.17 Under review &#xff08;正…

中核浦原一号数字员工诞生,实在智能为智能制造再添强大引擎

头戴博士帽&#xff0c;脚踩喷气引擎&#xff0c;身披“科技蓝”披风&#xff0c;今年的全国科技工作者日&#xff0c;一位硬“核”数字员工正式诞生&#xff01; 这就是由上海中核八所科技有限公司&#xff08;下称中核八所&#xff09;人工智能研发中心为上海中核浦原有限公司…

网工内推 | 上海网工专场,NP认证,朝九晚六,多次调薪机会

01 上海伍贤信息技术股份有限公司 &#x1f537;招聘岗位&#xff1a;网络工程师 &#x1f537;职责描述&#xff1a; 1、在网络基础设施方面提供日常支持&#xff0c;例如&#xff0c;WIFI、局域网、视频会议等。 2、协助管理亚太地区的网络安全服务&#xff08;防火墙/代理/…

CVE-2023-33246 Apache RocketMQ 命令注入漏洞复现及分析

CVE-2023-33246 Apache RocketMQ 命令注入漏洞复现及分析 0x0.威胁情报&#xff1a; 漏洞编号CVE编号CVE-2023-33246漏洞评估危害评级高危漏洞类型RCE公开程度PoC已公开利用条件1.在受影响版本内威胁类型远程利用情报在野利用无漏洞活跃度中影响产品产品名称Apache RocketMQ受…

代码随想录算法训练营第二十四天| 回溯理论基础 、77. 组合

理论基础 回溯通常隐藏在递归函数的下面&#xff0c;递归和回溯是相辅相成的&#xff0c;通常来说 回溯函数就是指递归函数。 回溯法其实是一个纯暴力的搜索&#xff0c;有些问题只能依靠回溯法将所有的结果搜出来。 例如&#xff1a; 组合问题&#xff1a;N个数里面按一定规…

【VictoriaMetrics】VictoriaMetrics单机版数据删除

在使用vm的过程中,我们会遇到需要删除不必要的数据,那vm是如何操作数据删除的呢? VictoriaMetrics通过发送HTTP请求到http://<victoriametrics-addr>:8428/api/v1/admin/tsdb/delete_series?match=<timeseries_selector_for_delete> 其中<timeseries_sele…

npm+模块加载机制

1.npm与包 1.什么是包 Node.js中的第三方模块又叫做包。 就像电脑和计算机指的是相同的东西&#xff0c;第三方模块和包指的是同一个概念&#xff0c;只不过叫法不同。 2.包的来源 不同于Node,js中的内置模块与自定义模块&#xff0c;包是由第三方个人或团队开发出来的&am…

Android 应用弹出悬浮窗

Android开发者经常遇到应用想弹出悬浮窗的操作&#xff0c;而且有可能还想要高层级弹窗&#xff0c;就像ipone的浮标touch一样。android当然也有类似的悬浮图标&#xff0c;比如前些年我们的流量监控提醒。 这里我们忽略UI美学&#xff0c;简单记录一下&#xff1a; 1、基本使…

mac os 安装rz/sz

说明&#xff1a;使用rz sz实现终端的文件传输&#xff0c;该命令主要使用场景为 macos中通过堡垒机登陆后无法使用ftp工具传输文件。 工具&#xff1a;iTerm2、lrzsz、homebrew 以及两个脚本文件&#xff08;iterm2-recv-zmodem.sh、iterm2-send-zmodem.sh&#xff09; …

基于确定有穷自动机(DFA算法)实现敏感词过滤

1.DFA算法简介 DFA(Deterministic Finite Automaton) 是一种非递归自动机&#xff0c;也称为确定有穷自动机。它是通过event和当前的state得到nextstate&#xff0c;即eventstatenextstate。 确定&#xff1a;状态以及引起状态转换的事件都是可确定的。 有穷&#xff1a;状态以…

EBU6304 Software Engineering 知识点总结_4 analysis, design, implementation, testing

analysis 仅仅从用户的需求来看得到的消息不全面&#xff0c;还需要分析。用户可能认为你明白了&#xff0c;或者他考虑不全面&#xff0c;觉得一些地方是不需要的。 因此我们需要分析来 Refining requirements。 gather requirements-analyse in real world context-develo…

(字符串 ) 剑指 Offer 58 - II. 左旋转字符串 ——【Leetcode每日一题】

❓剑指 Offer 58 - II. 左旋转字符串 难度&#xff1a;简单 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如&#xff0c;输入字符串"abcdefg"和数字2&#xff0c;该函数将返回左旋转两位得到的…

Codeforces Round 875 (Div. 2)

Problem - D - Codeforces 思路&#xff1a; 注意到bibj<2*n&#xff0c;所以ai*aj<2*n&#xff0c;即我们实际只需要枚举个a的匹配即可为了不重不漏&#xff0c;我们可以枚举x从1到&#xff0c;寻找所有与x匹配且大于等于x的[a,b]对这样复杂度就是 #include <bits…

OpenCV笔记整理【人脸检测之Harr级联分类器dlib库】

1. 级联分类器&#xff1a; OpenCV提供的级联分类器有Harr、HOG、LBP这3种&#xff0c;这些分类器以XML文件保存&#xff0c;这里主要演示Harr检测人脸&#xff08;OpenCV提供的分类器不仅限于检测人脸&#xff0c;还包括下表特征检测&#xff0c;当然OpenCV还支持训练自己的级…

Vim学习笔记【Ch02】

Vim学习笔记 系列笔记链接Ch02 Buffers, Windows, TabsBuffers什么是buffer查看所有bufferbuffer之间的切换删除buffer退出所有窗口 Windows窗口的创建窗口切换快捷键其他快捷键 Tabs什么是tabtab相关命令 window和buffer结合的3D移动小结 系列笔记链接 Ch00&#xff0c;Ch01 …

python介绍——入门

前言 Python 是一种面向对象的解释型计算机程序设计语言&#xff0c;它继承了传统编译语言的强大性和通用性&#xff0c;同时也借鉴了脚本语言和解释语言的易用性。今天我就来给大家简单介绍一下python&#xff1a; 一、python的起源 1989年的圣诞节期间&#xff0c;吉多▪范…

Redis底层学习(二)—存储类型-String篇

这里写目录标题 应用场景底层结构知识普及 应用场景 服务器命令&#xff1a; 1.单值缓存 存储key,value &#xff1a;SET key value2.对象缓存 存储key,value :SET key value&#xff08;json 格式数据&#xff09; 3.分布式锁 底层结构 我们可以通过命令查看 value…