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

news2025/2/27 15:04:34

目录

一、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.0
    public 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、客户端和服务器交互的过程

        过程如图:

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


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

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

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

相关文章

探索React中的类组件和函数组件

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Netty Review - 探究Netty服务端主程序无异常退出的背后机制

文章目录 概述故障场景尝试改进问题分析铺垫&#xff1a; Daemon线程Netty服务端启动源码分析逻辑分析 如何避免Netty服务端意外退出最佳实践 概述 在使用Netty进行服务端程序开发时&#xff0c;初学者可能会遇到各种问题&#xff0c;其中之一就是服务端意外退出的问题。这种问…

真实案例分享:MOS管电源开关电路,遇到上电冲击电流超标

做硬件&#xff0c;堆经验。 分享一个案例&#xff1a;MOS管电源开关电路&#xff0c;遇到上电冲击电流超标&#xff0c;怎么解决的呢&#xff1f; 下面是正文部分。 —— 正文 —— 最近有一颗用了挺久的MOSFET发了停产通知&#xff0c;供应链部门找到我们研发部门&#xff0c…

KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记10 - STM32的SDIO学习2

KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记10 - STM32的SDIO学习2 一、问题回顾二、本次的任务三、 需要注意的问题3.1 Card Identification Mode时的时钟频率3.2 CMD0指令的疑似问题3.3 发送带参数的ACMD41时要注意时间时序和时效3.4 CPSM的指令发送问题3.5 调试过程中的SD卡的…

伪分布Hadoop的安装与部署

1.实训目标 &#xff08;1&#xff09;熟悉掌握使用在Linux下安装JDK。 &#xff08;2&#xff09;熟悉掌握使用在Linux下安装Hadoop。 &#xff08;3&#xff09;熟悉掌握使用配置SSH免密登录。 2.实训环境与软件 环境 版本 说明 Windows 10系统 64位 操作电脑配置 …

【ENVI精讲】处理专题五:基于像元二分模型的植被覆盖度反演

一、专题概述 植被覆盖度是指植被&#xff08;包括叶、茎、枝&#xff09;在地面的垂直投影面积占统计区总面积的百分比。植被覆盖度常用于植被变化、生态环境研究、水土保持、气候等方面。植被覆盖度数据来源于地理遥感生态网平台。 二、像元二分法模型 像元二分模型是一种…

什么是自动化测试?什么情况下使用?

什么是自动化测试? 自动化测试是指把以人为驱动的测试行为转化为机器执行的过程。实际上自动化测试往往通过一些测试工具或框架&#xff0c;编写自动化测试脚本&#xff0c;来模拟手工测试过程。比如说&#xff0c;在项目迭代过程中&#xff0c;持续的回归测试是一项非常枯燥…

蓝桥集训之序列

蓝桥集训之序列 核心思想&#xff1a;多路归并 每次将两个序列合并 –> 两序列n2个和中最小的n个 构成新序列 第一行都是加b1 每次在最外面的元素中取最小(优先队列) #include<iostream>#include<algorithm>#include<cstring>#include<queue>#incl…

ChatGPT 控制机器人的基本框架

过去的一年&#xff0c;OpenAI的chatGPT将自然语言的大型语言模型&#xff08;LLM&#xff09;推向了公众的视野&#xff0c;人工智能AI如一夜春风吹遍了巴黎&#xff0c;全世界都为AI而疯狂。 OpenAI ChatGPT是一个使用人类反馈进行微调的预训练生成文本模型。不像以前的模型主…

LoadBalancer (本地负载均衡)

1.loadbalancer本地负载均衡客户端 VS Nginx服务端负载均衡区别 Nginx是服务器负载均衡&#xff0c;客户端所有请求都会交给nginx&#xff0c;然后由nginx实现转发请求&#xff0c;即负载均衡是由服务端实现的。 loadbalancer本地负载均衡&#xff0c;在调用微服务接口时候&a…

云计算项目十一:构建完整的日志分析平台

检查k8s集群环境&#xff0c;master主机操作&#xff0c;确定是ready 启动harbor [rootharbor ~]# cd /usr/local/harbor [rootharbor harbor]# /usr/local/bin/docker-compose up -d 检查head插件是否启动&#xff0c;如果没有&#xff0c;需要启动 [rootes-0001 ~]# system…

VARMA(Vector Auto Regressive Moving Average) in Time Series Modelling

what is VARMA? ARIMA是针对单一变量进行建模的方法,当我们需要进行多变量时序建模时,需要使用VAR and VMA and VARMA模型。 VAR:Vector Auto-Regressive,a generalization of the auto-regressive model for multivariate time series where the time series is station…

【重新定义matlab强大系列十七】Matlab深入浅出长短期记忆神经网络LSTM

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

音视频按照时长分类小工具

应某用户的需求&#xff0c;编写了这款根据音视频时长分类小工具。 实际效果如下&#xff1a; 显示的是时分秒&#xff1a; 核心代码&#xff1a; MediaInfo MI; if (MI.Open(strPathInput.c_str()) 0){return -1;}_tstring stDuration MI.Get(stream_t::Stream_Audio,0,_T…

【Flink】Flink 的八种分区策略(源码解读)

Flink 的八种分区策略&#xff08;源码解读&#xff09; 1.继承关系图1.1 接口&#xff1a;ChannelSelector1.2 抽象类&#xff1a;StreamPartitioner1.3 继承关系图 2.分区策略2.1 GlobalPartitioner2.2 ShufflePartitioner2.3 BroadcastPartitioner2.4 RebalancePartitioner2…

手机APP测试——如何进行安装、卸载、运行?

手机APP测试——主要针对的是安卓( Android )和苹果IOS两大主流操作系统,主要考虑的就是功能性、兼容性、稳定性、易用性、性能等测试&#xff0c;今天先来讲讲如何进行安装、卸载、运行的内容。 一、App安装 1、点击运行APP安装包,检测安装包是否正常; . 2、进入[安装向导]…

Java17 --- SpringCloud之OpenFeign

目录 一、OpenFeign实现服务调用 1.1、创建openfeign微服务 二、Openfeign超时控制 2.1、全局默认配置 2.2、单个微服务配置 三、重试机制 四、替换openfeign默认的HttpClient 五、请求响应压缩 六、日志打印 一、OpenFeign实现服务调用 1.1、创建openfeign微服…

LLM长上下文外推方法

现在的LLM都集中在卷上下文长度了&#xff0c;最新的Claude3已经支持200K的上下文&#xff0c;见&#xff1a;cost-context。下面是一些提升LLM长度外推能力的方法总结&#xff1a; 数据工程 符尧大佬的最新工作&#xff1a;Data Engineering for Scaling Language Models to …

[虚拟机保护逆向] [HGAME 2023 week4]vm

[虚拟机保护逆向] [HGAME 2023 week4]vm 虚拟机逆向的注意点&#xff1a;具体每个函数的功能&#xff0c;和其对应的硬件编码的*长度* 和 *含义*&#xff0c;都分析出来后就可以编写脚本将题目的opcode转化位vm实际执行的指令 &#xff1a;分析完成函数功能后就可以编写脚本输出…

c++ primer plus 笔记 第十六章 string类和标准模板库

string类 string自动调整大小的功能&#xff1a; string字符串是怎么占用内存空间的&#xff1f; 前景&#xff1a; 如果只给string字符串分配string字符串大小的空间&#xff0c;当一个string字符串附加到另一个string字符串上&#xff0c;这个string字符串是以占用…