Java网络编程(2):(socket API编程:UDP协议的 socket API -- 回显程序)
Java网络编程(2):(socket API编程:UDP协议的 socket API – 回显程序)
文章目录
- Java网络编程(2):(socket API编程:UDP协议的 socket API -- 回显程序)
- 1. socket API 有两套编码方式
- 2. UDP 的 socket API
- 2.1 网卡 和 socket文件
- 2.2 DatagramSocket 和 DatagramPacket和的作用
- 2.3 DatagramSocket 提供的方法
- 2.3.1 构造方法(打开 socket 文件)
- 2.3.1 读写操作
- 2.3.3 关闭 socket 文件
- 2.4 DatagramPacket 提供的方法
- 2.4.1 构造方法(实例化一个数据报)
- 2.4.2 提供获取信息的方法
- 3. 使用UDP协议的 socket API 编写服务器端程序
- 响应和请求的介绍
- 3.1 创建 socket 对象(DatagramSocket 对象)
- 3.2 创建 start 方法,用来启动服务器
- 3.3 编写一个死循环,持续读取请求数据
- 3.4 处理请求数据(核心工作)
- 3.5 根据三个步骤,编写服务器端程序
- 4. 使用UDP协议的 socket API 编写客户端程序
- 5. 启动客户端 和 服务器端,查看运行效果
- 两个程序的运行,结合起来看
- 存在的疑问
- 1. 是否需要联网才能运行程序?
- 2. 能否让你的电脑运行客户端,连接我这个电脑上的服务器?
- 3. 遗留问题
- 6. 完整代码:
- 6.1 服务器代码:
- 6.2 客户端代码:
- 总结:
在看这篇博客之前,如果你对 socket API 有一定了解,可以直接看这篇博客。
如果没有太多了解,需要你看完这篇博客: Java网络编程(1):(使用 socket 进行网络编程前的预备知识)
注意:我们这篇博客,介绍的是如何编写服务器端的程序,所以,文章中提到的 数据报 或 数据包,我可能会写成这两种,但它们都是同一个东西,不用太纠结!!!
非要问他们有什么具体区别?
你可以看看这篇博客:Java网络初识(4):网络数据通信的基本流程 的 1.4 标题中,关于网络传输的基本数据单位的介绍。
再过来看本篇博客!
1. socket API 有两套编码方式
socket API 本身是操作系统的功能,在 JDK中,针对 socket API 进行了进一步的封装。所以,我们拿着这套封装好的 socket API 进行网络编程就可以了。
之前的博客介绍过,我们编写的程序,想要进行网络通信,就要把我们写的程序交给传输层,而我们要使用操作系统提供的 socket API ,让传输层接收数据,完成数据传递的过程。
而传输层中,有两个最核心的协议:TCP 和 UDP。
这两个协议的差别是非常大的,所以,传输层给应用层提供 socket API 的时候,也是提供了两套不一样的编写代码的方式:
- UDP的 socket API
- TCP的 socket API
接下来,我们就分别来讲这两组编码方式。
本篇博客,介绍的是第一组编码方式:UDP的 socket API
2. UDP 的 socket API
UDP 的 socket API 涉及到两个最核心的类:DatagramSocket 和 DatagramPacket 。
2.1 网卡 和 socket文件
讲这两个类之前,我们先来铺垫一些知识。
之前 Java 文件操作我们说过,计算机中的 “ 文件 ”,通常是一个 “ 广义 ” 的概念,而针对文件IO的操作,是 “ 狭义 ” 的概念,指的是硬盘上的文件。
除此之外,文件,还可以代指一些硬件设备,换句话说:操作系统管理硬件设备,也是抽象成文件,统一管理的。
那么,我们网络通信会用到什么硬件设备?
答:网卡。
网卡在实际中,又有 有线网卡和无线网卡。
有线网卡,一般是集成在主板上的,不好观察。
无线网卡,一般是独立出来的,比较明显。
网卡的功能和作用如下:
网卡作为计算机与计算机间进行通信的桥梁,主要有以下两大功能:
一、是读入由网络设备传输过来的数据包,经过拆包,将其变成计算机可以识别的数据,并将数据传输到所需设备中
二、是将计算机发送的数据,打包后输送到其他网络设备中
那么,网卡这个硬件设备,在操作系统中,就被抽象成 “ socket文件 ”,既然是抽象成文件,那么,我们在操作网卡的时候,就是在操作 socket文件 ,操作的过程,和操作普通文件差不多,都是:打开 —> 读写 —> 关闭
为什么会有这样的设定?
答:网卡是个硬件设备,直接操作网卡,不好操作,所以把操作网卡转换成操作 socket 文件,socket 文件就相当于 “ 网卡的遥控器 ”。
DatagramSocket 这个类,可以当作是用来表示网卡的文件,通过它,来操作网卡,前面加的 “ Datagram ”,表示它通过UDP这个协议来进行网络通信。
2.2 DatagramSocket 和 DatagramPacket和的作用
这两个类的作用,是什么?
DatagramSocket :是 UDP Socket,用于发送和接收UDP数据包,它是用来操作网卡的。
DatagramPacket :是 UDP Socket 发送和接收的数据包,它是用来创建(实例化)一个完整的UDP数据包的。
2.3 DatagramSocket 提供的方法
2.3.1 构造方法(打开 socket 文件)
注意:我们这里这个表格显示的构造方法,不是全部的构造方法,不完整!!!
这里提供的构造方法,构造方法的作用:相当于打开文件(socket文件)
打开文件,是读写网络数据的前提条件。
方法名 | 方法说明 |
---|---|
DatagramSocket() | 创建⼀个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端) |
DatagramSocket(int port) | 创建⼀个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端) |
表格中的第二个方法,int port
,指的是端口号,需要手动指定。
当我们创建 socket 的时候,传入一个端口号,就会关联上一个端口号。
端口号的作用:使用端口号,来区分同一台计算机(主机)上不同的应用程序。
换句话说:只要你这个程序,需要操作网络,在这之前,需要你把这个程序本身,和一个端口号建立关联关系,而且这个端口号,不能和其他的程序所关联的端口号相同,也就是,一个端口号不能重复使用。
DatagramSocket(),这个构造方法,会随机分配一个端口(常用于客户端)。
DatagramSocket(int port),带有参数的构造方法,要手动指定一个端口(常用于服务器端)。
为什么一个常用于客户端?一个常用于服务器端?
编写 客户端 程序的时候,会解释到。
为什么要有端口?
可以看这篇博客:Java网络初识(2):IP地址和端口号,协议,五元组
2.3.1 读写操作
这里提供读写操作的方法,和文件操作中的read 和 write 不一样,这里提供的是 receive(接收,相当于读取数据) 和 send(发送,相当于写数据)。
方法名 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
从以上两个方法的参数我们可以知道,参数都是 DatagramPacket 这个类实例化的对象,也就是说,我们进行网络通信的时候,是通过 DatagramPacket 这个类,实例化出一个一个的数据报,以数据报为单位,进行接收和发送。
2.3.3 关闭 socket 文件
方法名 | 方法说明 |
---|---|
void close() | 关闭此数据报套接字 |
2.4 DatagramPacket 提供的方法
2.4.1 构造方法(实例化一个数据报)
注意:我们这里这个表格显示的构造方法,不是全部的构造方法,不完整!!!
通过 DatagramPacket 这个类提供的构造方法,我们可以实例化出一个完整的数据报。
方法名 | 方法说明 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个 DatagramPacket 对象以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length) |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) | 构造一个 DatagramPacket 对象以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第⼆个参数length)。address指定目的主机的IP和端口号 |
UDP 数据包的载荷数据,就可以通过构造方法中的byte[] buf
来指定并存储。
2.4.2 提供获取信息的方法
不论是服务器端还是客户端,通过 DatagramPacket 类实例化出的对象(一个数据包),我们总会获取到这个对象(数据包)的某些信息,如:
- 发送端主机IP地址
- 接收端主机IP地址
- 发送端主机的端口号
- 接收端主机端口号
- 数据报中的数据(字节数组里存着的信息)
为了获取这些信息,DatagramPacket 这个类,也提供了一些方法,让我们可以获取到以上这些信息。
方法名 | 方法说明 |
---|---|
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;从发送的数据报中,获取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
3. 使用UDP协议的 socket API 编写服务器端程序
这里编写的是 UDP协议 下,编写一个服务器端的网络编程代码,这个代码中,会用到第二个大标题中,介绍到的所有类和类提供的方法。
创建过程:打开 IDEA —> 新建项目 —> 在 src 目录下创建一个包(network包)—> 包里面,创建两个类:UdpEchoServer 和 UdpEchoClient。
解释一下这两个类的类名的意思:
Server:这个单词的中文意思:服务器
Cilent:这个单词的中文意思:客户端
Echo:这个单词的中文意思:回声,回显
EchoServer 就叫做回显服务器,什么意思?
响应和请求的介绍
讲解意思之前,还需要介绍一下请求和响应:
请求:客户端 给 服务器端 发送一个数据,叫做请求
响应:服务器端 返回给 客户端 一个数据,叫做响应
回显服务器的意思就是说:请求是什么,响应就是什么。
比如:请求是 “ hello ” 这么一个字符串,响应同样是 “ hello ” 。
所以,我们这里设计的程序,就是要达成这样的效果!
但是真实情况中,真正的服务器,请求和响应肯定是不一样的,我们这里,不去理会服务器的复杂逻辑,主要是学习 UDP 的 socket API 的初步使用,因此,这里就写一个最简单的回显。
回显类似于什么呢?
有点像鹦鹉学舌,就你说什么,鹦鹉就说什么,模仿你说话。
3.1 创建 socket 对象(DatagramSocket 对象)
创建 socket对象,为的是调用 DatagramSocket 提供的方法。
图中可以看到:
- 关于网络编程的类,都在 net 包里面
- DatagramSocket 这个类的构造方法,需要处理 SocketException 这个异常,抛出到方法处即可
- UdpEchoServer类的构造方法,初始化socket对象,初始化的时候,指定了一个固定的端口号,让服务器来使用
经过以上三步,我们就创建好了一个 socket对象。
3.2 创建 start 方法,用来启动服务器
3.3 编写一个死循环,持续读取请求数据
对于服务器来说,客户端什么时候发请求,发送多少个请求,我们是无法预测的。
例如:
你玩游戏(手机里的游戏APP,就是客户端),游戏对应的服务器端(也就是服务器),是不知道你什么时候登录游戏,发送一个登录游戏的请求的,也不知道,在一个小时内,有多少个玩家发送了多少个登录请求,游戏公司无法预测,所以,游戏的服务器,是 7 X 24 小时,不断工作的,只有游戏版本更新,系统维护的时候,服务器才会关闭。
服务器关闭了,玩家登录游戏的时候,发送的登录请求,服务器关闭了,无法作出响应,你就登录不了游戏了。
只有更新结束,游戏系统维护好了以后,才能正常玩游戏。
通过上述例子可知:服务器端程序(服务器)通常都需要有一个 死循环,持续不断的尝试读取客户端的请求数据,做到 7 X 24小时运行。
进入循环后,意味着服务器要开始不断的处理客户端的请求数据了。
3.4 处理请求数据(核心工作)
处理请求的过程,典型的服务器都是分为三个步骤的,分别是:
- 读取请求,并解析请求数据
- 根据请求,计算响应(服务器最关键的逻辑)
本身服务器就是要提供各种各样的功能的,比如:百度搜索 “ 如何学习好Java ”,我的请求是:“ 如何学习好Java ”,服务器端的程序就会根据请求,搜索出很多个相关的页面。
说起来很简短,但是,在服务器端程序上,会经过各种各样的逻辑处理,处理请求,计算响应,最终返回结果。
所以,这一块是服务器最关键的逻辑。
但是,我们这篇博客编写的是回显服务器,所以,这个环节就相当于省略了,请求是什么,我们就返回什么。
- 把响应返回给客户端
所以,一个典型的服务器,都是按照这三个步骤来进行处理的,不仅是我们编写的这个回显服务器,未来,你从事编写服务器端程序(服务器)相关的工作岗位,涉及到更加复杂的,商业级别的服务器,总体上的逻辑,也就是这三步。
只不过,公司的服务器端程序,会考虑的更多,支持的功能会更多,会很复杂,上至几十万行的代码量。
所以,清楚了这三步基本的步骤,我们就可以开始真正编写服务器了。
3.5 根据三个步骤,编写服务器端程序
由于本篇博客,加上这第五步的全部内容,达到 14000+ 字数,所以,我单独写到了另一篇博客当中:
Java网络编程:(socket API编程:UDP协议的 socket API – 服务器端程序的编写)
大家点进去,看完这篇博客,再回来看这篇博客!
4. 使用UDP协议的 socket API 编写客户端程序
同样,为了避免本篇博客字数太多,编写客户端的介绍,我也分离出了一篇博客出来:
Java网络编程:(socket API编程:UDP协议的 socket API – 客户端程序的编写)
大家点进去,看完这篇博客,再回来看这篇博客!
5. 启动客户端 和 服务器端,查看运行效果
客户端运行效果:
服务器端运行效果:
这就是一整个回显程序的运行效果了。
两个程序的运行,结合起来看
- 客户端发送请求数据,服务器端当前处于 阻塞等待 中
- 服务器端接收请求数据包,并解析
- 计算响应后,返回响应数据给 客户端
- 客户端接收响应数据
所以,客户端 和 服务器端 要相互配合,才能完成一个网络数据请求的工作。
存在的疑问
1. 是否需要联网才能运行程序?
这个回显程序,是否需要我们联网才能运行呢?
答:如果是在 同一个 主机上,就不需要联网,也能运行程序。
如果是 不同 主机的话,就需要连接网络了。
2. 能否让你的电脑运行客户端,连接我这个电脑上的服务器?
你希望,用你自己的电脑,运行我刚才实现的客户端程序,同时连接我的服务器,你发送请求,我这边响应数据,这样的方式,能不能实现?
答:可以,但是又不可以!!!
原因:想要实现这样的方式,有很多限制,比如:你电脑要和我连接同一个 WiFi 才可以,即咱们两个要处于同一个局域网。
如果你连接的是你家里的 / 学校的,就不可以连接我的这个服务器。
为啥要这样?原因后面博客会讲解到(NAT机制)。
NAT机制(网络地址映射),导致我们要处于同一个局域网,才能实现:你运行客户端,连接我的服务器端。
但是,我们还可以用另一种方式解决:云服务器!
我可以把程序部署到云服务器上,你就可以访问到云服务器,连接到服务器了。
那么,这个操作,后续会讲到。
3. 遗留问题
- NAT机制(网络地址映射)
- 部署云服务器(网络不涉及太深,当你学习Java框架,部署项目的时候,就会了解到)
6. 完整代码:
6.1 服务器代码:
以下,是整个服务器端程序,完整的代码:
/*** Created with IntelliJ IDEA.* Description:服务器端程序* Date: 2025-04-28* Time: 20:48*/
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class UdpEchoServer {private DatagramSocket socket = null;// UdpEchoServer类的构造方法,初始化socket对象public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}public void start() throws IOException {
// 启动服务器System.out.println("服务器启动");// 服务器要持续不断的读取客户端的请求数据,要有一个死循环while(true){
// 循环一次,就相当于处理一次请求
// 处理请求的过程,典型的服务器都是分为三个步骤的,分别是:
// 1. 读取请求,并解析请求数据
// 此处创建的 DatagramPacket对象,表示一个 UDP数据报,
// 此处传入的字节数组,就保存了 UDP数据报 的载荷部分DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
// 读取请求:socket.receive(requestPacket);
// 把读取到的二进制数据,转换成字符串String request = new String(requestPacket.getData(),0, requestPacket.getLength());
// 解释:1.requestpacket.getData():获取到请求数据
// 2. 0,表示从字符串的 0 下标开始存放数据,
// 3. requestpacket.getLength():获取到requestpacket中,有效的数据的长度// 2. 根据请求,计算响应(服务器最关键的逻辑)
// 回显服务器,请求是什么,响应就是什么String response = process(request);// 3. 把响应返回给客户端
// 根据 response 构造一个新的 DatagramPacket 对象,将这个对象返回给客户端DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());// 将创建好的DatagramPacket 对象(响应数据报)发送给客户端socket.send(responsePacket);// 4.打印日志System.out.printf("[%s:%d] req(请求): %s, resp(响应): %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
// requestPacket.getAddress().toString():获取请求方(客户端)的IP地址并以字符串的方式进行打印
// requestPacket.getPort():获取请求方(客户端)的端口号}}private String process(String request) {return request;}// 编写main方法,启动服务器public static void main(String[] args) throws IOException {UdpEchoServer server = new UdpEchoServer(9090);server.start();}}
6.2 客户端代码:
以下,是整个客户端程序的完整代码:
import java.io.IOException;
import java.net.*;
import java.util.Scanner;/*** Created with IntelliJ IDEA.* Description:客户端程序* Date: 2025-04-28* Time: 20:48*/
public class UdpEchoCilent {private DatagramSocket socket = null;
// 由于 UDP 协议本身,并不会保存对端的信息,我们需要在自己的代码中保存一下private String serverIP;private int serverPort;
//客户端和服务器端不一样,
//客户端这里的构造方法,是要指定访问的服务器的地址(IP 和 端口号)public UdpEchoCilent(String serverIP,int serverPort) throws SocketException {this.serverIP = serverIP;this.serverPort = serverPort;socket = new DatagramSocket();}public void start() throws IOException {
// 启动客户端程序// 客户端不断工作,发送请求数据while(true) {
// 1.从控制台读取用户输入的内容Scanner scanner = new Scanner(System.in);System.out.println("请输入要发送内容:");String request = scanner.next();// 2.把请求发送给服务器,需要构造 DatagramPacket 对象 ,将请求封装成数据包,再进行发送
// 构造过程中,不光要构造载荷(实际传输的用户数据),还要设置 服务器IP地址 和 端口号DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,InetAddress.getByName(serverIP),serverPort);// 3.发送数据包socket.send(requestPacket);// 4.接收服务器的响应内容DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);socket.receive(responsePacket);// 5.从服务器读取的数据,进行解析,并打印出来(将返回的响应数据,解析并打印)String response = new String(responsePacket.getData(),0,responsePacket.getLength());System.out.println("服务器返回的响应数据:"+response);}}public static void main(String[] args) throws IOException {UdpEchoCilent cilent = new UdpEchoCilent("127.0.0.1",9090);cilent.start();}}
总结:
本篇博客,我们主要是使用了 UDP协议的 socket API,来实现了回显程序(请求是什么,响应就是什么),虽然这个回显,在以后的工作中,不会见到。
但是,我们通过这个程序,也知道了网络编程,是怎么编程的,中间要做的事情有哪些,这些编程的思想,才是最重要的。
最后,如果看完这篇博客,你有所收获,请给我点点赞,如果这篇博客有错误的地方,或者你有什么意见,也欢迎指出!