回显服务器(基于UDP)

回显服务器(基于UDP),第1张

目录

基本概念

API学习

DatagramSocket

DatagramPacket

InetSocketAddress

回显服务器实现

服务端

思路分析

具体实现

完整代码

客户端

思路分析

具体实现 

完整代码

运行测试


基本概念

发送端和接收端

在一次网络数据传输时:

发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机

接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机

发送端和接收端是相对的

回显服务器(基于UDP),第2张

请求和响应

一般来说,获取一个网络资源,涉及到两次网络数据传输:

第一次:请求数据的发送

第二次:响应数据的发送

回显服务器(基于UDP),第3张

服务端和客户端

服务端:在常见的网络数据传输场景下,将提供服务的一方进程,称为服务端,可以提供对外服务

客户端:获取服务的一方进程,称为客户端

对于服务来说,一般是提供:

客户端获取服务资源:

回显服务器(基于UDP),第4张

        客户端保存资源在服务端:

回显服务器(基于UDP),第5张

常见的客户端服务端模型:

最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:

(1)客户端先发送请求到服务端

(2)服务端根据请求数据,执行相应的业务处理

(3)服务端返回响应:发送业务处理结果

(4)客户端根据响应数据,展示处理结果(展示获取的资源,或是提示保存资源的处理结果)

回显服务器(基于UDP),第6张

API学习

DatagramSocket

DatagramSocket是UDP Socket,用于发送和接收UDP数据报

构造方法:

方法说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)

常用方法:

方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)从此套接字发送数据包(不会阻塞等待,直接发送)
void close关闭此数据报套接字

DatagramPacket

DatagramPacket是UDP Socket发送和接收的数据报

构造方法:

方法说明
DatagramPacket(byte[] buf, int length)构造一个DatagramPacket用来接收数据报,接收的数据保存在字节数组(buf)中,接收指定长度(length)
DatagramPacket(byte[] buf, int length, SocketAddress address)构造一个DatagramPacket用来发送数据报,发送的数据在字节数组(buf),发送指定长度(length),address指定目的主机的IP地址和端口号
DatagramPacket(byte[] buf, int int length, InentAddress, int port)构造一个DatagramPacket用来发送数据报,发送的数据保存在字节数组(buf),发送指定长度(length)到address主机的port端口

 常用方法:

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

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

InetSocketAddress

InetSocketAddress(SocketAddress的子类)构造方法:

方法说明
InetSocketAddress(InetAddress addr, int port)创建一个Socket地址,包含IP地址和端口号

回显服务器实现

回显服务器的业务逻辑较为简单,通过编写回显服务器,我们能够进一步熟悉相关API和编写流程

服务器接收客户端的请求,返回响应,响应的内容是客户端发送的请求内容(即客户端发什么,服务端响应什么)

其中,涉及到客户端和服务端,我们先实现服务端相关逻辑

服务端

思路分析

对于服务器,要实现的内容有:

1. 接收客户端发送的请求

2. 读取解析请求,并根据请求计算响应

3. 将响应返回给客户端

在实现服务器过程中我们需要注意的是,由于服务器要等到客户端发送请求时才能进行接收、解析、计算响应等 *** 作,而服务器不知道客户端什么时候发送请求,因此服务器需要一直“待命”,等待客户端发送请求

具体实现

首先我们需要创建一个 DatagramSocket 对象用于接收UDP数据报,并通过构造方法来指定服务器要绑定的端口号

import java.net.DatagramSocket; import java.net.SocketException;public class UdpEchoServer { DatagramSocket socket = null; //创建 DatagramSocket 对象用于发送和接收UDP数据报 public UdpEchoServer(int port) throws SocketException { socket = new DatagramSocket(port); } }

接下来,我们实现服务器的启动,接收客户端发送的请求并解析

//启动服务器 public void start() throws IOException { System.out.println("启动服务器"); while (true) {//服务器需要长期、返回地接收请求、处理请求 // 接收请求并解析 //创建 DatagramPacket 对象用于接收客户端发送的数据报 DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); //接收请求数据报 socket.receive(requestPacket); //将读取到的字节数组转换成String 方便后续逻辑处理 String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); } }

其中的receive()能够读取到一个UDP数据报,并将其放到requestPacket中,其中,UDP数据报的载荷部分被放到requestPacket内置的字节数组中,而报头部分,则会被requestPacket的其他属性保存。在执行到receive()时,若还没有客户端发来请求,则会先阻塞等待,直到接收到客户端发送的请求

接下来,根据请求计算响应,对于回显服务器,这一步非常简单,只需要将请求内容直接作为响应内容即可

//启动服务器 public void start() throws IOException { System.out.println("启动服务器"); while (true) {//服务器需要长期、反复地接收请求、处理请求 // 接收请求并解析 //创建 DatagramPacket 对象用于接收客户端发送的数据报 DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); //接收请求数据报 socket.receive(requestPacket); //将读取到的字节数组转换成string 方便后续逻辑处理 String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); //根据请求计算响应 String response = process(request); } } public String process(String request){ return request; }

基于字节数组构造出String,字节数组中保存的内容不一定是二进制数据,也可能是文件数据,而String既可以保存文本数据也可以保存二进制数据

最后,我们将响应返回到客户端

//将响应返回给客户端 //构造 DatagramPacket 对象用于存储向客户端发送响应内容 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress()); //此时需要传入发送数据的字节数组、数据长度和发送请求的客户端的IP地址和端口号 //发送响应数据报 socket.send(requestPacket);

 在发送请求时,将请求中的源IP和源端口号,作为响应的目的IP和目的端口号,此时就能够将消息返回给客户端

为了方便观察程序的执行效果,我们可以打印日志

完整代码

import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException;public class UdpEchoServer { DatagramSocket socket = null;//创建 DatagramSocket 对象用于发送和接收UDP数据报 public UdpEchoServer(int port) throws SocketException { socket = new DatagramSocket(port); } //启动服务器 public void start() throws IOException { System.out.println("启动服务器"); while (true) {//服务器需要长期、反复地接收请求、处理请求 // 接收请求并解析 //创建 DatagramPacket 对象用于接收客户端发送的数据报 DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); //接收请求数据报 socket.receive(requestPacket); //将读取到的字节数组转换成string 方便后续逻辑处理 String request = new String(requestPacket.getData(), 0, requestPacket.getLength()); //根据请求计算响应 String response = process(request); //将响应返回给客户端 //构造 DatagramPacket 对象用于存储向客户端发送响应内容 DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress()); //此时需要传入发送数据的字节数组、数据长度和发送请求的客户端的IP地址和端口号 //发送响应数据报 socket.send(requestPacket); //打印日志 System.out.printf("[%s:%d] req: %s, resp: %s\n", responsePacket.getAddress().toString(), responsePacket.getPort(), request, response); } } public String process(String request){ return request; } }

接下来,我们运行服务器:

public static void main(String[] args) throws IOException { UdpEchoServer server = new UdpEchoServer(9010); server.start(); }

由于服务器要等待客户端发送请求后才能执行后续 *** 作,因此,我们先实现客户端后再来观察效果

实现服务器后,我们接着实现客户端相关 *** 作

客户端

思路分析

对于服务端要实现的内容有:

1. 从控制台读取用户输入的内容

2. 将内容构造成UDP请求,并发送给服务器

3. 等待服务器响应,当接收到服务器响应时,解析响应内容

4. 将响应内容显示到屏幕上

具体实现 

首先,我们要创建一个 DatagramSocket 对象用于接收UDP数据报,但此时不需要手动指定端口号

import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException;public class UdpEchoClient { private DatagramSocket socket = null; private String serverIp;//服务器IP private int serverPort;//服务器端口号 public UdpEchoClient(String serverIp, int serverPort) throws SocketException { this.serverIp = serverIp; this.serverPort = serverPort; socket = new DatagramSocket(); } }

由于我们要知道服务器的IP地址和端口才能向其发送请求,因此,在这里我们通过构造方法来指定服务器的IP和端口号 

为什么服务器需要指定端口号,而客户端则是不需要?

 对于服务器,需要手动指定端口,对于客户端,则一般不用手动指定,此时系统会自动分配一个空闲的端口号。这是因为对于客户端来说,无法保证手动指定的端口是可用的,因此由系统随机分配一个空闲的端口号,而对于服务器端口来说,我们事先知道服务器上有哪些端口被使用,因此我们可以确保指定的端口未被占用,此外,客户端要主动给服务器发起请求,通过服务器端口号找到服务器,若服务器端口号也随机分配,则客户端不能找到服务器

接下来我们启动服务器,并从控制台读取要发送的请求数据

//启动服务器 public void start(){ System.out.println("启动客户端"); Scanner scanner = new Scanner(System.in); while (true){ System.out.print("请输入:"); if(!scanner.hasNext()){ break; } String request = scanner.next(); } }

接下来,我们由输入的内容构造请求并发送

//启动服务器 public void start() throws IOException { System.out.println("启动客户端"); Scanner scanner = new Scanner(System.in); while (true){ System.out.print("请输入:"); if(!scanner.hasNext()){ break; } String request = scanner.next(); //构造请求并发送 DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp), serverPort); socket.send(requestPacket); } }

由于数据报需要进行发送,因此需要有目的主机的IP地址和端口号,而构造方法中传入的IP地址是字符串类型,且是点分十进制风格的(例如“127.0.0.1”),因此我们需要使用 InetAddress.getByName 创建InetAddress对象

然后我们需要读取服务器返回的响应并显示

//读取服务器返回的响应 DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096); socket.receive(responsePacket); //将响应显示到控制台上 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", 9010); client.start(); }

完整代码

import java.io.IOException; import java.net.*; import java.util.Scanner;public class UdpEchoClient { private DatagramSocket socket = null; private String serverIp;//服务器IP private int serverPort;//服务器端口号 public UdpEchoClient(String serverIp, int serverPort) throws SocketException { this.serverIp = serverIp; this.serverPort = serverPort; socket = new DatagramSocket(); } //启动服务器 public void start() throws IOException { System.out.println("启动客户端"); Scanner scanner = new Scanner(System.in); while (true){ System.out.print("请输入:"); if(!scanner.hasNext()){ break; } String request = scanner.next(); //构造请求并发送 DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIp), serverPort); socket.send(requestPacket); //读取服务器返回的响应 DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096); socket.receive(responsePacket); //将响应显示到控制台上 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", 9010); client.start(); } }

运行测试

此时,我们同时运行服务器和客户端,并输入请求观察代码是否存在问题:

客户端:

回显服务器(基于UDP),第7张

服务器:

回显服务器(基于UDP),第8张 此时我们看到,客户端输入的请求后响应成功显示,且服务器记录日志正确

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zz/13518745.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2024-02-27
下一篇 2024-03-02

发表评论

登录后才能评论

评论列表(0条)

保存