编写UDP版本的客户-服务器程序(echo server 和 echo client)

news2024/11/19 23:38:09

目录

前言概要

 关于数据报流的关键方法签名

UDP协议传输案例

服务端(接收端)

 服务端完整代码

 客户端(发送端)

客户端完整代码


创作不易多多支持😶‍🌫️😘


前言概要

         我们首先来了解一下, 什么是网络编程. 网络编程也就是网络上的主机, 通过不同的进程, 以编程的形式实现网络通信. 这种通信可以是同一个主机, 也可以是不同主机:

同一个主机上的不同进程之间的通信

 也可以是不同主机上的通信:

 计算机资源包括: 视频资源, 图片资源, 文本资源

        网络中的数据传输, 一般有发送端: 数据的发送方进程(源主机), 接收端: 数据的接收方进程, 收发方: 发送端和接收端两端.

        Socket套接字:  Socket套接字, 是由操作系统提供用于网络通信的技术, 是基于TCP/IP协议的网络通信的基本操作单元, 基于Socket套接字的网络程序开发就是网络编程

        Socket套接字, 主要针对传输层协议, 划分为三类: 

  1. 流套接字: 使用传输层TCP协议
  2. 数据报套接字: 使用UDP协议
  3. 原始套接字: 自定义传输层协议

接下来, 我们着重讲解数据报套接字.

        对于UDP协议来说, 具有无连接, 面向数据报的特征, 也就是说每次都是没有简历连接, 一次性的发送和接收数据.

        Java中使用UDP协议通信, 主要是基于DatagramSocket类来创建数据报套接字, 并使用DatagramPacket类作为被发送和接收的UDP数据报.

        其流程图大致如下:

         客户端和服务器之间通过DatagramSocket来建立连接, 客户端创建DatagramPacket数据报, 然后将对应的数据报发送给服务器, 服务器解析然后处理这个数据报, 随后服务器端创建一个DatagramPakcet数据报, 并填充处理结果, 并返回给客户端, 这里的客户端可以理解为发送端, 服务器可以理解为接收端.


 关于数据报流的关键方法签名

DatagramSocket:

        DatagramSocket是UDP协议的Socket套接字, 主要用于收发UDP数据报, 其构造方法如下:

DatagramSocket() : 创建一个UDP数据报套接字, 绑定到本机的任意一个端口(一般用于客户端)

DatagramSocket(int port) : 创建一个UDP数据报套接字, 绑定端口号为port. 一般用于服务器端

         其类方法如下:

void send(DatagramPacket dp) : 从此套接字发送数据(不会阻塞等待, 直接发送)

void receive(DatagramPacket dp) : 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)

void close() : 关闭此数据的套接字

 DatagramPacket:

        DatagramPakcet是UDP的数据报, 用来装填要传输的数据, 可以理解为装饭的饭盒子. 其方法的构造如下:

DatagramPacket(byte[ ] buf, int len) : 构造一个DatagramPacket数据报, 其数据保存在buf字节数组中, 接收的指定长度为len.

DatagramPacket(byte[ ] buf, int offset, int length, SocketAddress address) : 构造一个DatagramPacket数据报, 数据存储在buf字节数组当中, 从offset开始到指定的length长度, 然后通过adress指定主机的IP和端口号

         其类方法如下:

InetAddress getAddress () : 从接收的数据报中,获取发送端主机IP 地址;或从发送的数据报中,获取接收端主机IP 地址
int getPort ()  : 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData () : 获取数据报中的数据

 构造UDP发送的数据的时候, 需要传入SocketAddress, 该对象可以使用InetSocketAddress来创建:

InetSocketAddress API 方法签名:

InetSocketAddress(InetAddress addr, int port) : 创建一个Socket地址, 包含IP地址和端口号.


UDP协议传输案例

        这个实例是一传一收式的, 为了简化这个案例, 这里不将详细展示UDP数据的业务逻辑, 只是仅仅将接收端收到的UDP数据报简单的原封原样的返回到发送端作为处理逻辑.

        一传一收, 也就是指的, 发送端发出一个UDP数据报, 接收端接收到这个数据报, 直接原封返回这个数据报, 然后发送端接收这个数据报然后展示在窗口上.

  • 服务端(接收端)

  1. 首先创建服务端类:
  2. 想要通过网络通信, 就必须创建Socket套接字, 对于UDP来说, 需要创建一个DatagramSocket套接字来接收发送数据报
  3. 使用DatagramSocket的构造方法(参数为int port, 为端口号), 来实例化一个UDP的Socket套接字, 然后传入端口并让服务端绑定这个端口, 注意: 绑定端口不一定能成功, 如果这个端口port正在被其他的进程占用, 那么就会绑定失败(同一个主机的一个端口, 只能被一个进程绑定)
  4. 设置启动服务器的主逻辑(start()方法):
    public void start() {}, 逻辑为, 启动start方法之后, 服务器不断读取接收端发来的请求, 然后根据请求来处理, 随后响应给发送端. 这个过程是时时刻刻在进行的, 所以使用一个while(true)循环来实现: 

    循环里面主要实现三个步骤:
    1.读取请求: 读取请求需要构造一个数据报来接受这个数据, 也就是构建一个DatagramPacket对象:

            其中new byte[4096]为存储数据的部分, 后面的为长度. 随后进行读取, 使用DatagramSocket对象方法receive方法, 来接收数据:

            接收之前创建的数据报中的内容,这里为了简化逻辑, 此实现不带有任何业务逻辑, 仅仅只是简单的返回客户端发来的数据: 使用DatagramPakcet里面的方法getData(), 来获取这个数据报里面的数据, 然后使用String的构造方法, 将其转化为一个字符串:

    其构造方法如下:

    使用DatagramPacket里面的getData方法来获取一个byte[] 类型的数据, 然后传入偏移量0, 和长度(DatagramPacket里面的getLength方法获取)

    2.处理请求: 使用创建处理方法process(String str){return str}, 这里直接返回的原因还是和上面一样, 这里没有业务逻辑, 只是简单的返回这个读取到的结果:


    3. 把响应结果返回给发送端(客户端): 构造DatagramPacket对象, 将处理的结果放入到这个DatagramPacket对象里面去. 此时需要使用上面所提到的构造方法:


    此处使用String类的getBytes方法, 将字符串转化为一个字节数组:

    然后传入长度, 随后使用发送段发送过来的数据报对象DatagramPacket对象的getSocketAddress()方法来获取发送端(客户端)的IP和端口.
    随后使用DatagramSocket的send方法, 进行发送即可.

 服务端完整代码

 

package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;

public class UdpEchoServer {
    // 需要先定义一个 socket 对象.
    // 通过网络通信, 必须要使用 socket 对象.
    private DatagramSocket socket = null;

    // 绑定一个端口, 不一定能成功!!
    // 如果某个端口已经被别的进程占用了, 此时这里的绑定操作就会出错.
    // 同一个主机上, 一个端口, 同一时刻, 只能被一个进程绑定.
    public UdpEchoServer(int port) throws SocketException {
        // 构造 socket 的同时, 指定要关联/绑定的端口.
        socket = new DatagramSocket(port);
    }

    // 启动服务器的主逻辑.
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 每次循环, 要做三件事情:
            // 1. 读取请求并解析
            //    构造空饭盒
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            //    食堂大妈给饭盒里盛饭. (饭从网卡上来的)
            socket.receive(requestPacket);
            //    为了方便处理这个请求, 把数据包转成 String
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 2. 根据请求计算响应(此处省略这个步骤)
            String response = process(request);
            // 3. 把响应结果写回到客户端
            //    根据 response 字符串, 构造一个 DatagramPacket .
            //    和请求 packet 不同, 此处构造响应的时候, 需要指定这个包要发给谁.
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),0, response.getBytes().length,
                    // requestPacket 是从客户端这里收来的. getSocketAddress 就会得到客户端的 ip 和 端口
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

    // 这个方法希望是根据请求计算响应.
    // 由于咱们写的是个 回显 程序. 请求是啥, 响应就是啥!!
    // 如果后续写个别的服务器, 不再回显了, 而是有具体的业务了, 就可以修改 process 方法,
    // 根据需要来重新构造响应.
    // 之所以单独列成一个方法, 就是想让同学们知道, 这是一个服务器中的关键环节!!!
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
        udpEchoServer.start();
    }
}
  •  客户端(发送端)

         对于发送端, 由于他不需要处理数据, 只需要接收和发送, 对比于服务器结构就要简单的多了, 他的实现需要下面几个步骤:

  1. 同服务端一样, 需要一个类来包含这个网络通信程序的全部内容, 
  2. 想要发送数据, 就得有一个UDP数据套接字,  DatagramSocket字段, 同时, 发送数据还需要指定IP, 还有对应IP的应用程序的端口号:

    随后使用构造方法对这些字段进行赋值:

    此处的DatagramSocket()没有指定端口, 因为对于客户端来说, 不需要关联端口, 但是并不代表没有端口, 而是程序自动分配空闲的端口, 因为服务器返回响应的时候, 客户端任然需要接收这个响应, 也就需要使用到端口.
  3. start方法来启动这个客户端程序的逻辑:
    假设: 首先这个客户端会不断的读取用户的输入, 每一次的读取到的一次字符串, 客户端都会将其发送到我们已经设计好的服务器上, 并接收打印这个服务器传回来的响应, 同样使用一个while(true)来执行. 下面是while(true)里面的逻辑
    1.获取用户输入

    2.将获取到的字符串装入DatagramPacket数据报:
    使用构造方法:

    此处需要获取到服务器的IP和端口号, 使用InetAddress类来表示Internet协议的IP地址, 然后通过InetAddress类的静态方法:

    来确定主机IP, 也就是将String表示的目标IP转化为IP地址, 并写入端口号, serverPort, :

     
  4. 将装入数据的DatagramPacket数据报发送, 使用DatagramSocket的send方法, 传入requstPacket作为参数, 
  5. 然后再创建一个DatagramPacket用来接收服务器的响应数据报:
  6. 把接收到的数据报响应打印出来, 首先需要构造解析出String数据, 并打印:

        对于客户端, 在构造的时候, 需要传入IP地址是127.0.0.1是因为我们的客户端和服务器是在一个主机上的, 这种UDP数据报流是完全可以跨主机实现的, 只不过这里只是演示这个UDP数据报的传输过程

客户端完整代码

package network;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    // 客户端启动, 需要知道服务器在哪里!!
    public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
        // 对于客户端来说, 不需要显示关联端口.
        // 不代表没有端口, 而是系统自动分配了个空闲的端口.
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

    public void start() throws IOException {
        // 通过这个客户端可以多次和服务器进行交互.
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 1. 先从控制台, 读取一个字符串过来
            //    先打印一个提示符, 提示用户要输入内容
            System.out.print("-> ");
            String request = scanner.next();
            // 2. 把字符串构造成 UDP packet, 并进行发送.
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIP), serverPort);
            socket.send(requestPacket);
            // 3. 客户端尝试读取服务器返回的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            // 4. 把响应数据转换成 String 显示出来.
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.printf("req: %s, resp: %s\n", request, response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);
        // UdpEchoClient udpEchoClient = new UdpEchoClient("42.192.83.143", 9090);
        udpEchoClient.start();
    }
}

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

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

相关文章

认识系统总线

目录 一、总线的基本概念 1.总线的定义 二、总线的分类 1.片内总线 2.系统总线 2.1数据总线 2.2地址总线 2.3控制总线 3.通信总线(通信总线) 三、总线特性及性能指标 1.总线特征 2.性能标准 2.1总线的传输周期(总线周期) 2.2总线时钟周期 2.3总线的工作频率 2.4…

vim编辑器命令模式——撤销与时间旅行

Vi介绍 Vi 编辑器是所有 Unix 及 Linux 系统下标准的编辑器,类似于 windows 系统下的 notepad (记事本)编辑器,由于在 Unix 及 Linux 系统的任何版本,Vi 编辑器是完全相同的,因此可以在其他任何介绍 vi 的地…

小红书数据,如何在垂类赛道中脱颖而出!

导语 近年来,泛娱乐内容在小红书平台盛行,面临流量见顶的情况。这时候,垂类账号的优势就显现出来,不仅可以规避激烈的竞争,还能去获取更精准的流量。 作为一个经久不衰的创作方向,美食赛道分化出教程、测…

webpack plugin原理以及自定义plugin

通过插件我们可以拓展webpack,加入自定义的构建行为,使webpack可以执行更广泛的任务。 plugin工作原理: webpack工作就像是生产流水线,要通过一系列处理流程后才能将源文件转为输出结果,在不同阶段做不同的事&#x…

核磁机器学习 | 基于机器学习的fMRI分类

导读 本文通过提取最显著的特征,将大脑图像分类为正常和异常,并对大脑各种状态的磁共振成像(MRI)进行了研究。本文描述了一种基于小波变换的方法,首先对图像进行分解,然后使用各种特征选择算法从MRI图像中提取最显著的大脑特征。…

[Netty] 面试问题 1 (十八)

文章目录 1.Netty的特点2.Netty应用场景3. Netty核心组件4.Netty的线程模型5. EventloopGroup和EventLoop6.Netty 的零拷贝7.Netty 长连接和心跳机制8.Netty 服务端和客户端的启动过程9.Netty 的 Channel 和 EventLoop10.Netty 的 ChannelPipeline11.Netty 中的 ByteBuf12.Nett…

数据分析01——Anaconda安装/Anaconda中的pip换源/jupyter配置

0、前言: 数据分析三大模块知识:numpy(数组计算)、pandas(基于numpy开发,用于数据清洗和数据分析)、matplotlib(实现数据可视化) 1、Anaconda安装: 安装Ana…

Spring常见面试题总结(2023最新版)

文章目录 1、谈谈你对Spring的理解?1.1 发展历程1.2 Spirng的组成1.3 Spring的好处 2、Autowired和Resource的区别2.1 共同点:2.2 Autowired2.3 Resource2.3.1 Resource的装配顺序 3、Spring常用注解3.1、给容器中注入组件3.1.1 包扫描组件标注注解3.1.2…

Faster-RCNN跑自己的数据集(详细过程)FPN学习

1、下载b站 :霹雳吧啦Wz 的代码 github链接:https://github.com/WZMIAOMIAO/deep-learning-for-image-processing 对应视频链接:2-自定义DataSet_哔哩哔哩_bilibili 2、配置环境,安装相应的包。 或者如果有报错可以直接更新重新…

数据结构之栈的详解

文章目录 一.什么是栈二. 栈的使用2.1栈的基本操作2.2 栈的基本使用 三.栈的实现3.1 数组实现栈的方式3.2 链式栈的实现 四.栈的应用4.1 括号匹配4.2 逆波兰表达式求值什么是逆波兰表达式 4.3 出栈入栈次序匹配4.4 最小栈五.总结 一.什么是栈 栈是一种先入后出(FILO)的线性表数…

【刷题笔记】结构体内存对齐举例+统计回文

一、结构体内存对齐举例 题目: 下面存在两个结构体: struct One {double d;char c;int i; } struct Two {char c;double d;int i; } 在#pragma pack(4)和#pragma pack(8)的情况下,结构体的大小分别是? 分析: C/C中结构…

mysql8.0性能对比以及新特性

MySQL8.0 性能测试与新特性介绍 性能对比 测试内容 测试mysql5.7和mysql8.0 分别在读写、只读、只写模式((oltp_read_write,oltp_read_only,oltp_write_only))下不同并发时的性能(tps&#x…

《微服务实战》 第一章 Java线程池技术应用

前言 介绍Java的线程、线程池等操作 1、Java创建线程方式回顾 1.1、继承Thread类(只运行一次) public class ThreadTest extends Thread{Overridepublic void run() {System.out.println(Thread.currentThread().getName());}public static void main(String[] args) {new …

【python】keras包:深度学习( MLP多层感知器 Multi-Layer Perceptron)

MLP多层感知器 Multi-Layer Perceptron Part 1. 算法逻辑 实现经典问题——如何通过图像区分猫和狗 神经网络:建立模型,模仿人的思考机制 将“机器学习_逻辑回归”按照神经元的逻辑,组成逻辑网络。 解释: 假设自变量x[]和应变…

档案库房建设需要遵守的一些规定

各单位在建设档案室时需要对照《机关档案管理规定》《档案馆建筑设计规范》关于档案库房的相关标准,对库房的位置、面积、承重、安全等方面进行全面考虑,建设符合国家规定的档案库房。 档案库房建设需要遵守什么规定? 一、《机关档案管理规定…

Transformer的位置编码

1. 什么是位置编码,为什么要使用位置编码 简单来说位置编码就是给一个句子中的每个token一个位置信息,通过位置编码可以明确token的前后顺序关系。 对任何语言来说,句子中词汇的顺序和位置都是非常重要的。它们定义了语法,从而定…

DP(9)--插头DP

DP(9)--插头DP /* Mondriaan’s Dream题目大意:在 N*M 的棋盘内铺满 1*2 或 2*1 的多米诺骨牌,求方案数。 砖只有横放和竖放两种状态,把横放记为两个0,竖放记为上1下0,逐格DP,每次无论前一格…

Kali Linux 配置动态/静态 IP

[笔者系统版本] [Kali]: Kali Linux 2023.1 [Kernel]: kernel 6.1.0 [Desktop]: Xfce 4.18.1 1. Kali Linux 配置动态 IP (1). 首先查看网卡接口名称。 (2). 编辑网络接口配置文件。 (3). 网络接口配置文件的默认内容是这样的。 (4). 新增配置内容如下; 指定网卡…

ChatGPT :十几个国内免费可用 ChatGPT 网页版

前言 ChatGPT(全名:Chat Generative Pre-trained Transformer),美国OpenAI 研发的聊天机器人程序 ,于2022年11月30日发布 。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言…

浅析智慧充电桩云平台的技术设计方案

自从我国提出“新基建”以来,充电基础设施产业也成为行业的话题与关注焦点。据数据统计,2021年,中国新能源汽车保有量达到784万辆,预计2025年,中国新能源汽车保有量达到2672万辆,2025年充电桩数量将达到654…