您的位置  > 互联网

Java下的UDP协议的基本概念介绍-苏州安嘉

为什么使用UDP

选择要使用的协议时,选择 UDP 时必须小心。 在网络质量不是很理想的环境下,UDP协议丢包会比较严重。 但是由于UDP的特点:它不是连接型协议,因此它具有资源消耗低、处理速度快的优点,所以通常音频、视频和普通数据都是使用UDP来传输的,因为即使偶尔会丢失一两个数据包,不会对接收结果产生太大影响。 例如,我们聊天时使用的ICQ和OICQ就是使用的UDP协议。

1.使用发送和接收数据的原理

Java使用UDP来表示UDP协议。 这只是一个码头。 它不维护状态,也不能生成 IO 流。 它唯一的功能是接收和发送数据报。 Java 使用 UDP 来表示数据报。 数据的接收和发送都是通过对象完成的。

1. 构造函数

():创建实例并将对象绑定到机器的默认IP地址和机器所有可用端口中随机选择的端口。

(int prot):创建实例并将对象绑定到本地默认IP地址和指定端口。

(int port, laddr):创建实例并将对象绑定到指定的IP地址和端口。

可以通过上面三个构造函数中的任何一个来创建实例。 通常在创建服务器时,会创建具有指定端口的实例 - 这确保其他客户端可以向服务器发送数据。 获取实例后,您可以通过以下两种方式接收和发送数据。

(p):从中接收数据报。

send(p):用该对象发送数据报。

从上面两种方法可以看出,发送数据报时,你并不知道数据报要发送到哪里,而是自己决定数据报的目的地。 就像码头不知道每个集装箱的目的地一样,码头只是将这些集装箱发送出去,集装箱本身就包含了集装箱的目的地。

2. 构造函数

(byte[] buf, int):从空数组创建对象。 该对象用于接收数据。

(byte[] buf, int, addr, int port):创建一个对象作为包含数据的数组。 创建对象时,您还可以指定 IP 地址和端口 - 这决定了数据报的目的地。

(byte[] buf, int, int):创建一个空数组的对象,并指定接收到的数据放入buf数组时的起点,最多1个字节。

(byte[] buf, int, int, , int port):创建一个用于发送的对象,指定从buf数组的开头开始发送的字节数。

当/程序使用UDP协议时,实际上没有明显的服务器端和客户端,因为双方都需要先创建一个对象来接收或发送数据报,然后使用该对象作为载体来传输数据。 通常将具有固定IP地址和固定端口的对象所在的程序称为服务器,因为它可以主动接收客户端数据。

在接收数据之前,应该使用上面的第一个或第三个构造函数生成一个对象,给出接收数据的字节数组及其长度。 然后被调用的()方法等待数据报的到来,()会等待(该方法会阻塞调用该方法的线程)直到接收到数据报。

如下代码所示:

//创建一个对象来接收数据

=新(buf,256);

//接收数据报

.();

在发送数据之前,调用第二个或第四个构造函数创建一个对象。 这时,字节数组就存储了你要发送的数据。 此外,必须给出完整的目标地址,包括IP地址和端口号。 发送数据是通过send()方法实现的。 send()方法根据数据报的目的地址寻找传输数据报的路径。 如下代码所示:

//创建一个对象来发送数据

= new (buf, , , 端口);

//发送数据报

。发送();

使用接收数据时,会感觉设计过于繁琐。 开发者只关心能存储多少数据,根本不关心是否使用字节数组来存储数据。 但是Java要求在创建接收数据的方法时,必须传入一个空字节数组,数组的长度决定了可以放多少数据,这实际上暴露了实现细节。 然后提供了一个()方法,可以返回封装在对象中的字节数组。 这种方法就更加多余了——如果程序需要获取封装在对象中的字节数组,直接访问传递给构造函数的字节即可。 数组实参就足够了,不需要调用该方法。

当服务器(或客户端)接收到一个对象时,它想要向数据报的发送者“反馈”一些信息。 然而,由于UDP协议是非面向连接的,接收者并不知道每一个数据。 报告是谁发送的,但是程序可以调用以下三个方法来获取发送者的IP地址和端口。

():当程序准备发送该数据报时,该方法返回该数据报的目标机器的IP地址; 当程序刚刚收到一个数据报时,该方法返回数据报发送主机的IP地址。

int():当程序准备发送该数据报时,该方法返回该数据报的目标机器的端口; 当程序刚刚收到一个数据报时,该方法返回数据报发送主机的端口。

():当程序准备发送该数据报时,该方法返回该数据报的目标; 当程序刚刚收到一个数据报时,该方法返回数据报的发送主机。 ()方法的返回值是一个对象,实际上是一个IP地址和一个端口号。 也就是说,该对象封装了一个对象和一个表示端口的整数,因此该对象既可以用来表示IP地址,也可以用来表示端口。

在 Java 中操作 UDP

使用JDK包中的 和 类,您可以非常方便地控制用户数据消息。

在描述它们之前,有必要了解位于同一位置的类。 实现 Java.io。 接口并且不允许继承。 它用于描述和包装IP地址,通过三种方法返回实例:

():返回封装本地地址的实例。

(host):返回封装主机地址的实例数组。

(host):返回一个封装了Host地址的实例。 Host可以是域名,也可以是合法的IP地址。

该类用于创建接收和发送 UDP 协议的实例。 与类依赖类一样,类的实现也依赖于专门为其设计的工厂类。 该类有 3 个构建器:

():创建实例。 这是一个比较特殊的用法,通常用在客户端编程中。 它没有特定的监听端口,仅使用临时端口。

(int port):创建实例,监听Port端口的数据包。

(int port, ):这是一个非常有用的构建器。 当一台机器有多个IP地址时,它创建的实例只接收来自的消息。

值得注意的是,在创建类实例时,如果端口已被使用,则会抛出异常并导致程序非法终止。 这个异常应该被捕获。 该类主要有4个方法:

(d):接收数据包进入d。 该方法产生“阻塞”。

Send(d):将消息d发送到目的地。

(int):设置超时时间,单位为毫秒。

关闭():关闭。 当应用程序退出时,通常会主动释放资源并关闭。 但由于异常退出,资源可能无法回收。 因此,应该在程序完成时,或者在抛出异常后捕获Close后,主动使用该方法关闭。

“阻塞”是一个专业术语,它创建一个内部循环,暂停程序直到触发条件。

该类用于处理数据包。 它将Byte数组、目的地址、目的端口等数据打包成数据包或者将数据包拆解成Byte数组。 应用程序在生成数据包时要注意。 TCP/IP 指定数据包的大小。 最多包含65507,通常主机接收548字节,但大多数平台可以支持8192字节大小的消息。 该类有 4 个构建器:

(byte[] buf, int, addr, int port):从Buf数组中取出long数据,创建对象。 目标是Addr地址和Port端口。

(byte[] buf, int, int, , int port):从Buf数组中取出第一个long数据,创建一个对象。 目标是Addr地址和Port端口。

(byte[] buf, int, int):将数据包开头和结尾的数据加载到Buf数组中。

(byte[] buf, int):将数据包中的long数据加载到Buf数组中。

该类最重要的方法是(),它从实例中获取消息的Byte数组编码。

下面的程序使用/结构实现网络通信。 该程序的服务器端使用1000次循环来读取数据报,并在每次读取内容时向数据报的发送者发送一条消息。

服务器端程序代码如下。

.java

班级

最终int PORT = 30000;

//定义每个数据报的最大大小为4KB

最终int = 4096;

//定义接收网络数据的字节数组

字节[]=新字节[];

//创建一个准备从指定字节数组接收数据的对象

=

新的 (, 。);

//定义一个发送对象

;

//定义一个字符串数组,服务器发送该数组的元素

[]书籍=新[]

《疯狂Java讲义》,

《轻量级Java EE企业应用实践》,

《疯狂的讲义》,

《疯狂的 Ajax 讲义》

};

无效初始化()

尝试(

//创建对象

= 新(端口))

// 使用循环接收数据

for (int i = 0; i < 1000; i++)

//读取数据,并将读取到的数据放入封装数组中

.();

// 判断.()和是否是同一个数组

.out.( == .());

//将接收到的内容转成字符串并输出

.out.(新(

, 0, .()));

// 从字符串数组中取出一个元素作为发送数据

byte[] = books[i % 4].();

// 使用指定的字节数组作为发送数据,并使用刚刚接收到的数据

// 源被创建为目标

= 新(

, . , .());

// 发送数据

。发送();

无效主([]参数)

new().init();

客户端程序代码与此类似。 客户端使用循环不断读取用户的键盘输入。 每当它读取用户输入的内容时,就会将内容封装成数据报,然后发送数据报; 然后将其中的数据读入接收介质(实际上是读入封装好的字节数组)。

客户端程序代码如下。

.java

班级

//定义发送数据报的目的地

最终int = 30000;

最终=“127.0.0.1”;

//定义每个数据报的最大大小为4KB

最终int = 4096;

//定义接收网络数据的字节数组

字节[]=新字节[];

//创建一个准备从指定字节数组接收数据的对象

=

新的 (, 。);

//定义一个发送对象

= 空;

无效初始化()

尝试(

//使用随机端口创建客户端

=新())

// 用于初始化发送,包含一个长度为0的字节数组

= new (新字节[0],0

, .() , );

//创建键盘输入流

扫描=新的(.in);

//不断读取键盘输入

同时(扫描。())

// 将键盘输入的一行字符串转换为字节数组

byte[] buff = scan.().();

//设置发送的字节数据

.(增益);

//发送数据报

。发送();

//读取数据,读取的数据放入封装好的字节数组中

.();

.out.(新(, 0

, .()));

无效主([]参数)

new().init();

客户端和服务器唯一的区别在于,服务器的IP地址和端口是固定的,因此客户端可以直接向服务器发送数据报,而服务器则需要根据收到的数据报来决定“反馈”。 数据报的目的地。

读者可能会发现,在使用网络通信时,服务器不需要也无法保存每个客户端的状态。 客户端完全有可能在向服务器发送数据报后立即退出。 但无论客户端是否退出,服务器都无法获知客户端的状态。

使用UDP协议时,如果希望将一个客户端发送的聊天信息转发给所有其他客户端,是比较困难的。 可以考虑在服务器端使用Set集合来保存所有客户端信息。 每当客户端收到来自端的数据报后,程序会检查数据报的来源是否在Set集合中,如果不在,则将其添加到Set集合中。 这就涉及到另一个问题:有的客户端可能会发送一个数据报,然后永久退出程序,但是服务器也将客户端的信息保存在Set集合中……总之,这种方法需要处理的问题比较多,编程比较麻烦。 幸运的是,Java提供了UDP协议的类,通过这些类可以很容易地实现多播。