先来认识一下 UDP 的 socket api,两个核心的类:DatagramSocket、DatagramPacket.
是一个 socket 对象。
什么是 socket?
操作系统,使用文件这样的概念,来管理一些软硬件资源。网卡,操作系统也是使用 文件 的方式来管理网卡的。表示网卡的这类文件,称为 Socket 文件。Java 中的 socket 对象,就对应 系统里的 socket 文件。
因此,想要进行网络通信,必须得先有 socket 对象。
DatagramSocket
构造方法:
方法签名 | 方法说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口 (一般用于服务端) |
DatagramSocket() 在客户端这边使用,客户端使用哪个端口,是系统自动分配的。
一个客户端的主机,上面运行的程序很多,天知道你手动指定的端口是不是被别的程序占用了。因此,让系统自动分配一个端口是更明智的选择.
DatagramSocket(int port) 在服务器这边使用,服务器使用哪个端口,是手动指定的。
对于服务器来说,需要有一个固定的端口号,方便其他客户端找到。
DatagramSocket
方法:
方法签名 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
表示了一个 UDP 发送和接收的数据报。
代表了系统中设定的 UDP 数据报的二进制结构。
DatagramPacket
构造方法:
方法签名 | 说明方法 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个 DatagramPacket 用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数 length) |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) | 构造一个 DatagramPacket 用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号 |
DatagramPacket
方法:
方法签名 | 方法说明 |
---|---|
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
接下来手动写 UDP 客户端服务器
实现一个最简单的回显服务器 (Echo Server).
回显服务器:顾名思义,就是客户端发啥,服务器就返回啥。
我们知道,一个服务器可以供多个客户端同时使用,因此我们最先想到的是利用多线程来实现 UDP.
但事实上 UDP 服务器不需要多线程,是因为UDP是无连接的,每个数据包都是独立的,服务器只需要监听一个端口,接收数据包并处理即可。
核心思路:
127.0.0.1
就表示自己的电脑)实现 UDP 会用到的方法:
getSocketAddress() :就是 getAddress() 和 getPort() 的结合体. [address:port]
InetAddress.getByName(“主机名”):如果传入的是主机名,则该方法会尝试解析该主机名,如果解析成功,则返回对应的 IP 地址;如果解析失败,则抛出
UnknownHostException
异常.
getData():获取数据报中的数据.
客户端代码:
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 ip, int port) throws SocketException {
serverIp = ip;
serverPort = port;
// 这个 new 操作,就不再指定端口了,让系统自动分配一个空闲端口
socket = new DatagramSocket();
}
public void start () throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("客户端启动!");
while (true) {
// 1. 从控制台读取用户输入的内容
System.out.print("->");
String request = scanner.nextLine();
// 2. 构造请求对象,并发给服务器
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);
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
// 4. 显示到屏幕上
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
/*
客户端启动!
->hello
hello
*/
服务器代码:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//回显服务器
//客户端发的请求是啥,服务器返回的响应就是啥
public class UdpEchoServer {
private DatagramSocket socket = null;
public UdpEchoServer(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());
// 2.根据请求,计算出响应
String response = process(request);
// 3.把响应写回给客户端
// 此时需要告知网卡,要发的内容是啥,要发给谁
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());
socket.send(responsePacket);
//记录日记,方便观察程序执行效果
System.out.printf("[%s:%d] req: %s , resp: %s\n", responsePacket.getAddress().toString(), responsePacket.getPort(),request, response);
}
}
//根据请求计算响应,由于是回显程序,响应内容和请求完全一样
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090); //设定服务器的端口号为 9090
server.start();
}
}
/*
服务器启动!
[/127.0.0.1:54015] req: hello , resp: hello
*/
问题1:我电脑上的 udp 服务器,别人可以访问吗??
答:不可以,因为我当前的电脑上没有 “外网IP”。解决办法就是买一个有外网的云服务器
问题2:socket 对象用完后需要关闭吗??
答:需要,我们要知道为什么要关闭 socket 对象。最主要的就是释放系统中的 socket 文件,从而释放文件描述符。
但是上述代码中我们为什么没去关闭 socket 对象呢?
因为对于咱们这个服务器来说,DatagramSocket 不关闭,问题不大。整个程序中只有一个 socket 对象,不是频繁创建的,生命周期是跟随整个进程的。但是如果是有多个 socket 对象, 且 socket 对象生命周期更短需要频繁创建释放。一定要记得去 close 。