网络编程(二):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,并确保防火墙允许该端口通信

相关推荐
星恒讯工业路由器6 小时前
新一代通信网:从“连接万物”到“智联万物”的关键跃迁
网络·物联网·智能路由器·工业路由器·工业物联网·5g-a·新一代通信网
暴力求解8 小时前
Linux网络---传输层协议TCP(二)
linux·服务器·开发语言·网络·tcp/ip
石头猫灯8 小时前
WordPress wp2shell 漏洞链完整拆解流程
网络·数据结构·安全·web安全
网安蟹佬霸9 小时前
OSINT开源情报收集实战:从信息搜集到资产测绘(2026最新万字保姆级指南)
前端·网络·安全·web安全·网络安全·开源
游戏开发爱好者89 小时前
WebSocket 抓包怎么查看内容?从握手到消息帧完整解读
网络协议·计算机网络·网络安全·ios·adb·https·udp
80s77710 小时前
动态ip和静态ip有什么区别
服务器·网络·tcp/ip
鲁邦通物联网11 小时前
出海物联网设备的全球化网络接入与合规脱敏架构:基于 Node-RED 与 C++ 零拷贝的边缘系统设计
网络·物联网·系统架构·边缘计算·边缘计算网关·5g数采·工业级边缘计算网关
星恒讯工业路由器11 小时前
家庭无线组网方式全解析:从单路由到FTTR,哪种适合你?
网络·物联网·智能路由器·路由器·家庭组网·ac+ap·mesh组网
05664611 小时前
agent学习——流式响应与文本切分
网络·python·学习
mqiqe11 小时前
AgentScope Java 2.0 Agent 状态存储(AgentStateStore)深度解析:构建可恢复、可扩展的智能体运行时
java·运维·网络