网络编程(二):UDP数据报在客户端与服务端之间的传输

一、Java UDP核心API解析

1.1DatagramSocket------数据报套接字

|--------------------------------------------|-----------------|------|
| 构造方法 | 说明 | 典型场景 |
| DatagramSocket() | 绑定到本机任意一个随机空闲端口 | 客户端 |
| DatagramSocket(int port) | 绑定到本机指定端口 | 服务端 |
| DatagramSocket(int port, InetAddress addr) | 绑定到指定IP和端口 | 很少用 |

|--------------------------------|----------------------------------|
| 核心方法 | 说明 |
| void send(DatagramPacket p) | 发送一个数据报,该方法不会阻塞,直接发送 若网卡队列满,可能丢包 |
| void receive(DatagramPacket p) | 阻塞等待接收一个数据报,直到有数据来或超时 |
| void close() | 关闭套接字,释放底层资源 |

1.2DatagramPacket------数据包

1.用于接收数据

java 复制代码
DatagramPacket(byte[] buf, int length)

创建一个空的包,准备接收数据,接收到的数据存入buf,最多length字节。

2.用于发送数据(客户端发送请求/服务器发送响应):

java 复制代码
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)

buf:待发送的数据字节数组

offset:从数组哪个位置开始发送

length:发送多少字节

address:目标主机的IP+端口

核心方法:

|---------------------------------|-----------------------------|
| 方法 | 说明 |
| byte\[\] getData() | 获取数据字节数组(接收时获取内容,发送时获取待发数据) |
| int getLength() | 获取数据长度 |
| inetAddress getAddress() | 获取发送方或接收方的IP |
| int getPort() | 获取发送方或接收方的端口 |
| SocketAddress getSocketAddree() | 获取完整的Socket地址,常用于直接回传响应 |

二、简单实现UDP回显服务器

java 复制代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpEchoServer {
    // 先创建 socket 对象
    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[1024], 1024);
            socket.receive(requestPacket);
            // 为了处理请求方便, 把里面的载荷数据获取出来, 构造成一个 String
            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);

            // 4. 打印日志
            System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(), responsePacket.getPort() , request, response);
        }
    }

    // 此处是回显服务器. 响应就和请求完全一样.
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        // 端口号是一个整数, 我们可以随意指定.
        // 端口范围是 0-65535, < 1024 的端口一般也不使用
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

socket.receive()会阻塞,直到收到数据,使用while循环一直保持服务器运行。

每次接收都创建新的DatagramPcaket,避免前一次数据残留

三、UDP回显客户端

java 复制代码
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 {
        System.out.println("客户端启动...");

        // 客户端我们也想要让它持续运行.
        // 每循环一次, 就从控制台读取到一个输入的数据, 把这个数据发送到服务器, 读取服务器的响应.
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 1. 从控制台读取用户输入
            System.out.print("-> ");
            String request = scanner.next();

            // 2. 构造请求, 发送给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIp), this.serverPort);
            socket.send(requestPacket);

            // 3. 读取服务器的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());

            // 4. 打印出 response
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        // UdpEchoClient client = new UdpEchoClient("8.137.149.9", 9090);
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

客户端IP为127.0.0.1表示本机回环

每次发送请求,receive()会阻塞直到服务器响应。

四、调试的常见问题

4.1端口被占用(BindException)

现象:启动服务器时抛出java.net.BingException:Address already in use.

原因:端口9090已经被其他进程占用

解决:netstat -ano|findstr 9090查找到PID,任务管理器结束该进程

4.2接收缓冲区不足

如果发送的数据大于接收端DatagramPacket的缓冲区大小,多余的数据会被截断。

安全做法:缓冲区设置为最大64kb,或使用getlength()检查实际接收长度

4.3同一主机VS跨主机测试

同一主机:IP用127.0.0.1或localhost

跨主机:需要修改客户端serverIp,为服务端实际公网/内网IP,并确保防火墙允许该端口通信

相关推荐
笨鸟先飞,勤能补拙2 分钟前
AI 赋能网络安全领域深度剖析
网络·人工智能·windows·安全·web安全·网络安全·github
鲜花飘飘扬1 小时前
HTTP请求头中表示代理IP地址的属性及获取情况
网络协议·tcp/ip·http
中微极客3 小时前
多智能体编排实战:CrewAI vs AutoGen(2026版)
大数据·网络·人工智能
小王C语言3 小时前
【3. 基于 Vibe Coding 的 OJ 平台】. 构建仓库、环境准备、需求梳理、安装依赖
网络·c++
新手unity自用笔记3 小时前
unity网络基础_1
网络·unity·游戏引擎
小王C语言4 小时前
【8.进行接口测试】:通过 curl 进行接口自动化测试 / 通过 python 程序进行接口自动化测试
网络·c++
一条大祥脚4 小时前
26杭电暑期第三场 DP|费用流|分解质因子|数论|贪心|图论|期望|无穷级数
网络·图论·网络流·无穷级数·费用流
早睡早起身体好1234 小时前
为什么 Network 中会出现两个同名请求?——CORS 预检请求学习笔记
网络·笔记·学习·网络安全
daad7775 小时前
记录matlab状态机demo
java·网络·matlab
paopaokaka_luck5 小时前
基于springboot3+vue3的智能文库平台(AI智能搜索、AI智能汇总、实时在线状态展示、多格式文档预览与富文本编辑、Echarts图形化分析)
前端·网络·spring boot·网络协议·echarts