基于UDP和TCP实现回显服务器

目录

一. UDP 回显服务器

1. UDP Echo Server

2. UDP Echo Client

二. TCP 回显服务器

1. TCP Echo Server

2. TCP Echo Client


回显服务器 (Echo Server) 就是客户端发送什么样的请求, 服务器就返回什么样的响应, 没有任何的计算和处理逻辑.

一. UDP 回显服务器

1. UDP Echo Server

下面实现服务器.

public class UdpEchoSever {private DatagramSocket socket = null;public UdpEchoSever(int port) throws SocketException { socket = new DatagramSocket(port); // 创建一个DatagramSocket对象,并绑定一个端口号.}}

(1) 这里声明的SocketException是IOException的子类, 是网络编程中常见的异常.

(2) UdpEchoSever的构造方法方法中, 在调用DatagramSocket的构造方法时, jvm就会调用系统API, 完成 "端口号 - 进程" 的绑定.

(3) 同一时刻, 一个端口号只能绑定一个进程; 而一个进程可以绑定多个端口号.

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 responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress()); socket.send(responsePacket);// 4. 打印日志System.out.printf("[%s:%d] req=%s, resp=%s\n", requestPacket.getAddress(), requestPacket.getPort(),request, response);}}

 接下来我们通过start()方法编写服务器的核心流程

1. 读取客户端请求并解析

(1) 服务器的主要工作, 就是不停地处理客户端发来的请求. 所以需要写一个 while(true) 死循环来不停地处理客户端发来的请求.

(2) 这里的 receive 方法: 一调用receive方法, 就会就从网卡上读取数据, 但是此时网卡上不一定有数据, 如果网卡上有数据, receive立即返回获取到的数据; 如果网卡上没数据, receive就会阻塞等待, 一直等待到获取到数据为止.  此处receive中的的参数也是"输出型参数", 从网卡中获取到的数据会存到requestPacket里面.

(3) receive接收到的数据是二进制数据, 为了方便后续处理, 我们把它转成字符串类型的数据.

2. 根据请求, 计算响应  

因为我们这里实现的是回显服务器, 所以响应 == 请求.

3. 把相应写回客户端

由于我们为了方便处理吧字节数组转成了字符串, 所以在往回发的时候需要再基于字符串构造出字节数组. 并且, 由于UDP是无连接的, 所以通信双方不包含对端信息, 所以在往回发的时候, 我们还要带上客户端信息. 客户端信息可以从请求中拿到. getSocketAddress()这个方法就会返回客户端的IP和端口号.

4. 打印日志

那么这样的话服务器端的代码就完成了.

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class UdpEchoSever {private DatagramSocket socket = null;public UdpEchoSever(int port) throws SocketException {socket = new DatagramSocket(port);}// 通过start()方法启动服务器核心流程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 responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress());socket.send(responsePacket);// 4. 打印日志System.out.printf("[%s:%d] req=%s, resp=%s\n", requestPacket.getAddress(), requestPacket.getPort(),request, response);}}private String process(String request) {return request;}}

此处还有一个小问题, 这里我们创建了Socket对象, 使用完成之后应该关闭资源啊, 但是我们的代码里并没有写close() --> 主要是因为这里Socket的生命周期是跟随进程的, 进程退出, Socket资源自然也就关闭了.

2. UDP Echo Client

import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket = null;private String severIP;private int severPort;public UdpEchoClient(String severIP, int severPort) throws SocketException {socket = new DatagramSocket(); // 服务器创建Socket对象, 一定要指定端口号 (服务器必须是指定了端口号, 客户端发起的时候, 才能找到服务器),// 但是客户端这里最好不要指定端口号 因为我们不知道客户端那个端口繁忙, 那个端口空闲, 所以我们手动指定, 让系统去指定一个最合适的端口.this.severIP = severIP;this.severPort = severPort;}public void start() throws IOException {System.out.println("启动客户端");Scanner scanner = new Scanner(System.in);while (true) {// 1.从控制台读取用户输入System.out.println("-> ");String request = scanner.next();// 2. 构造出一个UDP请求数据报, 发送给服务器DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,InetAddress.getByName(this.severIP), this.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 {UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9071);// 这里的127.0.0.1是特殊的IP地址, 是"环回IP" 这个IP代表"本机", 如果客户端和服务器在一个主机上, 就使用这个IPclient.start();}
}

注意这里使用到了一个特殊的IP地址: "127.0.0.1" 这个IP地址叫做"回环IP", 代表"本机", 如果客户端可服务器都在同一个主机上, 就使用这个IP.

下面我们来看一下运行结果:

没有任何问题~

服务器和客户端交互的过程大致如下:

(1) 启动服务器, 服务器等待请求, 如果没有请求发来, 就一直阻塞.

(2) 启动客户端, 在客户端输入内容, 发送请求. (客户端发送完请求之后进入receive等待服务器返回响应.)

(3) 服务器收到请求, 并对请求做出响应. 服务器往客户端返回响应.

(4) 客户端收到响应, 交互结束. 进入下一轮交互.

二. TCP 回显服务器

1. TCP Echo Server

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 ServerSocket serverSocket = null;public TcpEchoSever(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("启动服务器");while (true) {Socket clientSocket = serverSocket.accept();// 每次服务器调用一次accept就会产生一个新的socket对象, 来和客户端进行"一对一服务"// TCP建立连接的过程是由操作系统完成的, 代码不能直接感知到 ~// 已经完成建立连接的操作了, 才能进行accept. accept相当于是针对内核中已经建立好的连接做一个"确认"动作.processConnection(clientSocket);}}private void processConnection(Socket clientSocket) {// 先打印客户端信息System.out.printf("[%s:%d] 客户端上线!", clientSocket.getInetAddress(), clientSocket.getPort());// 获取到Socket中持有的流对象.//TCP是全双工的通信, 一个Socket对象, 既可以读, 也可以写 ~try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){// 使用Scanner包装一下inputStreamScanner scanner = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while (true) {// 1. 读取请求并解析if (!scanner.hasNext()) {// 如果scanner中无法读取出数据, 说明客户端关闭了连接, 导致服务器这里读到了末尾 ~break;}String request = scanner.next();// 2. 根据请求计算响应String response = process(request);// 3. 把响应写回给客户端//outputStream.write(response.getBytes());printWriter.println(response); //这里使用println是为了在数据末尾能够加上一个换行.// 4. 打印日志System.out.printf("[%s:%d] req=%s; resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),request, response);}} catch(IOException e) {e.printStackTrace();}System.out.printf("[%s:%d] 客户端下线\n", clientSocket.getInetAddress(),clientSocket.getPort());}private String process(String request) {return request;}
}

2. TCP Echo Client

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 Socket socket = null;public TcpEchoClient(String serverIp, int serverPort) throws IOException {socket = new Socket(serverIp, serverPort);}public void start() {System.out.println("客户端启动");try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);Scanner scannerIn = new Scanner(System.in);PrintWriter printWriter = new PrintWriter(outputStream);while (true) {// 1. 从控制台读取数据System.out.print("-> ");String request = scannerIn.next();// 2. 把请求发送给服务器printWriter.println(request);printWriter.flush(); // 刷新缓冲区// 3. 从服务器读取响应if (!scanner.hasNext()) {break;}String response = scanner.next();// 4. 打印响应结果System.out.println(response);}} catch (Exception e) {throw new RuntimeException(e);}}

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

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

相关文章

银行卡OCR识别API接口的作用有哪些?

在当今数字化高速发展的时代,各种创新技术不断涌现,为我们的生活带来了极大的便利。其中,银行卡 OCR 识别 API 接口就是一项非常实用的技术,它提高了业务办理的效率,准确性高,便捷性强,安全性高…

STM32完全学习——使用标准库完成PWM输出

一、TIM2初始化 我这里使用的是STM32F407ZGT6这个芯片,我这里使用的是定时器TIM2来完成PWM输出,由于这里没有使用中断,因此不需要初始化NVIC,下面先来进行定时器的相关初始化 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;R…

Qt Qt::UniqueConnection 底层调用

在这里插入图片描述 步骤1: 1:判断槽函数连接方式, 以及信号对象是否有效2: 信号计算格式是否 大于 signal_index 目前调试 signal_index 不太清楚怎末计算的(有清楚的帮忙街道)3:获取槽函数对…

7-10 解一元二次方程

7-10 解一元二次方程 分数 20 全屏浏览 切换布局 作者 李祥 单位 湖北经济学院 请编写程序,解一元一次方程 ax2bxc0 。 已知一元二次方程的求根公式为: 要求: 若 a0,则为一元一次方程。 若 b0,则方程有唯一解&…

Oracle - 多区间按权重取值逻辑 ,分时区-多层级-取配置方案(三)

本篇紧跟第一篇, 和 第二篇无关 Oracle - 多区间按权重取值逻辑 ,分时区-多层级-取配置方案 Oracle - 多区间按权重取值逻辑 ,分时区-多层级-取配置方案(二) 先说需求: 某业务配置表,按配置的时间区间及组织层级取方…

prompt

1解释概念 中文指令:请借助费曼学习法,以简单的语言解释[特定概念]是什么,并提供一个例子来说明它如何应用。 Prompt:Please use the Feynman Learning Technique to explain [specific concept] insimple language,and provide an example …

(免费送源码)计算机毕业设计原创定制:Java+JSP+HTML+JQUERY+AJAX+MySQL springboot计算机类专业考研学习网站管理系统

摘 要 大数据时代下,数据呈爆炸式地增长。为了迎合信息化时代的潮流和信息化安全的要求,利用互联网服务于其他行业,促进生产,已经是成为一种势不可挡的趋势。在大学生在线计算机类专业考研学习网站管理的要求下,开发一…

「Mac玩转仓颉内测版32」基础篇12 - Cangjie中的变量操作与类型管理

本篇将深入探讨 Cangjie 编程语言中的变量操作与类型管理,涵盖变量的定义、作用域、类型推断、常量、变量遮蔽、类型转换等方面的知识。通过这些概念的学习,开发者将更好地理解和灵活掌握变量的使用与管理技巧。 关键词 变量定义类型推断常量变量作用域…

Python和R基因组及蛋白质组学和代谢组学

🌵Python片段 1. 数据处理与清理 基因组病理学的数据通常非常庞大,且可能包括 DNA 或 RNA 测序结果、基因表达数据等。Python 提供了高效的数据处理工具。 工具和库 Pandas: 用于加载、清理和操作数据。Numpy: 用于高效的数值计算。Dask: 用于大规模数…

【华为云函数工作流】python的函数中如何获取请求链接中带的参数

背景 通过调用函数的url,将参数传递给函数执行,函数里如何获取这个参数 过程 下一个简单的demo如下 参考这个链接https://support.huaweicloud.com/devg-functiongraph/functiongraph_02_0420.html写一个demo,这个是百度视频云获取token的…

Varjo:垂直起降机混合现实培训解决方案

混合电动垂直起降机(VTOL)作为一种新型的航空运输机具有超越传统汽车的安全性、与飞机相当的速度以及无与伦比的灵活起降功能。电动垂直起降机能够在建筑顶部、直升机场或是没有跑道的地区起飞或降落,且排放要远远低于由航空汽油驱动的传统飞…

AWTK fscript 中的 大端小端扩展函数

fscript 是 AWTK 内置的脚本引擎,开发者可以在 UI XML 文件中直接嵌入 fscript 脚本,提高开发效率。本文介绍一下 fscript 中的 ** 大端小端扩展函数 ** 1.is_little 判断当前 CPU 是否是小端。 原型 is_little() > bool示例 print(is_little());2…

Mybatis中使用原生sql与参数进行查询

Mybatis中使用原生sql与参数进行查询.md 一、mapper中定义接口二、mapper对应的xml三、使用样例 有时需要使用原生sql和参数进行动态查询(比如可能是通过参数或配置的sql或sql片段执行查询,可能需要用到原生sql查询场景),使用方式…

禁止 Kindeditor富文本粘贴图片和html格式

Kindeditor 文本编辑器是可以通过ctrlv粘贴图片的,粘贴完的图片会转成base54格式,发送到后端需要后端将base64图片转成图片存到服务器上,在将图片路径回填回去,比较费事, 可以将 Kindeditor的 pasteType参数设置成1&am…

从监控异常发现网络安全

前言 最近在前端异常监控系统中,发现一些异常信息,从中做了一些分析,得到一些体会,因此作文。 发现异常 某天早上打开监控系统发现,当天凌晨1点过测试环境有2个前端上报的异常,报错的原因都是由于没有获取…

Android 实现悬浮球的功能

Android 实现悬浮球的功能 在 Android 中&#xff0c;实现悬浮球可以通过以下方式实现&#xff0c;常见的方法是使用 WindowManager 创建一个悬浮窗口。以下是具体的实现步骤&#xff1a; 1. 配置权限 在 AndroidManifest.xml 中添加悬浮窗权限&#xff1a; <uses-permis…

[Python3学习笔记-基础语法] Python3 基础语法

本篇文章详细介绍Python3的基础语法&#xff0c;主要包括编码、标识符、Python保留字、注释、行缩进、多行语句、Number类型、字符串、空行、print打印等。 这些是Python最基础的东西&#xff0c;掌握好了才能更好的学习后续的内容。 有兴趣共同结伴学习Python的朋友&#xff0…

RabbitMQ3:Java客户端快速入门

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

使用JDBC操作数据库

文章目录 使用JDBC操作数据库1. JDBC访问数据库步骤2. Statement与PreparedStatement区别3. JDBC的内容4. JDBC封装4.1 为什么进行JDBC封装4.2 实现JDBC封装4.3 什么是DAO4.4 配置数据库访问参数4.5 配置数据库连接池使用之JNDI的方式 5. 单例模式5.1 懒汉模式5.2 饿汉模式 使用…

《那个让服务器“跳舞”的bug》

在程序的世界里&#xff0c;bug 就像隐藏在暗处的小怪兽&#xff0c;时不时跳出来捣乱。而在我的职业生涯中&#xff0c;有一个bug让我至今难忘&#xff0c;它不仅让项目差点夭折&#xff0c;还让我熬了无数个通宵。这个故事发生在一个风和日丽的下午&#xff0c;我们正在开发一…