Java EE(13)——网络编程——UDP/TCP回显服务器

前言

本文主要介绍UDP和TCP相关的API,并且基于这两套API实现回显服务器

UDP和TCP

UDP和TCP属于网络五层模型中传输层的协议
特点:
UDP:无连接,不可靠,面向数据包,全双工
TCP:有连接,可靠,面向字节流,全双工
1.无连接和有连接
这里所说的连接不是指物理意义上的通过实物来进行绑定,而是虚拟的连接。举个例子,你打电话给对方,对方接通之后你才能进行后续通信,这就是有连接;无连接就相当于QQ发消息,无论对方是否同意,消息都能发过去
2.可靠和不可靠
不论是哪种通信方式,实际上都无法保证数据一定能传输成功。所以,这里的可靠指的是,能获取到数据的传输情况。即使传输失败了我能知道它传输失败了,重传就是了;不可靠指的是信息传输之后就不管了,传输成功与否都和我无关,更不会重传
3.全双工和半双工
全双工通信允许通信的双方可以同时发送和接收数据,半双工通信同一时间内只能在同一方向上传输

什么是回显服务器

回显的意思是无论客户端给服务器发送什么请求,服务器会把客户端的请求原样返回

1.UCP回显服务器

1.1API介绍

Java中UDP协议的API有两个,一个是DatagramSocket,一个是DatagramPacket

1.1.1DatagramSocket类

作用:用于应用程序之间发送和接收UDP数据报
构造方法:

//不指定端口号,由系统随机分配
DatagramSocket()
//指定端口号
DatagramSocket(int port)

其他方法:

//接收一个数据报
receive(DatagramPacket p)
//发送一个数据报
send(DatagramPacket p)

1.1.2DatagramPacket类

作用:封装UDP数据报的数据和目标地址信息。它包含要发送的数据,目的主机的端口号和IP地址
构造方法:

//字节数组,字节数组长度,服务器IP,服务器端口号
//这是传入的IP地址和端口号是固定值,因为服务器的IP和端口号一般不变
//所以作为DatagramSocket类的send方法的参数,用于客户端向服务器发送请求
DatagramPacket(byte buf[], int length,InetAddress address, int port)//字节数组,字节数组长度
//这个和上面有所不同,主要用于服务器向客户端返回请求
//一会代码再具体讲解
DatagramPacket(byte buf[], int length, SocketAddress address)//字节数组,字节数组长度
//作为DatagramSocket类的receive方法的参数,用于客户端接收服务器的响应   或者   用于服务器接收客户端的请求
DatagramPacket(byte buf[], int length)

1.2UDP回显服务器代码

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class EchoSever {private final DatagramSocket socket;//传入端口号public EchoSever(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);//receive从网卡读取到UDP数据报,载荷部分放到byte数组里//UDP报头存放在requestPacket其他属性里//还能通过requestPacket知道源IP/端口//receive具有阻塞功能socket.receive(requestPacket);//将接收到的字节数组转换为字符串//requestPacket.getLength()得到的长度是有效长度,不一定是4096String request = new String(requestPacket.getData(),0,requestPacket.getLength());//2.根据请求计算相应(回显服务器啥都不用做)String response = process(request);//3.返回响应到客户端//response.getBytes()得到String内部的字节数组//当String里面全都是英文字符的时候,response.length()是可以的//因为一个英文字母对于一个字节,但是一个汉字对应多个字节//requestPacket.getSocketAddress()找到对应客户端的IP和端口号DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);//打印日志//IP,端口号,请求,响应System.out.printf("[%s:%d] request:%s,response:%s\n",requestPacket.getAddress().toString(),responsePacket.getPort(),request,response);}}//根据请求计算相应public String process(String request){return request;}//public static void main(String[] args) throws IOException {EchoSever sever = new EchoSever(9090);sever.start();}
}

注意1:DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length,requestPacket.getSocketAddress());
因为requestPacket数据报保存了客户端的IP地址和端口号,所以通过getSocketAddress()能获取到客户端的IP地址和端口号
//
其次,客户端的IP地址和端口号是系统随机分配的,而且服务器会同时处理多个客户端请求,这些客户端的IP和端口号都不一样,通过getSocketAddress()才能正确的找到对应客户端,而不是输入固定的IP和端口号
//
这就是为什么该方法用于服务器返回客户端的响应,而DatagramPacket(byte buf[], int length,InetAddress address, int port)用于客户端向服务器发送请求
//
对比两个方法不难发现,前者获取到的IP和端口号是不固定的,而后者是指定IP和端口号
注意2:为什么服务器要指定端口号?而客户端的端口号是随机分配?
讲服务器比作餐厅,客户端比作顾客。顾客来餐厅吃饭,坐的桌子就相当于端口号,顾客今天坐001号桌,改天坐002号桌,人家乐意坐哪就坐那;但是餐厅的位置肯定是固定的,不可能今天餐厅在河边,明天餐厅就跑到半山腰去了吗,餐厅的位置不能改变,否则顾客找不到餐厅的位置

1.2UDP客户端代码

import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class EchoClient {private final DatagramSocket socket;//这里的IP是Stringprivate final String severIP;private final int severPort;//传入服务器IP和端口public EchoClient(String severIP,int severPort) throws SocketException {this.socket = new DatagramSocket();this.severIP = severIP;this.severPort = severPort;}//启动客户端public void start() throws IOException {System.out.println("客户端启动");Scanner in = new Scanner(System.in);while (true){//提示用户要输入请求了System.out.print("-> ");//1.从控制台读取要发送的请求数据//在用户输入之前有阻塞效果if (!in.hasNext()){break;}String request = in.next();//2.请求并发送//字节数组,字节数组长度,服务器IP,服务器端口号DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(severIP),severPort);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 {EchoClient client = new EchoClient("127.0.0.1",9090);client.start();}
}

2.TCP回显服务器

2.1API介绍

Java中TCP协议的API有两个,一个是SeverSocket,一个是Socket

2.1.1SeverSocket类

作用:用于服务器监听来自客户端的TCP连接请求
构造方法:

//不指定端口号,由系统随机分配
ServerSocket()
//指定端口号
ServerSocket(int port)

其他方法:

//监听并接受客户端传入的连接请求。此方法有阻塞效果,直到有客户端连接
Socket accept():

2.1.2Socket类

作用:主要用于客户端和服务器之间建立TCP连接
构造方法:

//通过传入IP和端口号连接到指定的主机(服务器)
Socket(String host, int port)

其他方法:

//返回此套接字(实例)的输入流,用于接收数据
getInputStream()
//返回此套接字(实例)的输出流,用于发送数据
getOutputStream()

2.1TCP回显服务器代码

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;public class TCPEchoSever {private final ServerSocket socket;//public TCPEchoSever(int port) throws IOException {socket = new ServerSocket(port);}//启动服务器private void start() throws IOException {System.out.println("服务器启动");while (true){//将服务器和客户端连接//accept()有阻塞效果,等待客户端建立联系Socket clientSocket = socket.accept();//每与一个客户端建立连接,都创建一个线程来执行客户端的请求Thread thread = new Thread(()->{//服务器和客户端交互try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});thread.start();}}//public void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 服务器上线\n",clientSocket.getInetAddress(),clientSocket.getPort());//inputStream从网卡读数据try(InputStream inputStream = clientSocket.getInputStream();//OutputStream往网卡写数据OutputStream outputStream = clientSocket.getOutputStream()) {//从网卡读数据//byte[] array = new byte[1024];int ret = inputStream.read(array);PrintWriter printWriter = new PrintWriter(outputStream);Scanner scanner = new Scanner(inputStream);while (true){//读取完毕,当客户端下线的时候产生//在用户输入之前,hasNext()有阻塞效果//当客户端断开连接时,scanner.hasNext()返回false并中断循环if (!scanner.hasNext()){System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}//1.读取请求并解析//用户传过来的请求必须带有空白符,没有的话就会阻塞String request = scanner.next();//2.计算响应String response = process(request);//3.返回响应//outputStream.write(response.getBytes(),0,response.getBytes().length);//这个方式不方便添加空白符//通过PrintWriter来封装outputStream//添加\nprintWriter.println(response);//刷新缓冲区printWriter.flush();//打印日志System.out.printf("[%s:%d] request:%s,response:%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);}} catch (IOException e) {throw new RuntimeException(e);}finally {clientSocket.close();}}//计算响应private String process(String request) {return request;}//public static void main(String[] args) throws IOException {TCPEchoSever sever = new TCPEchoSever(9090);sever.start();}
}

注意:为什么要调用clientSocket.close()?
因为每和一个客户端连接都会创建一个clientSocket套接字,它负责和客户端交互,即便客户端进程终止了,客户端的socket会被操作系统回收,但服务器中的clientSocket仍然会占用文件描述符和内存资源。当文件资源耗尽时,就无法与新的客户端建立连接

2.2TCP客户端代码

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;public class TCPEchoClient {private final Socket socket;public TCPEchoClient(String severIp,int port) throws IOException {//与服务器建立联系socket = new Socket(severIp,port);}//public void start(){System.out.println("客户端启动");try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {//读取控制台Scanner scannerConsole = new Scanner(System.in);Scanner scannerNetWork = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while (true){System.out.print("->");//在用户输入之前,hasNext()有阻塞效果if (!scannerConsole.hasNext()){break;}//1.从控制台输入请求String request = scannerConsole.next();//2.发送请求//让请求的结尾有\nprintWriter.println(request);//刷新缓冲区printWriter.flush();//3.从服务器读取响应String response = scannerNetWork.next();//4.将响应打印到控制台System.out.println(response);}} catch (IOException e) {throw new RuntimeException(e);}}//public static void main(String[] args) throws IOException {TCPEchoClient client = new TCPEchoClient("127.0.0.1",9090);client.start();}
}

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

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

相关文章

【蓝桥杯】12111暖气冰场(多源BFS 或者 二分)

思路 这题可以用BFS做,也可以用二分来做。 用二分这里只提供一个思路:对时间来二分查找,check函数就是检查在特定的时间 t 0 t_0 t0​内每一个暖气炉的传播距离能否覆盖所有格子。 用BFS做: 由几个点开始向外扩散,知道…

使用bat批量获取WORD中包含对应字符的段落,段落使用回车换行

get_word_paragraphs.vbs 获取命令行参数 If WScript.Arguments.Count 0 ThenWScript.Quit 1 End If 获取 Word 文档路径 docPath WScript.Arguments(0) 创建 Word 应用程序对象 Set objWord CreateObject("Word.Application") objWord.Visible False 打开 Word …

DeepSeek自学手册:《从理论(模型训练)到实践(模型应用)》|73页|附PPT下载方法

导 读INTRODUCTION 今天分享是由ai呀蔡蔡团队带来的DeepSeek自学手册:《从理论(模型训练)到实践(模型应用)》,这是一篇关于DeepSeek模型训练、应用场景及替代方案的综合指南文章,主要介绍了Deep…

WEB API 设计规范

REST API 简介 REST 是 Representational State Transfer 的缩写,它将资源作为核心概念,通过 HTTP 方法对资源进行操作。其本身是一套围绕资源进行操作的架构规范。在实际应用中,更多的是体现在 API 的设计上。 企业在进行产品设计开发时&a…

QT软件匠心开发,塑造卓越设计服务

在当今这个数字化飞速发展的时代,软件已经成为我们生活中不可或缺的一部分。而QT,作为一款跨平台的C图形用户界面应用程序开发框架,凭借其强大的功能和灵活性,在众多软件开发工具中脱颖而出。我们深知,在软件开发领域&…

标贝科技入选2025年市级数据要素市场化配置改革“揭榜挂帅”名单

近日,山东省大数据局、青岛市大数据局公布2025年数据要素市场化配置改革“揭榜挂帅”名单。标贝科技联合崂山区电子政务和大数据中心申报的“政务热线通话录音数据价值挖掘与权益保护”项目成功入选。这一成果不仅彰显了标贝科技在数据领域的创新实力,更…

Flutter TextField 从入门到精通:掌握输入框的完整指南

目录 1. 引言 2. TextField 的基本用法 3. 主要属性 4. 自定义 TextField 样式 4.1 自定义边框与提示文本 4.2 增加前缀/后缀图标 4.3 只允许输入数字 4.4 表单验证系统 4.5 动态样式修改 4.6 防抖搜索(Debounce) 5. 结论 相关推荐 1. 引言…

蓝桥杯备赛 背包问题

背包问题 ![[背包问题.png]] 01背包 1.题意概要:有 n n n个物品和一个容量为 V V V的背包,每个物品有重量 w i w_i wi​和价值 v i v_i vi​ 两种属性,要求选若干物品放入背包使背包中物品的总价值最大且背包中物品的总重量不超过背包的容…

MyBatis-Plus 自动填充:优雅实现创建/更新时间自动更新!

目录 一、什么是 MyBatis-Plus 自动填充? 🤔二、自动填充的原理 ⚙️三、实际例子:创建时间和更新时间字段自动填充 ⏰四、注意事项 ⚠️五、总结 🎉 🌟我的其他文章也讲解的比较有趣😁,如果喜欢…

arduino R4 SD卡读写测试

使用买来的 st7789LCD 显示器背面就带着一个 tf 卡槽,可以直接连接 tf 卡。使用 Sdfat 库就可以实现对 sd 卡的读写操作。这里尝试测试 sd 卡的读写功能。 LCD 显示器的初始化 //定义LCD的对象 Adafruit_ST7789 tft Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);tf…

【武汉·4月11日】Parasoft联合光庭信息研讨会|邀您共探AI赋能新机遇

Parasoft联合光庭信息Workshop邀您共探AI赋能新机遇 AI浪潮已至,你准备好了吗? 在智能网联汽车飞速发展的今天,AI技术正以前所未有的速度重塑行业生态。如何把握AI机遇,赋能企业创新? 4月11日,自动化软件…

VLLM专题(三十九)—自动前缀缓存(二)

前缀缓存(Prefix Caching)是一种在LLM推理中广泛使用的优化技术,旨在避免冗余的提示词(prompt)计算。其核心思想很简单——我们缓存已处理请求的键值缓存(kv-cache)块,并在新请求的前缀与之前请求相同时重用这些块。由于前缀缓存几乎是一种“免费的午餐”,并且不会改变…

自动驾驶系统的车辆动力学建模:自行车模型与汽车模型的对比分析

在自动驾驶系统的车辆动力学建模中,自行车模型(Bicycle Model)和更复杂的汽车模型(如双轨模型或多体动力学模型)各有其适用场景和优缺点。以下是两者的详细对比及选择原因解析: 1. 模型定义与核心差异 特性…

C语言入门教程100讲(6)类型修饰符

文章目录 1. 什么是类型修饰符?2. 常见的类型修饰符3. 类型修饰符的使用3.1 short 和 long3.2 signed 和 unsigned 4. 类型修饰符的组合5. 示例代码代码解析:输出结果: 6. 常见问题问题 1:short 和 long 的具体大小是多少&#xf…

Linux-Ubuntu 系统学习笔记 | 从入门到实战

📘 Linux-Ubuntu 系统学习笔记 | 从入门到实战 📜 目录 环境安装基本操作Linux操作系统介绍文件系统常用命令用户权限管理编辑器vimGCC编译器动态库与静态库Makefile 1. 环境安装 🌟 下载镜像 推荐使用清华大学开源镜像站下载Ubuntu镜像&a…

防火墙带宽管理

拓扑 配置 [fw]interface GigabitEthernet 0/0/0 [fw-GigabitEthernet0/0/0]service-manage all permit [fw]interface GigabitEthernet 1/0/0 [fw-GigabitEthernet1/0/0]ip address 12.0.0.1 24 [fw]interface GigabitEthernet 1/0/1 [fw-GigabitEthernet1/0/1]ip ad…

一人系统 之 为什么要做一人系统?

一人系统 之 赚钱认知篇(下) 本文 2119个字,大概阅读时间 16分钟。 在上一篇文章中,主要讲了以下三个内容: 什么是好的工作?时薪高,并且有能力提升,而且最终可以独立创业的工作&…

基于springboot的电影院管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 互联网技术的成熟和普及,势必会给人们的生活方式带来不同程度的改变。越来越多的经营模式中都少不了线上运营,互联网正强力推动着社会和经济发展。国人对民族文化的自信和不同文化的包容,再加上电影行业的发展,如此繁荣吸引…

Java安全-类的动态加载

类的加载过程 先在方法区找class信息,有的话直接调用,没有的话则使用类加载器加载到方法区(静态成员放在静态区,非静态成功放在非静态区),静态代码块在类加载时自动执行代码,非静态的不执行;先父类后子类,…

ROS多机通信功能包——Multibotnet

引言 这是之前看到一位大佬做的集群通信中间件,突发奇想,自己也来做一个,实现更多的功能、更清楚的架构和性能更加高效的ROS多机通信的功能包 链接:https://blog.csdn.net/benchuspx/article/details/128576723 Multibotnet Mu…