网络编程套接字(3)——Java数据报套接字(UDP协议)

2024-03-10 08:20

本文主要是介绍网络编程套接字(3)——Java数据报套接字(UDP协议),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、Java数据报套接字通信模型

二、UDP数据报套接字编程

1、DatagramSocket

        (1)DatagramSocket构造方法

        (2)DatagramSocket方法

2、DatagramPacket

        (1)DatagramPacket构造方法

        (2)DatagramPacket方法

3、InetSocketAddress

三、代码示例:回显服务器

        1、服务器代码

     代码解析

          2、客户端代码

代码解析

3、创建DatagramPacket三种不同的参数场景

4、执行代码

5、客户端和服务器交互的过程


一、Java数据报套接字通信模型

        对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次发送全部数据报。

        java中使用UDP协议通信,主要基于DatagramSocket类来创建数据报套接字,并使用DatagramSocket作为发送或接收的UDP数据报。对于一次发送及接收UDP数据报的流程如下:

        以上只是一次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就是只有请求,没有响应。对于一个服务端来说,重要的是提供多个客户端的请求处理响应,流程如下:


二、UDP数据报套接字编程

        Java把系统原生的API进行了封装,供我们使用,以下是API介绍,主要核心的类有两个:DatagramSocketDatagramPacket

1、DatagramSocket

        操作系统中,有一类文件就叫做 socket 文件,普通文件、目录文件是在硬盘上的,socket 文件抽象表示了 “网卡” 这样的硬件设备

        进行网络通信最核心的硬件设备就是 网卡

                通过网卡发送数据,就是写 socket 文件。

                通过网卡接受数据,就是读 socket 文件。

        DatagramSocket是 UDP 的 Socket,负责对 socket 文件的读写也就是借助网卡发送和接收数据报

        (1)DatagramSocket构造方法

方法签名方法说明
DatagramSocket()

创建一个UDP数据报套接字的Socket,绑定到本机

任意一个随机端口(一般用于客户端)

DatagramSocket(int port)

创建一个UDP数据报套接字的Socket,绑定到本机

指定的端口(一般用于服务端)

        (2)DatagramSocket方法

方法签名方法说明
void receive(DatagramPacket p)

从此套接字接受数据报(如果没有收到数据报,该

方法会阻塞等待)

void send(DatagramPacket p)从此套接字发送数据包(不会阻塞等待,直接发送)
void close()关闭此数据报套接字

2、DatagramPacket

        DatagramPacket是UDP Socket发送和接收的数据报

        UDP面向数据报,每次发送接受数据的基本单位,就是一个UDP数据报,表示了一个UDP数据报。

        (1)DatagramPacket构造方法

方法签名方法说明
DatagramPacket(byte[] buf, int length)

构造一个DatagramPacket以用来接收数据报,接收的

数据保存在字节数组(第一个参数buf)中,接收指定

长度(第二个参数length)

DatagramPacket(byte[] buf, int offset, int length,

SocketAddress address)

构造一个DatagramPacket以用来发送数据包,发送的

数据为字节数组(第一个参数buf)中,从0到指定长度

(第二个参数length)。address指定目的主机IP和端口号

        (2)DatagramPacket方法

方法签名方法说明
InetAddress getAddress()

从接收的数据报中,获取发送端主机IP地址;

或从发送的数据报中,获取接收端主机IP地址

int getPort()

从接收的数据报中,获取发送端主机的端口号;

或从发送的数据报中,获取接收端主机的端口号

byte[] getData()获取数据报中的数据

        构造UDP发送的数据报时,需要传入SocketAddress,该对象可以使用InetSocketAddress来创建。

3、InetSocketAddress

        InetSocketAddress (SocketAddress的子类) 构造方法:

方法签名方法说明
InetSocketAddress(InetAddress addr, int port)创建一个Socket地址,包含IP地址和端口号

三、代码示例:回显服务器

        在网络编程中,回显服务器是最简单的程序了,相当于网络编程的 hello world

        功能:服务器接受客户端的请求,返回响应。(根据客户端发出的请求,返回啥响应,要更具实际的业务场景来编写代码,这里的案例没啥业务逻辑)

        1、服务器代码

public class UdpEchoServer {private DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);//参数里面指定端口号}//服务器的启动逻辑public void start() throws IOException {System.out.println("服务器启动");while (true) {//1、读取请求并解析DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);//把读到的字节数组,转换成字符串,方便后面的逻辑处理String request = new String(requestPacket.getData(), 0, requestPacket.getLength());//2、根据请求计算响应(对于回显服务器来说,这一步啥也不用做)String response =process(request);//3、把响应返回给客户端//构造一个 DatagramPacket 作为响应对象DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);//打印日志System.out.printf("[%s : %d] req: %s resp: %s", requestPacket.getAddress().toString(),requestPacket.getPort(), request, response);System.out.println();}}//计算响应计算方法(这里没逻辑)public String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer udpEchoServer = new UdpEchoServer(9090);udpEchoServer.start();}
}

     代码解析

        第一步需要创建 DatagramSocket 对象,接下来要操作网卡,都是通过 socket 对象来完成的。socket 对象是在内存中的,操作这个对象就能影响到网卡类似遥控器。

        这个程序需要绑定 / 关联上一个操作系统上的端口号,所以构造方法里传的就是端口号。

        端口号:是一个整数,用来区分一个主机上进行网络通信的程序。一个主机上的一个端口号只能被一个进程绑定,如果一个端口被进程A绑定了,如果进程B绑定这个端口,就会失败,除非进程A释放了这个端口,进程B才能绑定上。

        反过来,一个进程可以绑定多个端口,很常见。端口号和 socket 对象是一一对应的,如果一个进程中有多个socket对象,那这个进程就能绑定多个端口了。

        如图的异常:

        该异常是网络很常见的异常,通常表示 socket 创建失败,比如说端口号被别的进程占用了,就会抛出该异常。

        如图的循环:

        

        对于服务器来说,需要不停的收到请求,返回响应。一个服务器在单位时间能处理的请求越多,那这个服务器就越厉害。

        服务器往往是 7 * 24 小时运行的,要持续不断的运行下去,所以,这里的 while(true) 没有退出的必要,如果要退出,简单粗暴的办法就是 “杀进程”,在 linux 中有个结束进程的命令:kill,带有 -9 是强制退出,不带的话就是告诉进程,你该挂了,而进程可以选择继续工作,也可以选择自挂东南枝。所以才有了 杀进程 这一说法。

        对于内部的逻辑要写的就是如下图:

        receive方法里的参数如图:

 

        里面放的参数是 DatagramPacket 对象,而此处它是 “输出型参数”,前面的文件IO涉及到了,像InputStream里的read方法,里面参数也是输出型参数(字节数组)

        实际上,DatagramPacket 内部就会包含一个字节数组

        如图的 DatagramPacket:

        通过这个字节数组来保存收到的消息正文 (应用层数据包) ,也就是 UDP 数据报的载荷部分。后面的数字大小,就是这个 载荷 有多长,可以灵活设置。

        如图:

        这里的 receive 就能从网卡读到一个 UDP 数据报,这个数据报是被放到 requestPacket 对象中的,其中的 载荷部分 就放到 requestPacket 内置的 字节数组 中了,另外,报头部分也被 request 的其他属性保存了。

        除了 UDP 报头之外,还有其他信息,比如 收到的数据源IP,可以通过 requestPacket 知道源IP 和 源端口

        如果服务器没有收到请求,服务器就会在这里阻塞,等到有客户端的请求过来了,再receive,然后执行下面的逻辑。

        如图,拿到数据报后的解析:

        这里是基于字节数组构造出的String的,字节数组里的内容不一定是二进制数据,也可能是文本数据,把文本数据交给 String 来保存,也是恰到好处,就算是二进制数据,Java的String 也是可以保存的。

        上面的 0 是指的是从 0 号位置开始构造String

        getLength 是获取字节数组中有效数据的长度,用这么长来构造String,这里的有效长度,不一定是4096,4096是最大长度,一定要使用有效长度来构造,如果使用4096,就会生成一个非常长的 String ,后半部分都是空白。

        客户端发来的请求数据,说是存在socket文件中,但要注意,此处的数据是在 socket 的内存缓冲器的,并不是在硬盘上,如图:

        如图:

        这个代码,要根据请求构造 / 计算响应通过 process 方法来完成这个工作

        由于此处是回显服务器,所以就单纯的 return 了,如果是一些具有特点业务的服务器,process 就可以写其他任何你想要的逻辑了。

        当计算完响应,可以 send 时,如图:

        这里的 send 也需要放 DatagramPacket 的对象,作为参数。

        DatagramPacket 的对象参数第一个:不是空白字节数组,直接把 String 包含的字节数组给拎过来了;

        第二个参数:单位是字节,得到字节的长度;这里不能写成:response.length(),这指的是字符个数 ,单位是字符;字符和字节个数肯定是不一样的,除非是标准ASCII表的字符

        第三个参数:requestPacket是客户端发生过来的数据报,得到INetAddress 对象,这个对象包含了 IP 和 端口号,也就是和服务器对端(对应的客户端)的 IP 和 端口。这里是把请求中的源 IP 和 源端口,作为响应的 目的IP 和 目的端口,此时就可以做到把响应返回给客户端了。

        从上面代码中可以看到:

        UDP 是无连接的通信:UDP socket 自身不保存对端的 IP 和 端口,而是在每个数据报中都有一个,另外代码中也没有 “建立连接” 、“接受连接” 的操作。

        不可靠传输:上面代码中体现不到。

        面向数据报:send 和 receive 都是以 DatagramPacket 为单位的。

        全双工:一个 socket 既可以发送,也可以接收。

        打印日志那里:打印客户端 IP 是以点分十进制 的形式打印的,如图:

        后面陆续是打印客户端端口号,打印请求字符串,打印响应字符串

        我们设置服务器端口时的注意事项,如图:

        上面的端口号你可以指定任何你想要的端口,但也是有范围的,通常来说,使用的端口号x范围是:1024 < x < 65535。

        但是也有前提,确保你当前这个端口在当前机器上没有被其他端口占用,如果指定了端口,创建服务器失败的话,换个端口就好了

          2、客户端代码

public class UdpEchoClient {private DatagramSocket socket = null;String serverIp;int serverPort;//此处使用的IP是点分十进制的风格,例如:192.168.0.0public UdpEchoClient(String serverIp, int serverPort) throws SocketException {socket = new DatagramSocket();this.serverIp = serverIp;this.serverPort = serverPort;}public void start() throws IOException {System.out.println("客户端启动");Scanner scanner = new Scanner(System.in);while (true) {System.out.print("->");//要做四个事情//1、从控制台读取要发送的请求数据if(!scanner.hasNext()) {break;}String request = scanner.next();//2、构造请求并发送DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIp), serverPort);socket.send(requestPacket);//3、读取服务器的响应DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);//4、把响应显示到控制台上String response = new String(responsePacket.getData(), 0, responsePacket.getLength());System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);udpEchoClient.start();}
}

代码解析

        首先要创建 socket 对象,如图:

        此处是不需要手动指定端口号,让系统随机分配一个端口号即可,而构造方法中传的是服务器IP 和 服务器端口,所以要保存一下,把它们设成客户端的属性。而源 IP 就是客户端的 本机IP,源端口就是 系统分配的空闲端口。

        对于客户端来说,系统随机分配的端口,是空闲的,但如果手动指定端口,是可用,但有可能被别的进程占用。

        同理,服务器端口不一定就100%不会被别的进程占用,但服务器这个机器,是在程序员手里的,程序员可以事先知道服务器上都有哪些端口被使用,就比较有谱;普通用户千千万,其中电脑上的环境也会千差万别,天知道用户按照了什么鬼程序,把你的服务器端口给占用了,你的程序也会因为端口的绑定失败而无法启用,用户就会怪在你头上,说你代码写的不行。

        输入请求,如图:

        注意:从控制台读取,使用 scanner 读取字符串,最好是使用 next,而不是nextLine.

        如果使用 nextLine 读取,就需要手动输入换行符:enter键 来控制。由于 enter键 不仅仅会产生 \n 字符,还好产生其他字符,就会导致读取到的内容容易出问题。

        使用 next 其实是以 “空白符” 作为分隔符,分隔符包括但不限于 换行,回车,空格,制表符,垂直制表符...

        如果是从文件中读取的,那就无所谓了,因为文件中有各种字符。

        构造请求并发送,如图:

        此处创建的 DatagramPacket 对象,里面参数放的先后分别是:请求字符串的字节数组,字节数组的长度,服务器的IP地址,服务器端口。

        读取服务器的响应,如图:

        是用新的字节数组存放响应的载荷

        把响应打印到控制台上,如图:

        先把响应构造成字符串,再打印。这里 responsePacket.getData() 是得到响应的字节数组。

3、创建DatagramPacket三种不同的参数场景

        如图:

        第一种:服务器接收客户端发来的请求,所以要存放在空白数组中。

        第二种:服务器构造好响应,返回给客户端的。

        第三种:客户端构造好请求,发送给服务器的。

4、执行代码

        启动服务器和启动客户端,如图:

        客户端分别输入 hello 和 world,如图:

        服务器这边的显示:

        我们结束客户端程序,在执行客户端程序,不动服务器,输入你好,如图:

        客户端:

        服务器:

        可以看到,客户端的端口号变了,因为终止了上一次的客户端程序,又重新执行客户端程序,系统会随机分配一个空闲端口给客户端使用,所以是不一样的。

5、客户端和服务器交互的过程

        过程如图:

下面是画图解释其中的过程:


都看到这了,点个赞再走吧,谢谢谢谢谢

这篇关于网络编程套接字(3)——Java数据报套接字(UDP协议)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/793641

相关文章

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

基于Java实现回调监听工具类

《基于Java实现回调监听工具类》这篇文章主要为大家详细介绍了如何基于Java实现一个回调监听工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录监听接口类 Listenable实际用法打印结果首先,会用到 函数式接口 Consumer, 通过这个可以解耦回调方法,下面先写一个

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Java字符串处理全解析(String、StringBuilder与StringBuffer)

《Java字符串处理全解析(String、StringBuilder与StringBuffer)》:本文主要介绍Java字符串处理全解析(String、StringBuilder与StringBu... 目录Java字符串处理全解析:String、StringBuilder与StringBuffer一、St

springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法

《springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法》:本文主要介绍springboot整合阿里云百炼DeepSeek实现sse流式打印,本文给大家介绍的非常详细,对大... 目录1.开通阿里云百炼,获取到key2.新建SpringBoot项目3.工具类4.启动类5.测试类6.测

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt