基于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代表"本机", 如果客户端和服务器在一个主机上, 就使用这个IP
        client.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包装一下inputStream
            Scanner 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);
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DICOM核心概念:显式 VR(Explicit VR)与隐式 VR(Implicit VR)在DICOM中的定义与区别

在DICOM(Digital Imaging and Communications in Medicine)标准中,VR(Value Representation) 表示数据元素的值的类型和格式。理解显式 VR(Explicit VR)与隐式 VR(Implicit VR&#…

安卓应用安装过程学习

声明:此文章来自http://shuwoom.com/?p60的学习记录 启动式安装 public static final IPackageManager main(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {PackageManagerService m new PackageManagerService(context, inst…

基于Java Springboot医疗垃圾分类系统

一、作品包含 源码数据库全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA/eclipse 数据库:…

SQL99版全外连接和交叉连接和总结

全外连接MySQL不支持 elect 查询列表 from 表名1 表别名1 cross join 表名2 表别名2 on 连接条件 ...... ; 交叉连接 就两个记录做笛卡尔积!没什么好说的,基本也没用过! 总结

推荐一款开源电子书阅读器Koodo Reader

Koodo Reader 是一个开源的电子书阅读器,支持多达15种主流电子书格式, 内置笔记、高亮、翻译功能,助力高效书籍阅读和学习。 官网地址:https://www.koodoreader.com/zh 一、下载软件 下载地址:https://dl.koodoreader.…

WebStorm 2024.3/IntelliJ IDEA 2024.3出现elementUI提示未知 HTML 标记、组件引用爆红等问题处理

WebStorm 2024.3/IntelliJ IDEA 2024.3出现elementUI提示未知 HTML 标记、组件引用爆红等问题处理 1. 标题识别elementUI组件爆红 这个原因是: 在官网说明里,才版本2024.1开始,默认启用的 Vue Language Server,但是在 Vue 2 项目…

Harbor2.11.1生成自签证和配置HTTPS访问

文章目录 HTTPS的工作流程部署Harbor可参考上一篇文章生成自签证书1.修改/etc/hosts文件2.生成证书a.创建存放证书路径b.创建ca.key密钥c.创建ca.crtd.创建给Harbor服务器使用密钥 yunzhidong.harbor.com.keye.创建给Harbor服务器使用证书签名请求文件 yunzhidong.harbor.com.c…

【深度学习之二】正则化函数(weight decay, dropout, label smoothing, and etc)详解,以及不同的函数适用的场景

在深度学习中正则化函数的重要性不言而喻,今天主要总结一些当前常用的一些正则化函数 在深度学习中,正则化(Regularization)是一种防止模型过拟合的技术。过拟合指的是模型在训练数据上表现很好,但在未见过的测试数据…

uni-app 修改复选框checkbox选中后背景和字体颜色

编写css(注意:这个样式必须写在App.vue里) /* 复选框 */ /* 复选框-圆角 */ checkbox.checkbox-round .wx-checkbox-input, checkbox.checkbox-round .uni-checkbox-input {border-radius: 100rpx; } /* 复选框-背景颜色 */ checkbox.checkb…

Ngrok实现内网穿透(Windows)

Ngrok实现内网穿透(Windows) 什么是内网穿透,内网穿透有什么用 内网穿透(NAT traversal)是一种技术手段,使得位于内网或防火墙后面的设备能够通过外网访问。例如,如果你的计算机、服务器等设备…

Simulink中Model模块的模型保护功能

在开发工作过程中,用户为想要知道供应商的开发能力,想要供应商的模型进行测试。面对如此要求,为了能够尽快拿到定点项目,供应商会选择一小块算法或是模型以黑盒的形式供客户测试。Simulink的Model模块除了具有模块引用的功能之外&…

Linux内核USB2.0驱动框架分析--USB包

一, 包的组成 每个包都由SOP(包起始域)、SYNC(同步域)、Packet Content(包内容)、EOP(包结束域)四部分组成,其中SOP、SYNC、EOP为所有包共有的域&#xff0c…

STM32F4----ADC模拟量转换成数字量

STM32F4----ADC模拟量转换成数字量 基本原理 当需要测量和记录外部电压的变化,或者根据外部电压的变化量来决定是否触发某个动作时,我们可以使用ADC(模拟—数字转换器)功能。这个功能可以将模拟的电压信号转换为数字信号&#x…

大数据学习18之Spark-SQL

1.概述 1.1.简介 Spark SQL 是 Apache Spark 用于处理结构化数据的模块。 1.2.历史 1.2.1.Shark Hadoop诞生初期,Hive是唯一在Hadoop上运行的SQL-on-Hadoop工具,MR的中间计算过程产生了大量的磁盘落地操作,消耗了大量的I/O,降低…

医学AI公开课·第一期|Machine LearningTransformers in Med AI

小罗碎碎念 从这周开始,我计划每个周末录一个视频,分享一些医学人工智能领域的进展。 作为第一期视频,我打算介绍一下机器学习和Transformer在医学AI领域中的应用。 为了准备这期视频,总共做了24页PPT(三部分内容&…

小白投资理财 - 解读威廉指标 WR

小白投资理财 - 解读威廉指标 WR WR 指标WR 指标特点WR 指标解读WR 与其他指标的结合实战案例:WR 计算WR 的优缺点WR 和 Williams Fractals 的主要区别总结 上篇《小白投资理财 - 解读威廉分形指标 Williams Fractals》,今天我们来了解另外一个威廉指标 …

前端速通(HTML)

1. HTML HTML基础&#xff1a; 什么是HTML&#xff1f; 超文本&#xff1a; "超文本"是指通过链接连接不同网页或资源的能力。HTML支持通过<a>标签创建超链接&#xff0c;方便用户从一个页面跳转到另一个页面。 标记语言&#xff1a; HTML使用一组预定义的标签…

电商一件发货软件闲管家使用教程

闲鱼闲管家是一款专为闲鱼卖家设计的电脑版工作台&#xff0c;旨在帮助卖家更高效地管理其在闲鱼平台上的业务。以下是关于闲鱼闲管家的一些主要特点和功能&#xff1a; 主要特点&#xff1a; 多账号管理&#xff1a;支持同时管理多达30个闲鱼账号&#xff0c;方便大型卖家或…

第一个autogen与docker项目

前提条件&#xff1a;在windows上安装docker 代码如下&#xff1a; import os import autogen from autogen import AssistantAgent, UserProxyAgentllm_config {"config_list": [{"model": "GLM-4-Plus","api_key": "your api…

JavaEE 【知识改变命运】02 多线程(1)

文章目录 线程是什么&#xff1f;1.1概念1.1.1 线程是什么&#xff1f;1.1.2 为什么要有线程1.1.3 进程和线程的区别1.1.4 思考&#xff1a;执行一个任务&#xff0c;是不是创建的线程或者越多是不是越好&#xff1f;&#xff08;比如吃包子比赛&#xff09;1.1.5 ) Java 的线程…