JavaEE第19节 用UDP套接字实现简单回显服务器

2024-08-31 10:44

本文主要是介绍JavaEE第19节 用UDP套接字实现简单回显服务器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 一、API介绍
    • `DatagramSocket` 用于发送和接收数据报。
    • `DatagramPacket` 是UDP对数据的封装的基本单位
  • 二、创建简单的回显服务器
    •  服务器端
    •  客户端
  • 三、程序优化

一、API介绍

UDP协议面向数据报进行传输,所以在代码中基本是以数据报(DatagramPacket)作为操作对象,进行输入和输出的。
JAVA提供了两个常用的类去操作UDP套接字:

  1. DatagramSocket 用于发送和接收数据报。

  • 构造方法:
方法签名方法说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端
DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)
  • 类方法:
方法签名方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()关闭此数据报套接字
  1. DatagramPacket 是UDP对数据的封装的基本单位

  • 构造方法:
方法签名方法说明
DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf),从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号
  • 类方法:
方法签名方法说明
getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据,以字节数组的形式
int getLength()返回字节数组的长度
getSocketAddress()获取原数据报持有放的IP和端口

注意:
1、只要是获取IP地址或者端口号的方法,数据原本产生自哪里,返回的IP或端口号就来自哪里。
2、只要是套接字,就会消耗文件资源描述符,而文件资源描述符是有限的,如果出现大量请求,在不关闭已经运行完了的程序的文件资源描述符,就可能出现文件资源泄露。所以在使用套接字的时候,程序执行完,一定要记得关闭文件资源!

二、创建简单的回显服务器

 服务器端

public class Server {DatagramSocket serverSocket=null;public Server(int port) throws SocketException {//这一步JVM使用操作系统提供的关于socket的API完成端口和进程之间的绑定serverSocket=new DatagramSocket(port);}public void start() throws IOException {System.out.printf("服务器启动!\n");try {while(true){//1、获取从客户端发来的请求,注意第二个参数不能大于第一个参数!DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096 );//如果没有获取到客户端发来的请求,会进入阻塞状态,知道获取到数据serverSocket.receive(requestPacket);//2、数据报转化成字符串String request=new String(requestPacket.getData(),0,requestPacket.getLength());//3、根据请求request,生成响应,然后打包成数据报//由于是回显服务器,所以直接返回相同的字符串,在实际情况中,这一步可能会很复杂String response=process(request);//把响应封装成数据报DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());//由于UDP是无连接的通信方式,所以要指定好请求客户端的IP和端口号//4、把封装好的数据报发送给客户端serverSocket.send(responsePacket);//5、打印工作日志[IP地址:端口号]System.out.printf("[%s:%d] 请求=%s 响应=%s\n",requestPacket.getAddress(),requestPacket.getPort(),request,response);}} finally {//记得只要是套接字,一定要分析是否要关闭文件资源描述符,避免文件资源泄露。if(serverSocket!=null&&!serverSocket.isClosed()){close();}}}private void close(){serverSocket.close();System.out.println("服务器套接字已关闭。");}private String process(String request){return request;}public static void main(String[] args) throws IOException {//程序员自己指定一个端口Server server=new Server(9090);server.start();}
}

 客户端

public class Client {//用于发送和接收数据private DatagramSocket clientSocket=null;//服务器的IP地址private String serverIP=null;//服务器的端口号private int serverPort=0;public Client(String ip,int port) throws SocketException {this.serverIP=ip;this.serverPort=port;//不设置参数,让本地计算机自己分配一个端口号,如果让用户设置端口号可能出现端口占用冲突clientSocket=new DatagramSocket();}public void start() throws IOException {System.out.printf("客户端启动!");while (true){//1、通过控制台,获取用户请求Scanner scanner=new Scanner(System.in);System.out.printf("-> ");//单纯做个标记,好看if(!scanner.hasNext()){System.out.println("客户端停止输入。");//养成良好的编程习惯,用完就关闭clientSocket.close();break;}String request=scanner.next();//2、把请求打包成数据报,然后发送给服务器处理,容量大小自己设置,只要数据能够存放的下就行DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length,//注意,因为UDP是无连接通信,所以要程序员自己添加服务器的IP和端口号,这样才能成功连接InetAddress.getByName(this.serverIP),this.serverPort);//发送请求clientSocket.send(requestPacket);//3、接收服务器发来的响应DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);clientSocket.receive(responsePacket);//等待阻塞直到服务器响应//4、把获取的数据报转化成字符串,打印到控制台String response=new String(responsePacket.getData(),0,responsePacket.getLength());System.out.printf("响应:"+response);System.out.println();}}public static void main(String[] args) throws IOException {//这里填写的是服务器的IP和端口号Client client=new Client("127.0.0.1",9090);client.start();}}

执行结果:

服务器:

服务器启动!
[/127.0.0.1:64316] 请求=11 响应=11
[/127.0.0.1:64316] 请求=22 响应=22
[/127.0.0.1:64316] 请求=33 响应=33

客户端:

客户端启动!-> 11
响应:11
-> 22
响应:22
-> 33
响应:33
->

三、程序优化

在上面这个回显服务器中,虽然成功运行了,但是只有一个客户端,并且服务器——客户端都是在本地主机上运行的。

在实际场景中,可能会有多个客户端同时发送请求给到服务器,那么效率就会变低,因为上述实现的服务器是串行执行的,这时我们该如何提高效率呢?

1. 引入多线程
多线程可以极大的避免同一时刻,多个客户端发来请求的情况,因为多个线程之间并发执行的,共同消费这些请求,从而缓解服务器压力。

2. 使用线程池
我们实现的这个服务器属于长连接1
倘若请求量较多,并且每个请求处理速度比较快,还可以进一步的使用线程池来缓解服务器压力,避免频繁的创建、销毁线程。

3. IO多路复用
IO多路复用是为了解决线程由于需要等待客户端IO操作而造成的CPU资源浪费,因为对应线程在等待过程中无法处理其他的任务。它的核心作用就是不让线程停下来,让它去完成别的任务,充分利用CPU资源。
最常见的就是Linux特用的epoll IO多路复用机制,它通过一些回调机制以及独特的数据结构,降低资源开销,并且本身的性能也非常高。

4 .部署公网IP
我们自己写的这个服务器,设置的是回环IP 2 。服务器和客户端也是在自己的电脑上运行的。倘若把客户端的代码给到其他设备执行,发送的请求在我们的电脑上是不会得到响应的。换句话说,这个服务器只能在自己的设备上自嗨,不具备真正的联网能力!
**想要具备联网能力,那么需要把回环IP“126.0.0.1”换成公网IP。**最好的方式就是把服务器代码部署到云服务器上,云服务器可以通过阿里云、腾讯云等厂商购买,这里就不作过多演示了。


    • 长连接:服务器与客户端建立连接之后,不会立即断开,而是持续接收、处理请求。它能够减少连接建立和断开的开销,因此适合频繁请求响应的场景。
    • 短连接: 客户端服务器建立连接在完成一次数据传输后就断开连接。比较适合请求量少或不频繁的场景。
    ↩︎
  1. 回环IP地址的标准范围是 127.0.0.0/8,即 127.0.0.1 到 127.255.255.255 都被视为回环地址。最常用的回环IP地址是 127.0.0.1,它通常被称为“本地环回地址”或“localhost”。这种IP仅在本地主机使用有效,无法通过回环IP和其他主机通信! ↩︎

这篇关于JavaEE第19节 用UDP套接字实现简单回显服务器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

C#借助Spire.XLS for .NET实现在Excel中添加文档属性

《C#借助Spire.XLSfor.NET实现在Excel中添加文档属性》在日常的数据处理和项目管理中,Excel文档扮演着举足轻重的角色,本文将深入探讨如何在C#中借助强大的第三方库Spire.... 目录为什么需要程序化添加Excel文档属性使用Spire.XLS for .NET库实现文档属性管理Sp

Python+FFmpeg实现视频自动化处理的完整指南

《Python+FFmpeg实现视频自动化处理的完整指南》本文总结了一套在Python中使用subprocess.run调用FFmpeg进行视频自动化处理的解决方案,涵盖了跨平台硬件加速、中间素材处理... 目录一、 跨平台硬件加速:统一接口设计1. 核心映射逻辑2. python 实现代码二、 中间素材处

Java方法重载与重写之同名方法的双面魔法(最新整理)

《Java方法重载与重写之同名方法的双面魔法(最新整理)》文章介绍了Java中的方法重载Overloading和方法重写Overriding的区别联系,方法重载是指在同一个类中,允许存在多个方法名相同... 目录Java方法重载与重写:同名方法的双面魔法方法重载(Overloading):同门师兄弟的不同绝

Spring配置扩展之JavaConfig的使用小结

《Spring配置扩展之JavaConfig的使用小结》JavaConfig是Spring框架中基于纯Java代码的配置方式,用于替代传统的XML配置,通过注解(如@Bean)定义Spring容器的组... 目录JavaConfig 的概念什么是JavaConfig?为什么使用 JavaConfig?Jav

Java数组动态扩容的实现示例

《Java数组动态扩容的实现示例》本文主要介绍了Java数组动态扩容的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1 问题2 方法3 结语1 问题实现动态的给数组添加元素效果,实现对数组扩容,原始数组使用静态分配

Java中ArrayList与顺序表示例详解

《Java中ArrayList与顺序表示例详解》顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构,:本文主要介绍Java中ArrayList与... 目录前言一、Java集合框架核心接口与分类ArrayList二、顺序表数据结构中的顺序表三、常用代码手动

JAVA项目swing转javafx语法规则以及示例代码

《JAVA项目swing转javafx语法规则以及示例代码》:本文主要介绍JAVA项目swing转javafx语法规则以及示例代码的相关资料,文中详细讲解了主类继承、窗口创建、布局管理、控件替换、... 目录最常用的“一行换一行”速查表(直接全局替换)实际转换示例(JFramejs → JavaFX)迁移建

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

Python实现快速扫描目标主机的开放端口和服务

《Python实现快速扫描目标主机的开放端口和服务》这篇文章主要为大家详细介绍了如何使用Python编写一个功能强大的端口扫描器脚本,实现快速扫描目标主机的开放端口和服务,感兴趣的小伙伴可以了解下... 目录功能介绍场景应用1. 网络安全审计2. 系统管理维护3. 网络故障排查4. 合规性检查报错处理1.

JAVA线程的周期及调度机制详解

《JAVA线程的周期及调度机制详解》Java线程的生命周期包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,线程调度依赖操作系统,采用抢占... 目录Java线程的生命周期线程状态转换示例代码JAVA线程调度机制优先级设置示例注意事项JAVA线程