阿里云开发者社区

电脑版
提示:原网页已由神马搜索转码, 内容由developer.aliyun.com提供.

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

2024-05-2368
版权
版权声明:
本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议》和 《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
简介:编写UDP版本的客户-服务器程序(echo server 和 echo client)


前言概要

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

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

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

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

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

     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(intport) : 创建一个UDP数据报套接字, 绑定端口号为port. 一般用于服务器端

     其类方法如下:

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

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

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

DatagramPacket:

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

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

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

     其类方法如下:

InetAddressgetAddress() : 从接收的数据报中,获取发送端主机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();    }}

Socket.close()

     上面这两个客户端和服务器, 均没有使用close方法, 我们说. 套接字在没有使用之后, 需要使用close方法来释放资源. 但是为什么我们这里没有使用close方法来关闭呢?

     从另外一个角度来说, 对于我们上述这个代码案例来说, 接收端和发送端的都是while(true)循环在一直运行, 并没有结束运行的标志, 对于我们这个socket套接字来说, 一般是可以确定不再使用的时候, 才需要去调用close方法. 此处的一直在循环调用, 没有结束的意思, 所以此处不需要使用close方法

     上述服务器, 客户端是一个简单的回显程序, 我们来设计一个有基本业务逻辑的程序, 也就是具有简单的单词查询功能的服务器:


UDP协议传输案例(简单的业务逻辑)

     也就是, 我输入一个单词dog, 他就会响应出他的中文, 小狗这样的程序:

     首先我们来初步分析一下, 作为客户端, 只需要发送dog这个请求给服务器就行了, 对于服务器, 需要接收这个数据并处理响应给客户端

     所以我们新建一个服务器, 让他继承我们之前所写的UdpEchoServer, 然后具体实现查询单词的业务即可.

     数据结构的选择, 由于是属于英文就可以查到与之对应的中文, 所以我们使用Map来存储这类键值数据:

     处理的过程也就是重写process的过程, 查找, 也就是在这个map中进行查找.

完整代码:

package network; import java.io.IOException;import java.net.SocketException;import java.util.HashMap;import java.util.Map; // 使用继承, 是为了复用之前的代码.public class UdpDictServer extends network.UdpEchoServer {    private Map<String, String> dict = new HashMap<>();     public UdpDictServer(int port) throws SocketException {        super(port);         dict.put("dog", "小狗");        dict.put("cat", "小猫");        dict.put("fuck", "卧槽");        // ...........     }     @Override    public String process(String request) {        return dict.getOrDefault(request, "该单词没有查到!");    }     public static void main(String[] args) throws IOException {        UdpDictServer udpDictServer = new UdpDictServer(9090);        udpDictServer.start();    }}


目录
相关文章
|
3月前
|
监控关系型数据库MySQL
|
30天前
|
安全C#
【Azure 应用服务】在安全漏洞扫描中发现有泄露服务器IIS版本的情况,如何实现屏蔽服务版本号信息呢?
【Azure 应用服务】在安全漏洞扫描中发现有泄露服务器IIS版本的情况,如何实现屏蔽服务版本号信息呢?
|
2月前
|
弹性计算云计算
云服务器 ECS产品使用问题之如何更新游戏服务端版本
云服务器ECS(Elastic Compute Service)是各大云服务商阿里云提供的一种基础云计算服务,它允许用户租用云端计算资源来部署和运行各种应用程序。以下是一个关于如何使用ECS产品的综合指南。
|
1月前
|
网络安全数据安全/隐私保护iOS开发
【Mac os】如何在服务器上启动Jupyter notebook并在本地浏览器Web端环境编辑程序
本文介绍了如何在服务器上启动Jupyter Notebook并通过SSH隧道在本地浏览器中访问和编辑程序的详细步骤,包括服务器端Jupyter的启动命令、本地终端的SSH隧道建立方法以及在浏览器中访问Jupyter Notebook的流程。
|
2月前
|
缓存弹性计算数据库
阿里云2核4G服务器支持多少人在线?程序效率、并发数、内存CPU性能、公网带宽多因素
2核4G云服务器支持的在线人数取决于多种因素:应用效率、并发数、内存、CPU、带宽、数据库性能、缓存策略、CDN和OSS使用,以及用户行为和系统优化。阿里云的ECS u1实例2核4G配置,适合轻量级应用,实际并发量需结合具体业务测试。
5000
阿里云2核4G服务器支持多少人在线?程序效率、并发数、内存CPU性能、公网带宽多因素
|
2月前
|
存储应用服务中间件文件存储
Ngnix服务器版本升级需求分析,如何不停止Ngnix服务进行升级
Ngnix服务器版本升级需求分析,如何不停止Ngnix服务进行升级
|
2月前
|
网络协议网络架构
【网络编程入门】TCP与UDP通信实战:从零构建服务器与客户端对话(附简易源码,新手友好!)
在了解他们之前我们首先要知道网络模型,它分为两种,一种是OSI,一种是TCP/IP,当然他们的模型图是不同的,如下
|
3月前
|
JavaScriptJava应用服务中间件
打包前后端程序并在阿里云服务器上部署,只需几步就能实现!
打包前后端程序并在阿里云服务器上部署,只需几步就能实现!
|
3月前
|
网络协议
UDP服务器的并发方案
UDP服务器的并发方案
4400
|
1月前
|
消息中间件网络协议算法
UDP 和 TCP 哪个更好?
【8月更文挑战第23天】
8300

热门文章

最新文章

  • 1
    如何解决使用若依前后端分离打包部署到服务器上后主包无法找到从包中的文件的问题?如何在 Java 代码中访问 jar 包中的资源文件?
    424
  • 2
    ECS使用体验的文章
    41
  • 3
    Windows安装TortoiseSVN客户端结合Cpolar实现公网提交文件到本地服务器
    72
  • 4
    阿里云服务器CPU内存配置详细指南,如何选择合适云服务器配置?
    812
  • 5
    阿里云服务器配置怎么选择合适?收藏级教程大家参考下
    188
  • 6
    阿里云服务器NVMe SSD本地盘和SATA HDD本地盘详解
    355
  • 7
    阿里云服务器付费模式包年包月、按量付费、抢占式实例选择说明
    351
  • 8
    TortoiseSVN客户端如何安装配置并实现公网访问服务端提交文件到本地服务器
    64
  • 9
    使用Serv-U FTP服务器共享文件,实现无公网IP环境下远程访问-2
    96
  • 10
    使用Serv-U FTP服务器共享文件,实现无公网IP环境下远程访问-1
    93