javaUDP数据报套接字编程

news2024/11/15 8:40:33

0.前言

对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次发送全部数
据报,一次接收全部的数据报。
java中使用UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用
DatagramPacket 作为发送或接收的UDP数据报。对于一次发送及接收UDP数据报的流程如下:
在这里插入图片描述以上只是一次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就是只有请
求,没有响应。对于一个服务端来说,重要的是提供多个客户端的请求处理及响应,流程如下:
在这里插入图片描述

1. DatagramSocket API

DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。

  1. 使用这个类,表示一个socket对象,在操作系统中,就把这个socket对象也当做一个文件来处理,相当于是文件描述符表生的一项。
  2. 普通的文件,对应的硬件设备是硬盘,socket文件对应的设备是网卡。
  3. 一个socket对象只能标记一台主机,所以要和多个主机通信,则需要创建多个socket对象。

DatagramSocket 构造方法:
在这里插入图片描述所以本质上来说,不是进程与端口建立了联系,而是进程中的socket对象与端口建立了联系。

DatagramSocket 方法:
在这里插入图片描述

  1. void receive(DatagramPacket P)此处传入的相当于是一个空对象,recieve方法内部,会对参数的这个空对象进行内容填充。从而构造出一个结果数据,就类似与c里面加了&的形参,所以这里的参数也是一个“输出型参数”。
  2. DatagramPacket P是一个报文对象
    本质上套接字也是文件,所以用完也需要关闭

2. DatagramPacket API

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

DatagramPacket 构造方法
在这里插入图片描述
`DatagramPacket 方法:

在这里插入图片描述构造UDP发送的数据报时,需要传入 SocketAddress,该对象可以使用 InetSocketAddress 来创建。

3. InetSocketAddress API

InetSocketAddress ( SocketAddress 的子类 )构造方法:
在这里插入图片描述

4.回显服务器

编写一个最简单的UDP版本的客户端服务器程序
一个最简单的UDP版本的客户端服务器程序,称之为回显服务器。

4.1服务器端代码

package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;


public class UDPEchoClient {
    //网络编程,本质上是要操作网卡
    //但是网卡不方便直接操作,在操作系统内核中,使用一种特殊的叫做“socket"这样的文件来抽象表示网卡
    //因此,进行网络通信,必须要有socket对象

    private DatagramSocket socket = null;

    //对于服务器而言,创建socket对象的同时,需要绑定一个具体的端口号
    //服务器一定需要关联上一个具体的端口号,服务器是网络传输中被动的一方,如果是操作系统随机分配的端口号,那么客户端就不知道这个端口是啥,从而无法通信

    public UDPEchoClient(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");

        while(true){

            //只要有客户端过来就会提供服务,所以用来while循环
       // 1.读取客户端发来的请求
            DatagramPacket  requestPacket = new DatagramPacket(new byte[4096], 4096);
            //receive方法的参数是一个输出型参数,需要先构造一个空包的DatagramPacket对象交给receive去填充。
            socket.receive(requestPacket);
            //此时这个DatagramPacket 是一个特殊的对象,并不方便直接进行处理,可以把这里包含的数据拿出来,构造成一个字符串
            //但是要注意这里getData返回的是一个byte数组,所以要调用String的带byte[]参数的构造方法
            //此外虽然这个数组设置4096个元素,但是实际传输中,4096个元素长度的字节数组不一定会全部占满,所以采用getlength()方法保证实际的有效部分构造成字符串即可
            String request = new String( requestPacket.getData(),0,requestPacket.getLength());

            //2.根据请求计算响应,由于此处是回显服务器,响应和请求相同
            String response = process(request);

            //3.把响应返回客户端,
            // send的参数也是DatagramPacket ,需要把这个packet对象构造好
            //此处构造的响应对象,不能用空的数组构造了,而是要用响应的结果来构造
            //参数1需要的是一个byte[]数组,那么就调用String里面的getBytes方法,将字符串以字节数组的形式取出
            //参数2需要是构建长度,这里要注意不能写response.length,因为这个求得是字符串长度,我们要的是字节长度,二者可能相同,可能不同,
            // 如果存的是ASCII码里面的字符当然没有用影响,但是如果存的是汉字等差距就很大了,一个汉字是一个字符但是是三个字节(UTF-8)
            //参数3还需要一个地址,一个响应结果的接收地址(也就是ip和端口号),这两个信息本来就在requestPacket中,所以直接调用requestPacket的getAdress方法即可。
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress());
            socket.send(responsePacket);

        }
    }

    //响应请求的具体操作,因为本例子中是回显服务器,所以直接返回就行了
    public String process(String request){
        return request;
    }
}

说明一下

  1. 首先DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);这一句里面,为什么明明已经初始化了byte[4096]还有额外增加一个变量表示长度4096.其实是因为请求数据报里面不只有数据,还有其他的比如首部尾部等,我们这里为了方便,就将整个数据报报都作为数据部分,所以后面的长度是4.96,实际应该是小于4.96的
  2. while循环,每循环一次,就处理一次请求-响应,客户端啥时候发请求,服务器端是不知道的,有多少客户端发请求,服务器端也是不知道的。但是socket.recieve()本身就具有阻塞特性,在请求多的时候,会积极响应,但是没有请求的时候,那么recieve()本身也会阻塞起来,不浪费系统资源。这个就和Scanner也是这样的。
  3. 打印请求数据报的地址记得调佣toString方法
  4. 对于服务器进程而言,whiel循环啥时候结束呢?答案是服务器程序就是死循环的,因为服务器要随时等待接受请求,都是7*24小时运行的。

4.2客户端代码

package network;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UDPEchoClient {
    private DatagramSocket socket = null;
    String serverIp = null;
    int serverPort = 0;
    //注意:这里与服务器端的区别在于客户端在构建socket的时候不需要手动指定端口号,交给操作系统随机分配;(一个socket对象对应唯一一个端口号)
    //因为在本例子中,我们客户端和服务器端都在本机上,也是就是ip地址就是本地环回地址127.0.0.1
    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.println("请输入信息:");
            String request = scanner.next();
            //简单判断一下
            if(request == "exit"){
                System.out.println("请求结束!");
                return;
            }
            //构造requestpack对象
            //参数1是一个byte类型的数组所以request调用Stringe类里面的getBytes方法,将字符串转变为字节数组
            //要构建字节数组的长度
            //InetAddress.getByName(serverIp)依据名字获取ip地址,ip地址实际上就是一个32位的二进制序列
            DatagramPacket requestPack = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPack);
            //读取服务器的UDP响应
            DatagramPacket responsePackte = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPack);
            String response = new String(responsePackte.getData(), 0, responsePackte.getLength());
            //打印响应
            System.out.println(response);

        }
    }
    public static void main(String[] args) throws SocketException {
        UDPEchoClient client = new UDPEchoClient("127.0.0.1", 9090);
        client.start();
    }
}
  1. 一般来说客户端指定端口号可不可以呢?理论上也是可以的·但是不推荐,这是因为手动指定的端口号可能存在已经被用了造成冲突的情况。
  2. 那么为什么服务端指定就不用担心冲突呢?这是因为服务器端大部分的内容都是程序员自己设定的,服务器上的程序都是可控的。但是客户端程序就非常繁杂了,千奇百怪。

4.3回显服务器的执行过程

在这里插入图片描述

说明一下:

  1. DatagramSocket中的receive究竟是如何阻塞呢?
    实际上这个的阻塞不是由java实现的,而是由操作系统内核实现,系统对于IO操作本身就有这样的阻塞机制。这个是看java原码是看不来的

执行结果
服务器端执行结果
在这里插入图片描述
客户端执行结果
在这里插入图片描述

注意:

  1. 一定要先启动服务器,再启动客户端。
  2. 我们如果在输入一次信息,实际是一个客户端发送了请求两次,并不是两个客户端,最直接的证据就是服务器端返回的信息ip地址和端口号是同一个。
    在这里插入图片描述

在这里插入图片描述

  1. 那么我们如何构造多个客户端同时对服务器端进行访问呢?可以使用构建多个线程的方法,一个线程代表一个客户端,这里为了操作方便我们可以直接构建多个进程,每个进程对应一个客户端来实现即可,具体方法如下:
    在UDPEchoClient进程下点击编辑配置 在这里插入图片描述点击允许多个实例,即可创建多个实例,也就是每点击一次允许,就会创建一个新的进程作为新的客户端
    在这里插入图片描述实际效果
    第一个客户端
    在这里插入图片描述

第二个客户端
在这里插入图片描述

服务器端
在这里插入图片描述

回显服务器只是用来演示的,并不具有实际的业务能力,那么我们在上述代码的基础上进行调整,增加一定的业务功能,实现一个“查字典”的服务器,具体功能是查一个英文单词的中文意思

5.“查字典服务器”

代码实例

package network;

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;


//对于DicSever而言,与EchoSever相比,大部分东西是一样的,所以我们直接继承EchoSever

public class UPDDicServer extends UDPEchoServer {

    //创建一个字典
    private Map<String,String> dict = new HashMap<>();

    //构造函数
    public UPDDicServer(int port) throws SocketException {
        super(port);//调用父类构造函数
        //初始化字典
        dict.put("cat","猫");
        dict.put("dog","狗");
        dict.put("hello","你好");
        dict.put("bye","再见");
    }

    //主要是“根据请求计算响应这个步骤是不一样的,所以我们直接重写
    @Override
    public String process(String request) {
        //查字典的过程
        return dict.getOrDefault(request, "当前单词没有查到结果");
    }

    public static void main(String[] args) throws IOException {
        UDPEchoServer server = new UPDDicServer(9090);
        server.start();

    }
}

结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

不同的服务器,其业务是不同的,但是基本逻辑是一样的,一些更复杂的服务器,process里面可能要运行几万行和几十万行。

5.1Address already in use: Cannot bind异常

一个端口号是只能被一个进程所使用的,那么如果端口冲突了会是什么样的情况呢?
我们先启动UDPEchosever,然后再启动UDPDicsever,那么就出抛出这样的异常
在这里插入图片描述

Address already in use: Cannot bind

这个异常需要大家记住,这个异常无论是后续学习中,还是实际工作照片中都会反复出现。

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

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

相关文章

探索人工智能的奇妙世界:解密AI技术的未来发展

作为一名热爱技术的开发者&#xff0c;当谈到人工智能&#xff08;AI&#xff09;和焦虑商业化时&#xff0c;我总会面临一个困境&#xff1a;到底是愁眉苦脸&#xff0c;还是开怀大笑&#xff1f;让我带你走进这个有趣又争议的话题。 首先我们需要面对AI的自学能力。这些智能…

Django4.0+使用rest_framework_jwt的问题

问题描述 python版本&#xff1a;3.10 Django版本&#xff1a;4.1 djangorestframework-jwt版本&#xff1a;1.11.0 在写jwt认证功能时&#xff0c;发现run的时候会报以下错误 from django.utils.translation import ugettext as _ ImportError: cannot import name ugettext…

day69_Vue进阶

今日内容 零、 复习昨日 零、 复习昨日 nginx 静态服务器(动静分离)反向代理服务器(代理后端服务器)负载均衡异步 前端工程化 —> java代码工程 一、使用Vue-Cli搭建Vue项目 1.1 什么是vue-cli cli: Command Line 命令行工具&#xff0c;vue-cli就是vue的命令行工具&#xf…

ThreadPoolExecutor 线程池源码学习

ThreadPoolExecutor 线程池源码学习 1.阅读源码 1.ThreadPoolExecutor.execute public void execute(Runnable command) {if (command null)throw new NullPointerException();// ctl 高三位记录线程状态。低29位记录线程池中线程数int c ctl.get();//位运算获取工作线程数 …

wireshark抓包实践

目录 ifconfig ( network interfaces configuring )tcpdump 命令tcpdump&wireshark例子 ifconfig ( network interfaces configuring ) eth0表示网卡UP代表网卡开启状态RUNNING代表网卡的网线被接上mtu1500: MTU&#xff08;最大传输单元&#xff09;是指在网络中传输数据时…

【javaEE面试题(五)在JMM(Java Memory Model (Java 内存模型))下谈volatile的作用】

volatile的作用 JMM下volatile作用 volatile 能保证内存可见性 volatile 修饰的变量, 能够保证 “内存可见性”. 代码在写入 volatile 修饰的变量的时候 改变线程工作内存中volatile变量副本的值将改变后的副本的值从工作内存刷新到主内存 代码在读取 volatile 修饰的变量的时…

B067-基础环境-抽取Basegit

目录 抽取base抽取domain和querymapper接口抽取service抽取 Git优点&#xff1a;Git安装及操作Git Bash命令行操作图形化客户端TortoiseGit操作Git集成Idea操作idea会把workspace作为本地仓库gitee操作idea解决代码冲突 抽取base 抽取domain和query domain&#xff1a;所有实体…

Nodejs 依赖包的存放路径设置(按其他博客修改路径后,安装路径仍在C盘的解决办法)

Nodejs 依赖包的存放路径设置 使用命令npm root -g 查看依赖包的安装位置 默认依赖包的安装位置是在C盘。为了防止C盘存太多东西&#xff0c;我这里已经将安装位置改到了D盘&#xff0c;下面就记录下修改的步骤。 1. 创建新的依赖包安装目录 在 nodejs 的安装目录下创建两个新…

8年资深测试总结,性能测试+性能优化(详细)进军高级测试...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 性能优化常见概念…

LabVIEW FPGA利用响应式数字电子板快速开发空间应用程序

LabVIEW FPGA利用响应式数字电子板快速开发空间应用程序 与传统的基于文本的语言相比&#xff0c;LabVIEW的编程和设计已被证明可以缩短开发时间。各种研究表明&#xff0c;生产率的提高在3到10倍之间。LabVIEW通过图形语言、集成开发环境和多个编译器的组合来实现这一点。 图…

qt对话框

完善文本编辑器 #include "second.h" #include "ui_second.h"second::second(QWidget *parent) :QWidget(parent),ui(new Ui::second) {ui->setupUi(this);this->setWindowTitle("聊天室界面");//设置标题this->setWindowIcon(QIcon(&…

边缘检测

目录 1、边缘检测原理 2、Sobel算子边缘检测 3、Scharr算子边缘检测​编辑 4、算子生成函数 5、Scharr、Sobel的使用 6、Laplacian算子边缘检测 7、Canny算子边缘检测 8、Laplacian、Canny的使用 1、边缘检测原理 2、Sobel算子边缘检测 3、Scharr算子边缘检测 4、算子生成函…

MySQL数据库 - 表的操作

目录 一、创建表 二、创建表案例 1、显示当前使用的数据库名 2、创建表 2.1 MyISAM存储引擎表 2.2 InnoDB存储引擎表 三、查看表结构 四、修改表 1、新增列 2、修改列类型 3、修改列名 4、修改表名 5、删除列 五、删除表 表的操作至少会涉及如下两类SQL语句&…

adb日常使用命令

重启电脑adb服务 adb start-server和adb kill-server mac中uiautoviewer的位置 android-sdk→tools→bin→uiautomatorviewer.bat adb查看本机abi类型 adb shell getprop ro.product.cpu.abi github 比较好的adb教程&#xff1a; https://github.com/mzlogin/awesome-adb a…

[VUE学习]权限管理系统前端vue实现9-动态路由,动态标签页,动态面包屑

1.动态路由 1.因为我们左侧权限菜单是根据不同用户显示不一样的 所以我们需要使用动态路由 来动态生成右侧路由信息 在总体布局页面添加router <router-view> 是 Vue Router 提供的组件&#xff0c;用于动态展示匹配到的路由组件内容。通过在合适的位置放置 <router-v…

将word中超链接的字体颜色更换成白色

文章目录 1、问题描述2、解决方法&#xff08;两种&#xff09;2.1 临时修改2.2 永久修改 1、问题描述 超链接是蓝色&#xff0c;需要将其换成正常颜色的字体 2、解决方法&#xff08;两种&#xff09; 2.1 临时修改 直接选中该字体&#xff0c;从字体的颜色那里选主题颜色…

zabbix安装监控客户端应用

添加 zabbix 客户端主机 服务端和客户端都配置时间同步 服务端和客户端都设置 hosts 解析 设置 zabbix 的下载源&#xff0c;安装 zabbix-agent2 在服务端验证 zabbix-agent2 的连通性 ​编辑 在 Web 页面中添加 agent 主机 自定义监控内容 在客户端创建自定义 key 1.明确…

XSS学习

目录 什么是XSS 概念 理解 XSS分类 存储型XSS 反射型XSS 原理 攻击过程 DOM型 攻击过程 DOM行XSS与反射型XSS区别 存储型XSS与反射型XSS区别 DVWA实验 反射型XSS low等级 JavaScript弹窗函数 攻击思路 攻击者web设计 medium等级 high等级 impissible等级 …

【ES6】中构造函数的语法糖 —— Class(类)

在现代前端开发中&#xff0c;JavaScript的面向对象编程成为了主流。ES6引入了class关键字&#xff0c;使得开发者可以更方便地使用面向对象的方式编写代码&#xff0c;更接近传统语言的写法。ES6的class可以看作是一个语法糖&#xff0c;它的绝大部分功能ES5都可以做到&#x…

Java基础---动态代理

目录 典型回答 静态代理和动态代理的区别 动态代理的用途 Spring AOP的实现方式 JDK 动态代理的代码段 Cglib动态代理的代码段 典型回答 动态代理就是&#xff0c;在程序运行期&#xff0c;创建目标对象的代理对象&#xff0c;并对目标对象中的方法进行功能性增强的一种技…