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

相关推荐
humors2211 小时前
【转帖】关于防范AI编程工具Claude Code安全后门隐患的风险提示
网络·安全·ai编程
深度智能科技1 小时前
IPv6无网络访问权限怎么解决?【图文讲解】Win10/Win11 IPv6无网络访问权限完整修复实操教程
网络·ipv6·深度c盘清理·ipv6协议开启自动获取ip·ipv6无网络访问权限·上不了网怎么办·防火墙拦截ipv6
梦帮科技2 小时前
Rust+Tauri+EVM:构建下一代内存安全级数字版权(DRM)主权音频网络与跨链金融系统的技术实践与全景架构
网络·安全·架构·rust·区块链·音视频·web3.py
mudtools2 小时前
HttpUtils:一个编译时生成的声明式 HTTP 客户端框架
网络·网络协议·http·.net
luj_176810 小时前
残熵算法实时化三大瓶颈突破
c语言·开发语言·网络·经验分享·算法
吴懿不在10 小时前
某次热身赛re方向wp WORDS 51,500 READ 172 MIN base64,
网络
小林ixn13 小时前
从“玩具工具”到“跨语言利器”:MCP 协议实战解析
运维·服务器·网络
2603_9547083115 小时前
全维度容错设计,打造微电网安全运行屏障
服务器·网络·数据库·人工智能·分布式·安全
青岛前景互联信息技术有限公司16 小时前
以新标准为底座,前景互联打造高危场景智能接处警新体系
大数据·网络·人工智能