目录
3.requestPacket.getSocketAddress():
4.DatagramPacket responsePacket = new DatagramPacket
在 Java 网络编程中,客户端(Client)和服务器(Server)是两个核心角色,它们共同构成了网络通信的基本模型(即 C/S 架构)。
以下我会逐行解析构建一个最简单的服务器和客户端的代码。
1.服务器
(1)基本概念
服务器是 接收请求 的一方,被动监听特定端口,处理客户端请求并返回响应。
典型场景:Web 服务器(如 Tomcat)、数据库服务器、游戏服务器等。
(2)代码实现
public class UdpEchServer {
private DatagramSocket socket=null;
public UdpEchServer(int port) throws SocketException {
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 request=new String(requestPacket.getData(),0,requestPacket.getLength());//数组中有效的长度,避免只收到很少的数据却构建了很大的byte数组
//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] request:%s, response:%s\n",
requestPacket.getAddress().toString(), // 客户端IP地址
requestPacket.getPort(), // 客户端端口号
request, // 请求内容
response // 响应内容
);
}
}
/*把根据请求计算响应的逻辑构建成了一个方法 */
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
client.start();
}
}
3.逐行解析
1)
private DatagramSocket socket=null;
DatagramSock是UDP socket ,用于发送和接受数据包。
2)
public UdpEchServer(int port) throws SocketException {
socket=new DatagramSocket(port);
}
指定一个端口号(int port)给服务器使用。因为服务器的端口号不能一直变,所以直接指定固定端口号。
3)
while(true)
因为服务器通常是7*24小时不间断的读取请求的,所以通常会用一个while(true)这样的死循环来保证服务器持续工作。循环一次,就相当于读取一次请求。
4)
DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
构建出一个用于接受请求数据报的DatagramPacket。
这里使用构造函数 DatagramPacket(byte[] buf, int length)
创建了一个 DatagramPacket
对象,其中:
byte[] buf
是用于存储接收到的数据的缓冲区。int length
是缓冲区的长度,表示可以接收的最大数据量。
5)
socket.receive(requestPacket);
这里将requestPacket作为输出型参数:
- 这一行代码是使用UDP套接字接收数据报的方法调用。
receive
方法会阻塞服务器进程,直到接收到一个数据报。 - 一旦接收到数据报,
requestPacket
将被填充接收到的数据、发送方的IP地址和端口号。
6)
String request=new String(requestPacket.getData(),0,requestPacket.getLength());
将读取到的二进制数据转换成字符串:
requestPacket.getData()
: 这个方法调用返回的是接收到的数据包中包含的字节数组。这个数组的大小是固定的(在此例中为4096字节),但其中只有部分是实际接收到的数据,所以还需要指定字节数组的有效长度。requestPacket.getLength()
: 这个方法调用返回的是实际接收到的数据的长度,也就是有效长度。由于UDP数据包的大小是固定的,而实际传输的数据可能较少,因此getLength()
的值可能小于getData().length
。这个方法确保只处理实际接收到的数据,而不是整个4096字节的数组。0
: 这是字节数组的偏移量,表示从数组的第0个位置开始读取数据。new String(...)
: 这是一个构造函数调用,用于将字节数组转换为字符串。这里使用的是特定范围的字节数组(从偏移量0开始,长度为requestPacket.getLength()
)来创建字符串,而不是整个数组。
7)
String response=process(request);
这一段代码的含义是根据请求来作出响应,一般来说是一个服务器最关键的逻辑,但是此处的服务器是回显服务器,所以这个逻辑被省略了。
可以response=request,但是此处将做出相应的逻辑构建成了一个process方法,方便后续做其他服务器:
private String process(String request) {
return request;
}
8)
DatagramPacket responsePacket = new
DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
再次构建一个DatagramPacket对象作为发送给客户端的数据报:responsePacket。
1. response.getBytes()
:
- 这个方法将 前面接受reqest后得到的
String
类型的response
转换为byte[]
类型的数组。 getBytes()
方法会根据平台的默认字符集将字符串编码为字节数组。
2.response.getBytes().length
:
- 这里调用了
response.getBytes()
方法得到的字节数组的长度。 - 这个长度表示实际响应数据的总字节数,确保发送的数据报中只包含有效的响应内容。
长度要用response.getBytes().length,因为response是字符串,需要先转成二进制数组,才能获取到数组的长度 不能用response.length,因为response是字符串,字符串的长度是字符的个数,而不是二进制数组的长度
3.requestPacket.getSocketAddress()
:
- 这个方法用于获取发送请求的数据报(
requestPacket
)的源地址(SocketAddress
)。 SocketAddress
包含了客户端的 IP 地址和端口号信息,这样服务器就能知道将响应数据发送回哪个客户端。
4.DatagramPacket responsePacket = new DatagramPacket
- 这里创建了一个新的
DatagramPacket
对象,命名为responsePacket
。 - 构造函数
DatagramPacket(byte[] buf, int length, SocketAddress address)
的参数分别为:byte[] buf
: 要发送的数据的字节数组。int length
: 数据的有效长度。SocketAddress address
: 目标地址,即要发送到的客户端地址。
9)
socket.send(responsePacket);
发送数据包给客户端,由于前面使用了requestPacket.getSocketAddress()
方法将源ip、源端口号都传给了requestPacket,所以此处UDP已经知道了该发送给谁。
10)
System.out.printf(
"[%s:%d] request:%s, response:%s\n",
requestPacket.getAddress().toString(), // 客户端IP地址
requestPacket.getPort(), // 客户端端口号
request, // 请求内容
response // 响应内容
);
打印一个日志,方便调试和监控。
11)
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
client.start();
}
在main方法中实例化服务器,并指定了服务器的端口号是9090。
2.客户端
(1)基本概念
客户端是 发起请求 的一方,主动连接服务器并发送请求,然后等待服务器的响应。
典型场景:浏览器、移动应用、桌面程序等。
(2)代码实现
public class UdpEchoClient {
private DatagramSocket socket = null;
//UDP本身不保存对方信息,在自己的代码中保存一下
private String sercerIp;
private int serverPort;
//和服务器不同,客户端的构造方法不能只有一个端口号,还要有ip地址
public UdpEchoClient(String sercerIp, int serverPort) throws SocketException {
this.sercerIp = sercerIp;
this.serverPort = serverPort;
//创建一个socket,不能传入端口号,因为端口号会有竞争,让系统随机分配一个空闲的即可
socket = new DatagramSocket();
}
//启动客户端
public void start() throws IOException {
Scanner sc = new Scanner(System.in);
while (true) {
//1.读取用户输入的数据
System.out.println("请输入要发送的数据");
String request = sc.next();
//2.将请求发送给服务器,构建一个DatagramPacket对象,发送给服务器
DatagramPacket packet = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(sercerIp), serverPort);
//3.发送数据
socket.send(packet);
//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:" + response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
client.start();
}
}
(3)逐行解析
1)
private DatagramSocket socket = null;
创建一个socket用来接收和发送数据
2)
private String sercerIp;
private int serverPort;
定义String类型的ip地址和int 类型的端口号。
3)
public UdpEchoClient(String sercerIp, int serverPort) throws SocketException {
this.sercerIp = sercerIp;
this.serverPort = serverPort;
//创建一个socket,不能传入端口号,因为端口号会有竞争,让系统随机分配一个空闲的即可
socket = new DatagramSocket();
}
创建客户端的构造方法。与服务器不同的是,参数不止有端口号,还有ip地址,因为客户端要知道服务器的ip地址和端口号以便发送,而服务器只需要一个端口号来监听、接受客户端发送来的数据。
4)
//1.读取用户输入的数据
System.out.println("请输入要发送的数据");
String request = sc.next();
读取用户输入的数据。
5)
DatagramPacket packet = new
DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName(sercerIp), serverPort);
将请求发送给服务器:构建一个DatagramPacket对象来发送。
1.request.getBytes()
来将字符串request中的字节数组取出来。
2.request.getBytes().length
指定字节数组的长度
3.InetAddress.getByName(sercerIp), serverPort
传入服务器的ip地址和端口号,其中ip地址是字符串,需要用getByname()方法来包装一下。
6)
socket.send(packet);
发送数据给服务器。
7)
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
构建一个DatagramPacket对象来作为服务器的响应数据
8)
socket.receive(responsePacket);
responsePacket作为输出型参数,得到了响应数据包里面的内容。
9)
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.println("response:" + response);
用getData()方法得到的字节数组构建一个字符串作为响应数据,指定了字节数组的长度为【0,字节数组的长度)。
打印日志。
10)
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
client.start();
}
在main方法中指定服务器的ip地址是“127.0.0.1”,这是一个环回ip,表示本机。指定服务器的端口号9090,与服务器的端口号对应。
3.结果展示
服务器:
客户端: