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

相关推荐
Escalating_xu43 分钟前
【C 语言】数据在内存中的存储:补码、大小端、整型陷阱与 IEEE 754 全解析
java·c语言·网络
爱跳舞的烤冷面1 小时前
Linux篇——网络编程
网络
Knight_AL2 小时前
基于 Netty 实现的 WebSocket 服务端
网络·websocket·网络协议
wuyk5554 小时前
《WiFi 嵌入式物联网开发全套实战》| 第 31 章 休眠唤醒 WiFi 断连、时间同步、网络恢复机制
网络·物联网·php
沫璃染墨4 小时前
从零入门计算机网络系列(一):计算机网络初识——从网络发展史到TCP/IP协议》
linux·网络·网络协议·tcp/ip·计算机网络
王大傻09284 小时前
堆叠注入(Stacked Queries Injection)详解:原理、利用与防御
服务器·网络·数据库·web安全·网络安全
艾芯微科技4 小时前
钰泰 ETA5055V330DD1E|SOT-223 3.3V LDO,低压差、大电流线性稳压选型分享
网络·单片机·嵌入式硬件·集成测试·51单片机
bigcarp5 小时前
安卓模拟器MUMU+tcpdump抓包Socket协议流量
服务器·网络·tcpdump
是个西兰花5 小时前
TCP套接字编程实战:从服务器搭建到多进程处理
服务器·网络协议·tcp/ip
懂点Tech6 小时前
ctfshow MISC入门图片篇(信息附加):misc11解题思路
网络